1.什么是元数据(MetaData)和反射(reflection)
一般情况下我们的程序都在处理数据的读、写、操作和展示。但是有些程序操作的数据不是数字、文本、图片,而是程序和程序类型本身的信息。
①元数据是包含程序以及类型信息的数据,它保存在程序的程序集当中。
②程序在运行的时候,可以查看其他程序集或者其本身的元数据。这个行为就是反射。
2.Type类
BCL声明了一个Type类型(它是抽象类),用来包含类型的特性。使用这个类的对象能让我们获取程序使用的类型的信息。
由于Type是抽象类,所以它不能被实例化。而是在运行时,CLR创建从Type(RuntimeType)派生的类型的实例。当我们要访问这些实例的时候,CLR不会返回派生类的引用而是返回Type基类的引用。
关于Type有如下重要的点:
①对于程序每一个需要用到的类型,CLR会穿件一个包含这个类型信息的Type类型的对象(真实的是上面说的派生的类型的实例)。
②程序中用到的每一个类型都会关联到独立的Type类的对两个象。
③无论创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例。就像下面的图表示的一样。创建了一个OtherClass的实例oc、以及两个MyClass的实例mc1和mc2,但是在堆上都只会有一个Type对象来的对应他们,如下面的图示:
简单看一下Type这个类型,里面可以看到如下的一些方法和属性。
官方文档更全面哦: https://docs.microsoft.com/zh-cn/dotnet/api/system.type?view=netframework-4.8
3.学习如何获取一个Type类对象
方法一:通过GetType方法
object类型包含了一个GetType方法,它可以用来返回事例的Type对象引用。由于所有的类都是继承自object类型,所以所有的类都可以调用GetType来获得Type类型对象的引用。下面的图很好的说明了,基类、派生类和object之间的关系
所以下面的代码,在遍历派生类的Field的时候才能,把基类的也输出出来。
//基类 class BaseClass { public int BaseField = 0; } //派生类 class DerivedClass : BaseClass { public int DerivedField = 0; } class Program { static void Main(string[] args) { var bc = new BaseClass(); var dc = new DerivedClass(); BaseClass[] bca = new BaseClass[] { bc, dc }; foreach(var v in bca) { //获取类型 Type t = v.GetType(); Console.WriteLine("Object Type: {0}", t.Name); //获取类中的字段 FieldInfo[] fi = t.GetFields(); foreach (var f in fi) Console.WriteLine(" Field:{0}", f.Name); Console.WriteLine(); } Console.WriteLine("End!"); Console.ReadKey(); } }
结果:
Object Type: BaseClass
Field:BaseField
Object Type: DerivedClass
Field:DerivedField
Field:BaseField
End!
方法二:还可以通过typeof()方法来获取一个类型的Type对象引用。例如下面的代码:
Type t = typeof(DerivedClass);
此外我们可以根据程序集来获取程序集内的类型
//通过程序集获取类型 var baseType = Assembly.GetExecutingAssembly().GetType("TestDemo.BaseClass"); var derivedType = Assembly.GetExecutingAssembly().GetType("TestDemo.DerivedClass");
4.常用的操作
结合GetType和typeof操作,可以做很多事情。
①获取数组类型
static void Main(string[] args) { var intArray = typeof(int).MakeArrayType(); var int3Array = typeof(int).MakeArrayType(3); Console.WriteLine($"是否是int 数组 intArray == typeof(int[]) :{intArray == typeof(int[]) }"); Console.WriteLine($"是否是int 3维数组 intArray3 == typeof(int[]) :{int3Array == typeof(int[]) }"); Console.WriteLine($"是否是int 3维数组 intArray3 == typeof(int[,,]):{int3Array == typeof(int[,,]) }"); //数组元素的类型 Type elementType = intArray.GetElementType(); Type elementType2 = int3Array.GetElementType(); Console.WriteLine($"{intArray}类型元素类型:{elementType }"); Console.WriteLine($"{int3Array}类型元素类型:{elementType2 }"); //获取数组的维数 var rank = int3Array.GetArrayRank(); Console.WriteLine($"{int3Array}类型维数:{rank }"); Console.ReadKey(); }
如上面的例子,
MakeArrayType() 可以用来获取数组类型,有一个参数是数组的维数
GetElementType() 可以用来获取数组元素的类型
GetArrayRank() 可以获取数组的维数
②获取嵌套类型
public class Class { public class Student { public string Name { get; set; } } } class Program { static void Main(string[] args) { #region 嵌套类型 var classType = typeof(Class); foreach (var t in classType.GetNestedTypes()) { Console.WriteLine($"NestedType ={t}"); //获取一个值,该值指示 System.Type 是否声明为公共类型。 Console.WriteLine($"{t}访问 {t.IsPublic}"); //获取一个值,通过该值指示类是否是嵌套的并且声明为公共的。 Console.WriteLine($"{t}访问 {t.IsNestedPublic}"); } Console.ReadKey(); #endregion } }
输出:
NestedType =TestDemo.Class+Student TestDemo.Class+Student访问 False TestDemo.Class+Student访问 True
③获取类型名称
Type里面具有NameSpace、Name和FullName属性。一般FullName是两者的组合。但是对于嵌套类型和封闭式泛型不成立。可以参考下面的demo
static void Main(string[] args) { #region 获取名称 var type = typeof(Class); Console.WriteLine($" ------------一般类型-------------"); PrintTypeName(type); //嵌套类型 Console.WriteLine($" ------------嵌套类型-------------"); foreach (var t in type.GetNestedTypes()) { PrintTypeName(t); } var type2 = typeof(Dictionary<,>); //非封闭式泛型 var type3 = typeof(Dictionary<string, int>); //封闭式泛型 Console.WriteLine($" ------------非封闭式泛型-------------"); PrintTypeName(type2); Console.WriteLine($" ------------封闭式泛型-------------"); PrintTypeName(type3); Console.ReadKey(); #endregion } private static void PrintTypeName(Type t) { Console.WriteLine($"NameSpace: {t.Namespace}"); Console.WriteLine($"Name :{t.Name}"); Console.WriteLine($"FullName: {t.FullName}"); }
结果:
------------一般类型------------- NameSpace: TestDemo Name :Class FullName: TestDemo.Class ------------嵌套类型------------- NameSpace: TestDemo Name :Student FullName: TestDemo.Class+Student NameSpace: TestDemo Name :Teacher FullName: TestDemo.Class+Teacher ------------非封闭式泛型------------- NameSpace: System.Collections.Generic Name :Dictionary`2 FullName: System.Collections.Generic.Dictionary`2 ------------封闭式泛型------------- NameSpace: System.Collections.Generic Name :Dictionary`2 FullName: System.Collections.Generic.Dictionary`2[ [System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089], [System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
④获取基类类型和接口类型
var base1 = typeof(System.String).BaseType; var base2 = typeof(System.IO.FileStream).BaseType; var base3 = typeof(DerivedClass).BaseType; Console.WriteLine($"base1 :{base1.Name}"); Console.WriteLine($"base2 :{base2.Name}"); Console.WriteLine($"base3 :{base3.Name}"); foreach (var iType in typeof(Guid).GetInterfaces()) { Console.WriteLine($"iType :{iType.Name}"); }
输出:
base1 :Object base2 :Stream base3 :BaseClass iType :IFormattable iType :IComparable iType :IComparable`1 iType :IEquatable`1
此外Type还有两个方法:
我们在判断某个实例对象是否是某个类型的时候,经常使用 is语句。
Type中的方法 IsInstanceOfType 其实和is是等价的。
var baseClassObject = new BaseClass(); var check1 = baseClassObject is BaseClass; var check2 = base3.IsInstanceOfType(baseClassObject); Console.WriteLine($"使用is判断类型是否相同 :{check1}"); //结果True Console.WriteLine($"使用IsInstanceOfType类型是否相同 :{check2 }"); //结果True
返回结果都是True的。
还有一个是 IsAssignableFrom ,它的作用是确定指定类型的实例是否可以分配给当前类型的实例。
var base4 = typeof(BaseClass); //baseClass的实例 var baseClassObject = new BaseClass(); var derivedClassObject = new DerivedClass(); var classObject = new Class(); var checkResult1 = base4.IsAssignableFrom(baseClassObject.GetType()); //判断BaseClass类型是否可以分配给BassClass类型 var checkResult2 = base4.IsAssignableFrom(derivedClassObject.GetType()); //判断DerivedClass类型是否可以分配给BassClass类型 var checkResult3 = base4.IsAssignableFrom(classObject.GetType()); //判断Class类型是否可以分配给BassClass类型 Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult1}"); //True Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult2}"); //True Console.WriteLine($"使用IsAssignableFrom类型是否和接受的类型一致 :{checkResult3}"); //False
⑤实例化类型
I. 有两种方法可以动态的实例化类型。
方法一 通过静态的 Activator.CreateInstance()方法创建,它有多个重载函数。
var dateTime1 = (DateTime)Activator.CreateInstance(typeof(DateTime),2019,6,19); var dateTime2 = (DateTime)Activator.CreateInstance(typeof(DateTime), 2019,6,19,10,10,10); Console.WriteLine($"DateTime1: {dateTime1}"); //DateTime1: 2019/6/19 0:00:00 Console.WriteLine($"DateTime2: {dateTime2}"); //DateTime2: 2019/6/19 10:10:10
一般我们像上面一样都是传一个Type和构造函数的参数。当不存在这个构造函数的时候,就会抛出错误。
方法二 调用ConstructInfo对象上面的Invoke方法,ConstructInfo对象是通过调用类型(高级环境)上的GetConstructor方法获取的。
先分析一下场景,例如我有下面这样的一个类型:
public class InvokeClass { private string _testString; private long _testInt; public InvokeClass(string abc) { _testString = abc; } public InvokeClass(StringBuilder abc) { _testString = abc.ToString(); } public InvokeClass(string abc,long def) { _testString = abc; _testInt = def; } }
存在两个构造函数,一个传入的是string类型,一个传入的是StringBuilder类型,此时如果我通过new 的方式去创建一个对象,并传入构造函数为null,那么就是报出下面的错误:说明存在二义性,也就是说找不到对应使用哪个来构造。
同样的,如果我使用方法一 Activator.CreateInstance 去创建对象,会出现下面的问题:找不到对应的构造函数。
但是采用ConstructInfo的方式就可以指定对应的构造函数了。类似如下代码
//找到一个参数为string的构造函数 var constructorInfo = typeof(InvokeClass).GetConstructor(new[] { typeof(string)}); //使用该构造函数传入一个null参数 var obj4 = (InvokeClass)constructorInfo.Invoke(new object[] { null });
还可以结合查询来找到对应的构造函数
//获取所有的构造函数 var constructorInfoArray = typeof(InvokeClass).GetConstructors(); //过滤一次,获取所有两个参数的构造函数 var constructorInfoArray2 = Array.FindAll(constructorInfoArray, x => x.GetParameters().Length == 2); //最后找的第二个参数是long类型的构造函数 var constructorInfo2 = Array.Find(constructorInfoArray2, x => x.GetParameters()[1].ParameterType == typeof(long)); //如果存在,就创建对象 if (constructorInfo2 != null) { var obj5 = (InvokeClass)constructorInfo2.Invoke(new object[] { "abc", 123 }); }
动态构造对象的缺点就是慢,简单对比一下,采用反射和new创建100万个对象,耗时对比还是比较明显的。
var sw = new Stopwatch(); sw.Start(); for (int i = 0; i < 100000; i++) { var obj3 = (InvokeClass)Activator.CreateInstance(typeof(InvokeClass), "abc", 123); } sw.Stop(); Console.WriteLine($"时间:{sw.ElapsedMilliseconds}ms"); var sw2 = new Stopwatch(); sw2.Start(); for (int i = 0; i < 100000; i++) { var obj = new InvokeClass("abc", 123); } sw2.Stop(); Console.WriteLine($"时间:{sw2.ElapsedMilliseconds}ms");
输出:
时间:280ms
时间:1ms
II. 实例化委托
动态创建静态方法和实例方法的委托传入的参数不太一样,使用的是CreateDelegate的重载,可以参考下面的例子
/// <summary> /// 创建指定类型的委托,该委托表示要对指定的类实例调用的指定实例方法。 /// </summary> /// <param name="type">要创建的委托的 System.Type</param> /// <param name="target"> 类实例,对其调用 method</param> /// <param name="method">委托要表示的实例方法的名称</param> /// <returns></returns> public static Delegate CreateDelegate(Type type, object target, string method); /// <summary> /// 创建指定类型的委托,该委托表示指定类的指定静态方法。 /// </summary> /// <param name="type">要创建的委托的 System.Type</param> /// <param name="target"> 表示实现 method 的类的 System.Type</param> /// <param name="method"> 委托要表示的静态方法的名称。</param> /// <returns></returns> public static Delegate CreateDelegate(Type type, Type target, string method);
例如:
class Program { public static int StaticSum(int a, int b) { return a + b; } public int InstanceSum(int a, int b) { return a + b; } //创建一个委托 delegate int delegateOperate(int a, int b); static void Main(string[] args) { #region 实例化委托 //静态方法的委托 Delegate staticD = Delegate.CreateDelegate(typeof(delegateOperate), typeof(Program), "StaticSum"); //实例方法的委托 Delegate instanceD = Delegate.CreateDelegate(typeof(delegateOperate), new Program(), "InstanceSum"); Console.WriteLine($"staticD:{staticD.DynamicInvoke(1,2)}"); Console.WriteLine($"instanceD:{instanceD.DynamicInvoke(10,20)}"); #endregion Console.ReadKey(); } }
III.范型的实例化
泛型分为封闭型和未封闭型,对于封闭类型的泛型是可以通过反射进行实例化的,而未封闭的泛型不能实例化。如下图所示:
封闭式的泛型和未绑定的泛型是可以相互转换的。
①未绑定的泛型可以通过 MakeGenericType 变成封闭的
②封闭的可以通过GetGenericTypeDefinition 获取未绑定的类型。
class Program { static void Main(string[] args) { Type closed = typeof(List<int>); Type unBound = typeof(List<>); //转换 var newClosed = unBound.MakeGenericType(typeof(int)); var newUnBound = closed.GetGenericTypeDefinition(); Console.WriteLine($"List<int> 类型{closed}"); Console.WriteLine($"List<> 类型{unBound}"); Console.WriteLine($"List<> MakeGenericType执行后 类型{newClosed}"); Console.WriteLine($"List<int> GetGenericTypeDefinition执行后 类型{newUnBound}"); } }
参考: 《C#图解教程》、《果壳中的C#》