一、反射和特性
1,反射
1.程序是用来处理数据的,文本和特性都是数据,而我们程序本身(类的定义和BLC中的类)这些也是数据。
2.有关程序及其类型的数据被称为元数据(metadata),它们保存在程序的程序集中。
3.程序在运行时,可以查看其它程序集或其本身的元数据。一个运行的程序查看本身的元数据或者其他程序集的元数据的行为叫做反射。
Type类
预定义类型(int long 和string等),BCL中的类型(Console,IEnumerable等)和程序员自定义类型(MyClass,MyDel等)。 每种类型都有自己的成员和特性。
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。
由于 Type是抽象类,因此不能利用它去实例化对象。关于Type的重要事项如下:
对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象。
程序中用到的每一个类型都会关联到独立的Type类的对象。
不管创建的类型有多少个示例,只有一个Type对象会关联到所有这些实例。
Type位于System.Reflection命名空间下
获取Type对象有两种方式
1,Type t = myInstance.GetType();//通过类的实例来获取Type对象
在object类有一个GetType的方法,返回Type对象,因为所有类都是从object继承的,所以我们可以在任何类型上使用GetType()来获取它的Type对象
2,Type t = typeof(ClassName);//直接通过typeof运算符和类名获取Type对象
//MyClass.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Fan_she_Te_Xing
{
class MyClass
{
private int id;
public int age;
public int number;
public string Name { set; get; }
public string Name1 { set; get; }
public string Name2 { set; get; }
public void Test1() { }
public void Test2() { }
}
}
//Program.cs
using System;
using System.Reflection;//注意引用
namespace Fan_she_Te_Xing
{
class Program
{
static void Main(string[] args)
{
//每一个类对应一个type对象,这个type对象存储了这个类 有哪些方法跟哪些数据 哪些成员
MyClass my = new MyClass();//一个类中的数据 是存储在对象中的, 但是type对象只存储类的成员
Type type = my.GetType();//通过对象获取这个对象所属类 的Type对象
Console.WriteLine(type.Name);//获取类的名字
Console.WriteLine(type.Namespace);//获取所在的命名空间
Console.WriteLine(type.Assembly);
FieldInfo[] array = type.GetFields();//只能获取public 字段
foreach (FieldInfo info in array)
{
Console.Write(info.Name + " ");
}
PropertyInfo[] array2 = type.GetProperties();
foreach (PropertyInfo info in array2)
{
Console.Write(info.Name + " ");
}
MethodInfo[] array3 = type.GetMethods();
foreach (MethodInfo info in array3)
{
Console.Write(info.Name + " ");
}
//通过type对象可以获取它对应的类的所有成员(public)
}
}
}
结果:
MyClass
Fan_she_Te_Xing
Fan_she_Te_Xing, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
age number Name Name1 Name2 get_Name set_Name get_Name1 set_Name1 get_Name2 set_Name2 Test1 Test2 GetType ToString Equals GetHashCode
Assembly类
Assembly类在System.Reflection命名空间中定义,它允许访问给定程序集的元数据,它也包含了可以加载和执行程序集。
如何加载程序集?
1,Assembly assembly1 = Assembly.Load(“SomeAssembly”);根据程序集的名字加载程序集,它会在本地目录和全局程序集缓存目录查找符合名字的程序集。
2,Assembly assembly2 = Assembly.LoadFrom(@“c:\xx\xx\xx\SomeAssembly.dll”)//这里的参数是程序集的完整路径名,它不会在其他位置搜索。
using System;
using System.Reflection;//注意引用
namespace Fan_she_Te_Xing
{
class Program
{
static void Main(string[] args)
{
MyClass my = new MyClass();
Assembly assem = my.GetType().Assembly;//通过类的type对象获取它所在的程序集 Assembly
Console.WriteLine(assem.FullName);
Type[] types = assem.GetTypes();
foreach (var type in types)
{
Console.WriteLine(type);
}
}
}
}
结果:
Fan_she_Te_Xing, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Fan_she_Te_Xing.MyClass
Fan_she_Te_Xing.Program
2,特性
Obsolete特性
一个程序可能在其生命周期中经历多次发布,而且很可能延续多年。在程序生命周期的后半部分,程序员经常需要编写类似功能的新方法替换老方法。处于多种原因,你可能不再使用哪些调用过时的旧方法的老代码。而只想用新编写的代码调用新方法。旧的方法不能删除,因为有些旧代码也使用的旧方法,那么如何提示程序员使用新代码呢?可以使用Obsolete特性将程序结构标注为过期的,并且在代码编译时,显示有用的警告信息。
using System;
namespace Fan_she_Te_Xing
{
class Program
{
[Obsolete("这个方法过时了,使用NewMethod代替")] //obsolete特性用来表示一个方法被弃用了
static void OldMethod()
{
Console.WriteLine("Oldmethod");
}
static void NewMethod()
{
}
static void Main(string[] args)
{
NewMethod();
OldMethod()
}
}
}
Conditional特性
Conditional特性允许我们包括或取消特定方法的所有调用。为方法声明应用Conditional特性并把编译符作为参数来使用。
定义方法的CIL代码本身总是会包含在程序集中,只是调用代码会被插入或忽略。
//#define IsTest //定义一个宏。通过注释这个宏可以决定test1是否能使用
using System;
using System.Diagnostics;//注意这个引用
namespace Fan_she_Te_Xing
{
class Program
{
[Conditional("IsTest")]
static void Test1()
{
Console.WriteLine("test1");
}
static void Test2()
{
Console.WriteLine("test2");
}
static void Main(string[] args)
{
Test1();
Test2();
}
}
}
并不会报错,直接跳过了Test1()的的调用。
调用者信息特性
调用者信息特性可以访问文件路径,代码行数,调用成员的名称等源代码信息。这个三个特性名称为CallerFilePath,CallerLineNumber和CallerMemberName。
using System;
using System.Runtime.CompilerServices;//注意这个引用
namespace Fan_she_Te_Xing
{
class Program
{
static void PrintOut(string str, [CallerFilePath] string fileName = "", [CallerLineNumber] int lineNumber = 0, [CallerMemberName] string methodName = "")
{
Console.WriteLine(str);
Console.WriteLine(fileName);
Console.WriteLine(lineNumber);
Console.WriteLine(methodName);
}
static void Main(string[] args)
{
PrintOut("123");
}
}
}
结果:
123
C:\Users\Administrator\source\repos\Fan_she_Te_Xing\Program.cs
18
Main
DebuggerStepThrough特性
我们在单步调试代码的时候,常常希望调试器不要进入某些方法。我们只想执行该方法,然后继续调试下一行。DebuggerStepThrough特性告诉调试器在执行目标代码时不要进入该方法调试。有些方法小并且毫无疑问是正确的,在调试时对其反复单步调试只能徒增烦恼。要小心使用该特性,不要排除了可能出现bug的代码。
其他预定义特性
特性 意义
CLSCompliant 声明可公开的成员应该被编译器检查是否符合CLS。兼容的程序集可以被任何.NET兼容的语言使用
Serializable 声明结构可以被序列化
NonSerialized 声明结构不可以被序列化
DLLImport 声明是非托管代码实现的
WebMethod 声明方法应该被作为XML Web服务的一部分暴露
AttributeUsage 声明特性能应用到什么类型的程序结构。将这个特性应用到特性声明上
什么是特性?
特性(attribute)是一种允许我们向程序的程序集增加元数据的语言结构。它是用于保存程序结构信息的某种特殊类型的类。
将应用了特性的程序结构叫做目标
设计用来获取和使用元数据的程序(对象浏览器)叫做特性的消费者
.NET预定了很多特性,我们也可以声明自定义特性
自定义特性
应用特性的语法和之前见过的其他语法很不相同。你可能会觉得特性跟结构是完全不同的类型,其实不是,特性只是某个特殊结构的类。所有的特性类都派生自System.Attribute。
//MyTestAttribute.cs
using System;
using System.Collections.Generic;
using System.Text;
namespace Fan_she_Te_Xing
{ //1, 特性类的后缀以Attribute结尾
//2, 需要继承自System.Attribute
//3, 一般情况下声明为 sealed
//4, 一般情况下 特性类用来表示目标结构的一些状态(定义一些字段或者属性, 一般不定义方法)
[AttributeUsage(AttributeTargets.Class)]//表示该特性类可以应用到的程序结构有哪些
sealed class MyTestAttribute:System.Attribute
{
public string Description { get; set; }
public string VersionNumber { get; set; }
public int ID { get; set; }
public MyTestAttribute(string des)
{
this.Description = des;
}
}
}
//Program.cs
using System;
namespace Fan_she_Te_Xing
{
[MyTest("简单的特性类", ID = 100)]//当我们使用特性的时候,后面的Attribute不需要写
class Program
{
static void Main(string[] args)
{
Type type = typeof (Program);//通过typeof+类名也可以获取type对象
object[] array = type.GetCustomAttributes(false);
MyTestAttribute mytest = array[0] as MyTestAttribute;
Console.WriteLine(mytest.Description);
Console.WriteLine(mytest.ID);
Console.ReadKey();
}
}
}
二、多线程
对于所有需要等待的操作,例如移动文件,数据库和网络访问都需要一定的时间,此时就可以启动一个新的线程,同时完成其他任务。一个进程的多个线程可以同时运行在不同的CPU上或多核CPU的不同内核上。
线程是程序中独立的指令流。在VS编辑器中输入代码的时候,系统会分析代码,用下划线标注遗漏的分号和其他语法错误,这就是用一个后台线程完成。Word文档需要一个线程等待用户输入,另一个线程进行后台搜索,第三个线程将写入的数据存储在临时文件中。运行在服务器上的应用程序中等待客户请求的线程成为侦听器线程。
进程包含资源,如Window句柄,文件系统句柄或其他内核对象。每个进程都分配的虚拟内存。一个进程至少包含一个线程。
一个应用程序启动,一般会启动一个进程,然后进程启动多个线程。
进程和线程的一个简单解释
1,计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
2,如果工厂的电力有限一次只能供给一个车间使用。也就是说一个车间开工的时候,其他车间就必须停工。背后的含义就是。单个CPU一次只能运行一个任务。(多核CPU可以运行多个任务)
3,进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
4,一个车间里,可以有很多工人,他们协同完成一个任务。
5,线程就好比车间里的工人。一个进程可以包括多个线程。
6,车间的控件是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享空间。
7,进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
8,一个防止他人进入的简单方法,就是门口加一把锁(厕所)。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫"互斥锁"(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域。
9,还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
10,这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做"信号量"(Semaphore),用来保证多个线程不会互相冲突。
不难看出,mutex是semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为mutex较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计。
11,操作系统的设计,因此可以归结为三点:
(1)以多进程形式,允许多个任务同时运行;
(2)以多线程形式,允许单个任务分成不同的部分运行;
(3)提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
资料来源-阮一峰
1,委托创建线程(.NET Core已不支持)
//注意这段代码并没有经过验证,会报Operation is not supported on this platform.错误
using System;
using System.Threading;
namespace Duo_Xian_Cheng
{
class Program
{ //一般我们会为比较耗时的操作 开启单独的线程去执行,比如下载操作
static int Test(int i, string str)
{
Console.WriteLine("test" + i + str);
Thread.Sleep(100);//让当前线程休眠(暂停线程的执行) 单位ms
return 100;
}
static void Main(string[] args)
{ //1,通过委托 开启一个线程
Func<int, string, int> a = Test;
IAsyncResult ar = a.BeginInvoke(100, "期颐", null, null);
//IAsyncResult 可以取得当前线程的状态
//检测线程结束
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);
//1000毫秒表示超时时间,如果等待了1000毫秒 线程还没有结束的话 那么这个方法会返回false 如果在1000毫秒以内线程结束了,那么这个方法会返回true
if (isEnd)
{
int res = a.EndInvoke(ar);
Console.WriteLine(res);
}
//通过回调 检测线程结束
Func<int, string, int> b = Test;
//倒数第二个参数是一个委托类型的参数,表示回调函数,就是当线程结束的时候会调用这个委托指向的方法 倒数第一个参数用来给回调函数传递数据
IAsyncResult cr = b.BeginInvoke(100, "期颐", OnCallBack, b);// 开启一个新的线程去执行 a所引用的方法
b.BeginInvoke(100, "siki", br =>
{
int res = b.EndInvoke(br);
Console.WriteLine(res + "在lambda表达式中取得");
}, null);
}
static void OnCallBack(IAsyncResult br)
{
Func<int, string, int> b = br.AsyncState as Func<int, string, int>;
int res = b.EndInvoke(br);
Console.WriteLine(res + "在回调函数中取得结果");
}
}
}
错误信息
System.PlatformNotSupportedException
HResult=0x80131539
Message=Operation is not supported on this platform.
Source=System.Private.CoreLib
StackTrace:
at System.Func`3.BeginInvoke(T1 arg1, T2 arg2, AsyncCallback callback, Object object)
at Duo_Xian_Cheng.Program.Main(String[] args) in C:\Users\Administrator\Desktop\IT\code\Duo_Xian_Cheng\Program.cs:line 17
因为.NET Core不支持BeginInvoke和EndInvoke委托调用。
2,Thread发起线程
//MyThread.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace Duo_Xian_Cheng
{
class MyThread
{
private string filename;
private string filepath;
public MyThread(string filename, string filepath)
{
this.filename = filename;
this.filepath = filepath;
}
public void DownFile()
{
Console.WriteLine("开始下载" + filepath + filename);
Thread.Sleep(2000);
Console.WriteLine("下载完成");
}
}
}
//Program.cs
using System;
using System.Threading;
namespace Duo_Xian_Cheng
{
class Program
{
static void DownloadFile1()
{
Console.WriteLine("开始下载:");
Thread.Sleep(2000);
Console.WriteLine("下载完成");
}
static void DownloadFile(object filename)
{
Console.WriteLine("开始下载:" + Thread.CurrentThread.ManagedThreadId + filename);
Thread.Sleep(2000);
Console.WriteLine("下载完成");
}
static void Main(string[] args)
{
//无参形式
Thread t1 = new Thread(DownloadFile1);//创建出来Thread对象,这个线程并没有启动
t1.Start();//开始,开始去执行线程
//lamda写法
Thread t2 = new Thread(() =>
{
Console.WriteLine("开始下载:" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.WriteLine("下载完成");
});
t2.Start();
//有参形式
Thread t3 = new Thread(DownloadFile);//创建出来Thread对象,这个线程并没有启动
t3.Start("xxx.种子");//开始,开始去执行线程
//类对象形式
MyThread my = new MyThread("xxx.bt", "http://www.xxx.bbs");
Thread t = new Thread(my.DownFile);//我们构造一个thread对象的时候,可以传递一个静态方法,也可以传递一个对象的普通方法
t.Start();
}
}
}
1,获取线程的状态(Running还是Unstarted,),当我们通过调用Thread对象的Start方法,可以创建线程,但是调用了Start方法之后,新线程不是马上进入Running状态,而是出于Unstarted状态,只有当操作系统的线程调度器选择了要运行的线程,这个线程的状态才会修改为Running状态。我们使用Thread.Sleep()方法可以让当前线程休眠进入WaitSleepJoin状态。
2,使用Thread对象的Abort()方法可以停止线程。调用这个方法,会在终止要终止的线程中抛出一个ThreadAbortException类型的异常,我们可以try catch这个异常,然后在线程结束前做一些清理的工作。
3,如果需要等待线程的结束,可以调用Thread对象的Join方法,表示把Thread加入进来,停止当前线程,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止。
3,后台线程和前台线程
只有一个前台线程在运行,应用程序的进程就在运行,如果多个前台线程在运行,但是Main方法结束了,应用程序的进程仍然是运行的,直到所有的前台线程完成其任务为止。
在默认情况下,用Thread类创建的线程是前台线程。线程池中的线程总是后台线程。
在用Thread类创建线程的时候,可以设置IsBackground属性,表示它是一个前台线程还是一个后台线程。
后台线程用的地方:如果关闭Word应用程序,拼写检查器继续运行就没有意义了,在关闭应用程序的时候,拼写检查线程就可以关闭。
当所有的前台线程运行完毕,如果还有后台线程运行的话,所有的后台线程会被终止掉。
4,线程池
创建线程需要时间。 如果有不同的小任务要完成,就可以事先创建许多线程 , 在应完成这些任务时发出请求。 这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。
不需要 自己创建线程池,系统已经有一个ThreadPool类管理线程。 这个类会在需要时增减池中线程的线程数,直到达到最大的线程数。 池中的最大线程数是可配置的。 在双核 CPU中 ,默认设置为1023个工作线程和 1000个 I/o线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。 如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。
using System;
using System.Threading;
namespace Duo_Xian_Cheng
{
class Program
{
static void ThreadMethod(object state)
{
Console.WriteLine("线程开始:" + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.WriteLine("线程结束");
}
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(ThreadMethod);//开启一个工作线程
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
}
}
}
5,任务的方式开启线程
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Duo_Xian_Cheng
{
class Program
{
static void ThreadMethod()
{
Console.WriteLine("任务开始");
Thread.Sleep(2000);
Console.WriteLine("任务结束");
}
static void Main(string[] args)
{
Task t1 = new Task(ThreadMethod);//传递一个需要线程去执行的方法
t1.Start();
TaskFactory tf = new TaskFactory();
Task t = tf.StartNew(ThreadMethod);
Console.ReadKey();
}
}
}
连续任务
如果一个任务t1的执行是依赖于另一个任务t2的,那么就需要在这个任务t2执行完毕后才开始执行t1。这个时候我们可以使用连续任务。
//代码仅供示意,无实际含义
static void DoFirst(){
Console.WriteLine("do in task : "+Task.CurrentId);
Thread.Sleep(3000);
}
static void DoSecond(Task t){
Console.WriteLine("task "+t.Id+" finished.");
Console.WriteLine("this task id is "+Task.CurrentId);
Thread.Sleep(3000);
}
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);
Task t3 = t1.ContinueWith(DoSecond);
Task t4 = t2.ContinueWith(DoSecond);
Task t5 = t1.ContinueWith(DoError,TaskContinuationOptions.OnlyOnFaulted);
任务层次结构
我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完,它的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion
//代码仅供示意,无实际含义
static void Main(){
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
Console.ReadKey();
}
static void ParentTask(){
Console.WriteLine("task id "+Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child , parent end");
}
static void ChildTask(){
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished ");
}
6,线程加锁
//yThreadObject.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _020_线程争用问题 {
class MyThreadObject
{
private int state = 5;
public void ChangeState()
{
state++;
if (state == 5)
{
Console.WriteLine("state=5");
}
state = 5;
}
}
}
//Program.cs
using System;
using System.Threading;
using _020_线程争用问题;
namespace _020_线程问题_争用条件 {
class Program {
static void ChangeState(object o)
{
MyThreadObject m = o as MyThreadObject;
while (true)
{
lock (m)//向系统申请可不可以 锁定m对象 如果m对象没有被锁定,那么可以 如果m对象呗锁定了,那么这个语句会暂停,知道申请到m对象
{
m.ChangeState();//在同一时刻 只有一个线程在执行这个方法
}//释放对m的锁定
}
}
static void Main(string[] args) {
MyThreadObject m = new MyThreadObject();
Thread t = new Thread(ChangeState);
t.Start(m);
new Thread(ChangeState).Start(m);
Console.ReadKey();
}
}
}