前言
在探究地球内部的结构中,如何做到在地球表面不用深入地球内部就可以知道内部的构造呢?其实,向地球发射“地震波”。利用这种方式,可以判断地球放回的情况,大体上,我们也可以断定地球内部的构造了。
从这个例子中,通过一个对象的外部去了解对象内部的构造,都是利用了波的反射功能。而利用这种原理,在编程程序时,我们如何也可以实现从对象的外部来了解对象以及程序集内部的结构功能?在.NET中的反射,不仅可以实现外部对内部的了解,也同时可以动态创建出对象并执行其中的方法。
反射是.NET中的重要机制,通过反射,可以在运行时获得程序或程序集中每一个类型(包括类、结构、委托、接口和枚举等)的成员和成员的信息。有了反射,即可对每一个类型了如指掌。另外我还可以直接创建对象,即使这个对象的类型在编译时还不知道。
开始
一、使用的命名空间
System.Reflection
System.Type
System.Reflection.Assembly
二、主要的类
System.Type 类--通过这个类可以访问任何给定数据类型的信息。
System.Reflection.Assembly类--它可以用于访问给定程序集的信息,或者把这个程序集加载到程序中。
说明
一、System.Type类
System.Type类对反射起着核心的作用。它是一个抽象的基类,Type有与每种数据类型对应的派生类,我们使用这个派生类的对象的方法、字段、属性来查找有关该类型的所有信息。
表示类型声明:类类型、接口类型、数组类型、值类型、枚举类型、类型参数、泛型类型定义,以及开放或封闭构造的泛型类型。
从Type中解析类型信息:
A、判断给定类型的引用的常用方式:
1. 使用C# typeof运算符
Type t = typeof(string);
2. 使用对象GetType()方法
string s = "i3yuan"; Type t2 = s.GetType();
3.调用静态Type类的静态方法GetType()
Type t3 = Type.GetType("System.String");
以上三种方式获取类型Type后,可以应用t来探测string里面的结构
foreach (MemberInfo mi in t.GetMembers()) { Console.WriteLine("{0}/t{1}", mi.MemberType, mi.Name); }
B、Type类属性:
1.命名空间和类型名
Name 数据类型名
FullName 数据类型的完全限定名(包括命名空间名)
Namespace 定义数据类型的命名空间名
2. 类和委托
Type.IsClass 判断一个类型是否为类或者委托。符合条件的会有普通的类(包括泛型)、抽象类(abstract class)、委托(delegate)
3. 是否泛型
Type.IsGenericType 属性可以判断类或委托是否为泛型类型。
Type.IsGenericTypeDefinition 属性可以判断Type是否是未绑定参数类型的泛型类型。
Type.IsConstructedGenericType 属性判断是否可以此Type创建泛型实例。
4.访问修饰符
Type.IsPublic 判断该类型是否是公有的
Type.IsNotPublic
5.密封类、静态类型、抽象类
Type.IsSealed 判断该类型是否是密封类,密封类不能被继承
IsAbstract 指示该类型是否是抽象类型
6. 值类型
Type.IsValueType 判断一个 Type 是否为值类型,简单值类型、结构体、枚举,都符合要求。
Type.IsEnum 判断该类型是否是枚举
Type.IsPrimitive 判断Type是否为基础类型
7.接口
Type.IsInterface 判断该类型是否是接口
8.数组
IsArray 判断该类型是否是数组,GetArrayRank()
获取数组的维数。
从Type类解析类型成员结构
一个类由以下一个或多个成员组成:
成员类型 | 说明 |
---|---|
PropertyInfo | 类型的属性信息 |
FieldInfo | 类型的字段信息 |
ConstructorInfo | 类型的构造函数信息 |
MethodInfo | 类型的方法 |
ParameterInfo | 构造函数或方法的参数 |
EventInfo | 类型的事件 |
C、Type类的方法
public class MySqlHelper { public MySqlHelper() { Console.WriteLine("{0}被构造", this.GetType().Name); } public void Query() { Console.WriteLine("{0}.Query", this.GetType().Name); } }
GetConstructor(), GetConstructors():返回ConstructorInfo类型,用于取得该类的构造函数的信息
MySqlHelper dBHelper = new MySqlHelper(); Type type = dBHelper.GetType(); ConstructorInfo[] constructorInfos= type.GetConstructors(); foreach (var item in constructorInfos) { ParameterInfo[] parameterInfos = item.GetParameters(); foreach (var info in parameterInfos) { Console.WriteLine("查看:" + info.ParameterType.ToString() + " " + info.Name); } }
GetEvent(), GetEvents():返回EventInfo类型,用于取得该类的事件的信息
GetField(), GetFields():返回FieldInfo类型,用于取得该类的字段(成员变量)的信息
MySqlHelper nc = new MySqlHelper(); Type t = nc.GetType(); FieldInfo[] fis = t.GetFields(); foreach (FieldInfo fi in fis) { Console.WriteLine(fi.Name); }
GetInterface(), GetInterfaces():返回InterfaceInfo类型,用于取得该类实现的接口的信息
GetMember(), GetMembers():返回MemberInfo类型,用于取得该类的所有成员的信息
string n = "i3yuan"; Type t = n.GetType(); foreach (MemberInfo mi in t.GetMembers()) { Console.WriteLine("{0}/t{1}",mi.MemberType,mi.Name); }
GetMethod(), GetMethods():返回MethodInfo类型,用于取得该类的方法的信息
MySqlHelper dBHelper= new MySqlHelper(); Type t = dBHelper.GetType(); MethodInfo[] mis = t.GetMethods(); foreach (MethodInfo mi in mis) { Console.WriteLine(mi.ReturnType+" "+mi.Name); }
GetProperty(), GetProperties():返回PropertyInfo类型,用于取得该类的属性的信息
MySqlHelper dBHelper= new MySqlHelper(); Type t = dBHelper.GetType(); PropertyInfo[] pis = t.GetProperties(); foreach(PropertyInfo pi in pis) { Console.WriteLine(pi.Name); }
可以调用这些成员,其方式是调用Type的InvokeMember()方法,或者调用MethodInfo, PropertyInfo和其他类的Invoke()方法。
用反射生成对象,并调用属性、方法和字段进行操作
//获取类型信息 MySqlHelper dbHelper=new MySqlHelper(); Type type=dbHelper.GetType //创建对象实例化 object DbHelper = Activator.CreateInstance(type); //类型转换 IDBHelper iDBHelper = (IDBHelper)DbHelper; //方法调用 iDBHelper.Query();
二、System.Reflection.Assembly类
Assembly类可以获得程序集的信息,也可以动态的加载程序集,以及在程序集中查找类型信息,并创建该类型的实例。使用Assembly类可以降低程序集之间的耦合,有利于软件结构的合理化
1. System.Reflection
用于访问给定程序集的信息,或者把这个程序集加载到程序中。可以读取并使用metadata
方法调用过程:
1.加载DLL ; 2. 获取类型信息 ;3. 创建对象类型 4. 类型转换 5. 方法调用
Assembly assembly = Assembly.Load("Yuan.DB.MySql");//dll名称无后缀 从当前目录加载 1 加载dll //完整路径的加载 可以是别的目录 加载不会错,但是如果没有依赖项,使用的时候会错 Type type = assembly.GetType("Yuan.DB.MySql.MySqlHelper");//2 获取类型信息 object oDBHelper = Activator.CreateInstance(type);//3 创建对象 //oDBHelper.Query();//oDBHelper是objec不能调用,但实际上方法是有的 编译器不认可 IDBHelper iDBHelper = (IDBHelper)oDBHelper;//4 类型转换 iDBHelper.Query();//5 方法调用
方法二:通过程序集的名称反射
Assembly assembly = Assembly.Load("Yuan.DB.MySql");//dll名称无后缀 从当前目录加载 1 加载dll Type type = assembly.GetType("Yuan.DB.MySql.MySqlHelper");//2 获取类型信息 object oDBHelper = Activator.CreateInstance(type);//3 创建对象 MethodInfo mi=oDBHelper.GetMethod("Query"); //获取方法 mi.Invoke(oDBHelper,null);//5 调用
总结
1. 作为一个开发人员,在每天都会应用到反射,使用的时候,会反射当前程序的元数据,将所有的方法,类等信息都全部显示出来,以便开发人员使用,大大的提高了效率
2. 同时反射提高了程序的灵活性和拓展性,降低耦合,动态加载,允许控制和实现任何类的对象。
3. 当然了,也存在弊端,写起来复杂,也存在性能问题,用于字段和方法接入时要远慢于直接代码。
参考 文档 和 《C#图解教程》
注:搜索关注公众号【DotNet技术谷】--回复【C#图解】,可获取 C#图解教程文件