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 = ;
} //派生类
class DerivedClass : BaseClass
{
public int DerivedField = ;
} 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(); 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($"\n------------一般类型-------------");
PrintTypeName(type); //嵌套类型
Console.WriteLine($"\n------------嵌套类型-------------");
foreach (var t in type.GetNestedTypes())
{
PrintTypeName(t);
} var type2 = typeof(Dictionary<,>); //非封闭式泛型
var type3 = typeof(Dictionary<string, int>); //封闭式泛型 Console.WriteLine($"\n------------非封闭式泛型-------------");
PrintTypeName(type2);
Console.WriteLine($"\n------------封闭式泛型-------------");
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`
FullName: System.Collections.Generic.Dictionary` ------------封闭式泛型-------------
NameSpace: System.Collections.Generic
Name :Dictionary`
FullName: System.Collections.Generic.Dictionary`[
[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`
iType :IEquatable`

此外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 = ; i < ; i++)
{
var obj3 = (InvokeClass)Activator.CreateInstance(typeof(InvokeClass), "abc", );
}
sw.Stop();
Console.WriteLine($"时间:{sw.ElapsedMilliseconds}ms"); var sw2 = new Stopwatch();
sw2.Start();
for (int i = ; i < ; i++)
{
var obj = new InvokeClass("abc", ); }
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#》

最新文章

  1. 【微框架】Maven +SpringBoot 集成 阿里大鱼 短信接口详解与Demo
  2. 【原】聊一聊 url 编码问题
  3. 十大经典排序算法总结(JavaScript描述)
  4. android 生命周期
  5. ue4 FPaths各目录
  6. [原创]可动态显示圆形图像或圆形文字的AvatarImageView
  7. powerdesigner12.5 设置表字符集和存储引擎
  8. HTTP 错误 500.19 - Internal Server Error
  9. ThinkPHP5中Session的使用
  10. (六)6.15 Neurons Networks Deep Belief Networks
  11. Android的布局优化之include、merge 、viewstub
  12. EF中的连接字符串
  13. php获取post参数的几种方式
  14. 联想A800新蜂ROM V1.1 基于官方4.0.4精简省电稳定
  15. JavaScript :memory leak [转]
  16. JS复习第五章
  17. Cognos报表调度与作业管理
  18. 嵌入页面的几种方法(转载自萤火虫小Q)
  19. 五、Python-字典与集合
  20. 实现Python与STM32通信

热门文章

  1. 聊聊Docker理论知识(二)
  2. java面试考点-面试准备
  3. Andrew Ng机器学习课程17(2)
  4. 最新 腾讯java校招面试题(含整理过的面试题大全)
  5. NIO理解
  6. solr后台【web页面】增删改查
  7. 结合 Nginx 谈谈 Http 状态码
  8. 如何安装Oracle--新手安装Oracle教程
  9. c# 面向对象/继承关系设计
  10. 《Mysql 锁 - 概述》