反射
一、为什么学习反射
因为反射真的是无处不在,ORM、MVC、IOC、AOP、Attribute等等都会使用到反射。反射是程序员的快乐
二、什么是反射
Ilspy:逆向工程,可以吧DLL/Exe文件反编译回来
DLL/EXE
文件下包含Metadata和IL,IL是对标于C#代码的代码,属于中间语言,是标准的面向对象语言
而Metadata(元数据)是一个清单数据,只是描述了类中有什么,而不是展示所有的实现。一般我们用F12查看元数据,可以发现只有方法体没有声明:
反射是一种工具,命名空间为System.Reflection,可以读取metadata,并使用metadata。(是微软提供的一个帮助类库)
三、Type类
3.1 简介
BCL声明了一个叫做Type的抽象类,它被设计用来包含类型的特性,使用这个类的对象能让我们获取程序使用的类型的信息。
由于Type是抽象类,因此它不能有实例,而是在运行时CLR创建从Type(RuntimeType)派生的类的实例。Type包含了类型信息,当我们要访问这些实例时,CLR不会返回派生类的引用而是返回Type基类的引用。为了简单起见,下面篇幅中我会把引用所指向的对象称为Type类型的对象。
Type类的重要事项:
● 对于程序中用到的每一个类型,CLR都会创建一个包含这个类型信息的Type类型的对象
● 程序中用到的每一个类型都会关联到独立的Type类型的对象
● 不管创建的类型有多少个实例,只有一个Type对象会关联到所有这些实例
如果你对C#中有哪些类型不是很了解,那么知识扩展: C#中的类型有:预定义的类型(int,long和string等),BCL中的类型(Console,IEnumerable等)以及用户自定义的类型(MyClass,MyDel等)
3.2 Type类的部分常见成员
成员 | 成员类型 | 描述 |
---|---|---|
Name | 属性 | 返回类型的名字 |
FullName | 属性 | 返回数据类型的完全限定名(包括命名空间名) |
NameSpace | 属性 | 返回包含数据类型声明的命名空间 |
Assembly | 属性 | 返回声明类型的程序集。如果类型是泛型的,返回定义这个类型的程序集 |
GetConstructor(), GetConstructors() | 方法 | 返回ConstructorInfo类型,用于取得该类的构造函数的信息 |
GetEvent(), GetEvents() | 方法 | 返回EventInfo类型,用于取得该类的事件的信息 |
GetField(), GetFields() | 方法 | 返回FieldInfo类型,用于取得该类的字段(成员变量)的信息 |
GetInterface(), GetInterfaces() | 方法 | 返回InterfaceInfo类型,用于取得该类实现的接口的信息 |
GetMember(), GetMembers() | 方法 | 返回MemberInfo类型,用于取得该类的所有成员的信息 |
GetMethod(), GetMethods() | 方法 | 返回MethodInfo类型,用于取得该类的方法的信息 |
GetProperty(), GetProperties() | 方法 | 返回PropertyInfo类型,用于取得该类的属性的信息 |
3.3 获取Type对象
都是获取类的引用的数据类型 System.Type
-
GetType()方法继承自Object,所以C#中任何对象都具有GetType()方法,x.GetType(),其中x为变量名
-
typeof(x)中的x,必须是具体的类名、类型名称等,不可以是变量名称
-
System.Type.GetType(),有两个重载方法:
- 比如有这样一个变量i,Int16 i = new Int16();使用GetType(),i.GetType()返回值是Int16的类型;但是无法使用typeof(i),因为i是一个变量
- 使用typeof(),则只能:typeof(Int32),返回的同样是Int16的类型
- 枚举类的转换,String——枚举类,以串口中Parity为例。objParity= (Parity)Enum.Parse(typeof(Parity), “9600”);
获取类的Type属性,除了上面的使用实例获取,或者typeof,还有一种提供类的完整信息字符串,根据类名来获取Type:
//取得当前方法命名空间
str += "命名空间名:" + System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.Namespace + "\n";
//取得当前方法类全名 包括命名空间
str += "类名:" + System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName + "\n";
//取得当前方法名
str += "方法名:" + System.Reflection.MethodBase.GetCurrentMethod().Name + "\n"; str += "\n";
//取得当前方法类全名 包括命名空间
string classname = System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName; //得到的是 命名空间.类
Type type = Type.GetType(classname); //通过类名获取同名类
object obj = System.Activator.CreateInstance(type); //创建实例
四、反射基本操作
4.1 反射–动态加载dll
-
通过dll文件名称进行加载
Assembly assembly = Assembly.Load("DB.SqlServer");// dll名称 默认到当前目录下查找
-
全名称= 全路径+dll名称 + 后缀
Assembly assembly = Assembly.LoadFile(@"D:\软谋教育\Ruanmou\Advanced13Encrypt\20191010Advanced13Course3Reflection\20191010Advanced13Course3Reflection\MyReflection\MyReflection\bin\Debug\DB.SqlServer.dll");
-
全名称
Assembly assembly = Assembly.LoadFrom("DB.SqlServer.dll");
-
全名称
Assembly assembly = Assembly.LoadFrom(@"D:\软谋教育\Ruanmou\Advanced13Encrypt\20191010Advanced13Course3Reflection\20191010Advanced13Course3Reflection\MyReflection\MyReflection\bin\Debug\DB.SqlServer.dll");
4.2 常规玩法
//1.动态加载dll
Assembly assembly = Assembly.Load("DB.SqlServer");// dll名称 默认到当前目录下查找
//获取所有类型
//assembly.GetTypes()
//2.通过类型名称获取指定类型
Type type = assembly.GetType("DB.SqlServer.SqlServerHelper");
//3.创建对象
object oDbHelper = Activator.CreateInstance(type);
//dynamic dDbHelper = Activator.CreateInstance(type);
//dDbHelper.Query();
//4.类型转换
IDBHelper iDBHelper = oDbHelper as IDBHelper;
//5.调用方法
iDBHelper.Query();
第二步获取类型时,形式 = 命名空间名称 + 类名
第三步创建对象,其实和IDBHelper dBHelper = new SqlServerHelper();
实例化一个对象等价,会执行到SqlServerHelper的构造函数里
经过第三步创建对象之后并不能直接调用Query方法,因为编译器就不认可(c# 是强类型语言);dynamic 是一个动态类型,可以避开编译器的检查,运行时检查,但存在安全问题 。
4.3 进阶玩法
封装 Factory + config 代码可以不用编译发布
简单工厂:
public class SimlpFactory
{
private static string IDBHelperConfig = ConfigurationManager.AppSettings["IDBHelperConfig"];
private static string DllName = IDBHelperConfig.Split(',')[1];
private static string TypeName = IDBHelperConfig.Split(',')[0];
public static IDBHelper CreateInstentce()
{
Assembly assembly = Assembly.Load(DllName);
Type type = assembly.GetType(TypeName);
object oDbHelper = Activator.CreateInstance(type);
return oDbHelper as IDBHelper;
}
}
config文件:
<appSettings>
<add key="IDBHelperConfig" value="DB.Orcale.OrcaleHelper,DB.Orcale"/>
</appSettings>
调用:
IDBHelper iDBHelper = SimlpFactory.CreateInstentce();
iDBHelper.Query();
4.4 反射–选择不同的构造函数创建对象
//1.动态加载
Assembly assembly = Assembly.Load("DB.SqlServer");
//2.获取类型
Type type = assembly.GetType("DB.SqlServer.ReflectionTest");
//3.创建对象
object oDbHelper = Activator.CreateInstance(type); //public ReflectionTest()
object oDbHelper1 = Activator.CreateInstance(type, new object[] { "杰克" }); // public ReflectionTest(string name)
object oDbHelper3 = Activator.CreateInstance(type, new object[] { 123 }); //public ReflectionTest(int id)
五、反射调用方法
5.1 反射–非类型转换调用方法
Assembly assembly = Assembly.Load("DB.SqlServer");
Type type = assembly.GetType("DB.SqlServer.ReflectionTest");
object oTest = Activator.CreateInstance(type);
无参数方法
MethodInfo show1 = type.GetMethod("show1");
show1.Invoke(oTest, null); //反射调用方法
有参数方法
MethodInfo show2 = type.GetMethod("show2");
show2.Invoke(oTest, new object[] { 123 }); //反射调用方法
重载方法,方法名称一样,参数不同
MethodInfo show1 = type.GetMethod("show1", new Type[] { typeof(string), typeof(int) });
show1.Invoke(oTest, new object[] { 123, "陈贺章" });
私有方法:
MethodInfo show4 = type.GetMethod("show4", BindingFlags.NonPublic | BindingFlags.Instance);
show4.Invoke(oTest, new object[] { "私有方法" });
静态方法: 对象的实例可以传入,也可以不传入。如果像常规使用是不需要实例化对象就可以直接调用方法(反射传null),但是反射却可以传入对象的实例
MethodInfo show5 = type.GetMethod("show5");
show5.Invoke(oTest, new object[] { "我是静态方法" });
show5.Invoke(null, new object[] { "我是静态方法" });
5.2 反射–调用泛型方法
六、反射黑科技
假设我们存在一个单例模式,代码如下:
/// <summary>
/// 单例模式:类,能保证在整个进程中只有一个实例
/// </summary>
public sealed class Singleton
{
private static Singleton _Singleton = null;
private Singleton()
{
Console.WriteLine("Singleton被构造");
}
static Singleton()
{
_Singleton = new Singleton();
}
public static Singleton GetInstance()
{
return _Singleton;
}
}
常规用法:
Singleton singleton1 = Singleton.GetInstance();
Singleton singleton2 = Singleton.GetInstance();
Console.WriteLine(singleton1.Equals(singleton2)); //结果为:true
反射应用: 两次对象不一致,Activator.CreateInstance完全等价于new Singleton(),每次都调用了私有构造函数。反射破坏了单例,其实就是反射调用了私有构造函数
Assembly assembly = Assembly.Load("DB.SqlServer");
Type type = assembly.GetType("DB.SqlServer.Singleton");
object oSingleton1 = Activator.CreateInstance(type, true); //第二个参数为 NoPublic--是否为公有的,完全等价于new Singleton();
object oSingleton2 = Activator.CreateInstance(type, true);
Console.WriteLine(oSingleton1.Equals(oSingleton2)); //结果为:false