一、反射概念:
1、概念:
反射,通俗的讲就是我们在只知道一个对象的内部而不了解内部结构的情况下,通过反射这个技术可以使我们明确这个对象的内部实现。
在.net中,反射是重要的机制,它可以动态的分析程序集assembly,模块module,类型type等等,我们在不需要使用new关键的情况下,就可以动态
创建对象,使用对象。降低代码耦合性提高了程序的灵活性。那么,反射是怎么实现的呢?它的内部实现依赖于元数据。元数据,简单来说,在
公共语言运行时clr中,是一种二进制信息,用来描述数据,数据的属性环境等等的一项数据,那么反射解析数据的内部实现通过元数据实现再
合适不过了。
2、实例:
首先先写一个你要反射的程序集:
using system; using system.collections.generic; using system.linq; using system.text; using system.threading.tasks; namespace studentclass { public class student { public student() { } public string name { get; set; } public int age { get; set; } public char gender { get; set; } public string idcard { get; set; } public string address { get; set; } private string mobile { get; set; } public void eat() { console.writeline("我今天吃啦好多东西"); } public void sing() { console.writeline("耶耶耶耶耶"); } public int calculate(int a, int b) { return a + b; } private string privatemethod() { return "我是一个私有方法"; } } }
先来看一下程序街、模块、以及类等信息。
using system; using system.collections.generic; using system.linq; using system.reflection; using system.text; using system.threading.tasks; namespace reflectioninvoke { class program { static void main(string[] args) { //获取程序集信息 assembly assembly = assembly.loadfile(@"e:\测试\studentclass\studentclass\bin\debug\studentclass.dll"); console.writeline("程序集名字:"+assembly.fullname); console.writeline("程序集位置:"+assembly.location); console.writeline("运行程序集需要的额clr版本:"+assembly.imageruntimeversion); console.writeline("===================================================="); //获取模块信息 module[] modules = assembly.getmodules(); foreach (module item in modules) { console.writeline("模块名称:"+item.name); console.writeline("模块版本id"+item.moduleversionid); } console.writeline("======================================================"); //获取类,通过模块和程序集都可以 type[] types = assembly.gettypes(); foreach (type item in types) { console.writeline("类型的名称:"+item.name); console.writeline("类型的完全命名:"+item.fullname); console.writeline("类型的类别:"+item.attributes); console.writeline("类型的guid:"+item.guid); console.writeline("====================================================="); } //获取主要类student的成员信息等 type studenttype = assembly.gettype("studentclass.student");//完全命名 memberinfo[] mi = studenttype.getmembers(); foreach (memberinfo item in mi) { console.writeline("成员的名称:"+item.name); console.writeline("成员类别:"+item.membertype); } console.writeline("====================================="); //获取方法 bindingflags flags = bindingflags.public | bindingflags.declaredonly | bindingflags.instance; methodinfo[] methodinfo = studenttype.getmethods(flags); foreach (methodinfo item in methodinfo) { console.writeline("public类型的,不包括基类继承的实例方法:"+item.name); } console.writeline("========================================"); bindingflags flag = bindingflags.declaredonly | bindingflags.instance | bindingflags.nonpublic; methodinfo[] methods = studenttype.getmethods(flag); foreach (methodinfo item in methods) { console.writeline("非public类型的,不包括基类继承的实例方法:"+item.name); } console.writeline("========================================"); //获取属性 bindingflags flags2 = bindingflags.public | bindingflags.nonpublic | bindingflags.declaredonly | bindingflags.instance; propertyinfo[] pi = studenttype.getproperties(flags2); foreach (propertyinfo item in pi) { console.writeline("属性名称:"+item.name); } } } }
结果:
1、assembly.load()以及assembly.loadfile():
loadfile这个方法的参数是程序集的绝对路径,通过点击程序集shift+鼠标右键复制路径即可。load方法有多个重载,还可以通过流的方式获取程序集,
在项目中,主要用来取相对路径,因为很多项目的程序集会被生成在一个文件夹里,此时取相对路径不容易出错。
2、gettypes和gettype():
很明显第一个获取程序集下所有的类,返回一个数组,第二个要有参数,类名为完全类名:命名空间+类名,用于获取指定的类。
3、type类下可以获取这个类的所有成员,也可以获取字段属性方法等,有:
constructorinfo获取构造函数, fieldinfo获取字段, methodinfo获取方法,propertyinfo获取属性,eventinfo获取事件,parameterinfo获取参数,通过他们的
get***获取,加s获取所有返回数组,不加s获取具体的。
4、bindflags:用于对获取的成员的类型加以控制:
通过反编译工具,可以看到这个enum的具体:
bindingflags.public公共成员,nonpublic,非公有成员,declaredonly仅仅反射类上声明的成员不包括简单继承的成员。createinstance调用构造函数,getfield获取字段值对setfield无效。还有很多读者可以f12打开看一下用法以及注释。注意必须指定:bindingflags.instance或bindingflags.static,主要为了获取返回值,是静态的还是实例的。
二、反射的运用:
1、创建实例:
创建实例大体分为2种,activator.createinstance和assembly.createinstance。这2种方法都可以创建实例,但是又有区别,下面来通过实例具体说明。
首先分析第一种activator.createinstance
这个方法有许多的重载,最常用的2种:(type type)和(type type,params object[] obj)第一种调用无参构造,第二种调用有参构造
在前面的实例student中添加一个有参构造:
public student(string name) { this.name = name; }
然后反射创建实例
assembly assmbly = assembly.loadfile(@"e:\测试\studentclass\studentclass\bin\debug\studentclass.dll"); type studenttype = assmbly.gettype("studentclass.student"); object obj = activator.createinstance(studenttype, new object[] { "milktea" }); if (obj != null) { console.writeline(obj.gettype()); }
这里就创建了一个实例,现在让我们用反编译工具查看它的底层实现:
public static object createinstance(type type, params object[] args) { return createinstance(type, bindingflags.createinstance | bindingflags.public | bindingflags.instance, null, args, null, null); }
调用它的参数最多的一个重载后,发现他调用了下面这个方法:
这里我们就可以知道这里创建实例和new创建实例的第三步实现相同,new创建实例,先在堆中开辟新空间,然后创建对象调用它的构造函数,
所以我们可以知道activator.createinstance的底层仍然是通过被调用的类别的构造创建的,那么如果没有参数就说明调用的是无参构造。
然后来看第二种assembly.createinstance:
assembly assmbly = assembly.loadfile(@"e:\测试\studentclass\studentclass\bin\debug\studentclass.dll"); type studenttype = assmbly.gettype("studentclass.student"); object o = assmbly.createinstance(studenttype.fullname,true); console.writeline(o.gettype());
运行程序,却发现此时抛出了missingmethodexception异常:
可是明明有一个构造函数,为什么还会说没有找到构造函数呢?
通过反编译工具,来看看什么原因:
我们发现assembly这个类下的createinstance方法,居然返回的是activator下的createinstance方法,那么就只有一种可能,他调用的
是反射类下的无参构造,而无参构造被我们新加的有参构造给替代了,因此也就找不到无参构造,为了证明结论的正确,我们把无参构造
加上,然后重新实验:
public student() { }
果然和我们预想的一样,如果没有无参构造,那么使用assembly类下的方法就会抛出异常。综合2种情况,既然assembly下的createinstance
也是调用的activator的方法,并且assembly限制更大,那我们在创建实例的时候应当还是选activator下的方法更不容易出错,不是吗。
2、调用方法,属性赋值等
创建了实例以后,就到了实际用途,怎么调用它的方法,怎么给它的字段赋值,怎么添加一个委托事件等,现在来看。
a、第一种方法:使用type类的invokemember()方法,实例如下:
assembly assmbly = assembly.loadfile(@"e:\测试\studentclass\studentclass\bin\debug\studentclass.dll"); type studenttype = assmbly.gettype("studentclass.student"); object o = assmbly.createinstance(studenttype.fullname, true); type instancetype = o.gettype(); //给属性赋值并检查 instancetype.invokemember("name",bindingflags.setproperty,null,o,new object[]{"milktea"}); string propertyvalue = instancetype.invokemember("name",bindingflags.getproperty,null,o,null).tostring(); console.writeline(propertyvalue); //调用方法无返回值 instancetype.invokemember("eat",bindingflags.invokemethod,null,o,null); //调用方法有返回值 int sum = convert.toint32(instancetype.invokemember("calculate",bindingflags.invokemethod,null,o,new object[]{2,3})); console.writeline(sum);
几个重要的参数:第一个方法的名称,enum的值,字段setfield,方法invokemethod,然后选择要使用的对象,即刚才反射创建的实例,最后一个要
赋的值或者方法参数等必须为一个object数组。
这个方法详情请看msdn官方文档:
b、 第二种方法:使用filedinfo,methodinfo...等的invoke方法,实例如下:
assembly assmbly = assembly.loadfile(@"e:\测试\studentclass\studentclass\bin\debug\studentclass.dll"); type studenttype = assmbly.gettype("studentclass.student"); object o = assmbly.createinstance(studenttype.fullname, true); type instancetype = o.gettype(); //给属性赋值并检查 propertyinfo ps = instancetype.getproperty("age",typeof(int32)); ps.setvalue(o,5,null); propertyinfo pi2 = instancetype.getproperty("age"); console.writeline(pi2.getvalue(o,null)); //调用方法 methodinfo mi = instancetype.getmethod("calculate", bindingflags.instance|bindingflags.public); object obj = mi.invoke(o, new object[] { 1, 2 }); int result = convert.toint32(mi.invoke(o,new object[]{1,2})); console.writeline(result);
方法的过程即先通过方法名取的方法,注意参数中的bindingflags的2个参数都不可以丢,否则会报空引用异常,然后invoke方法中
第一个参数为反射创建的对象,第二个参数为赋的值,或参数等。
c、第三种方法:对于反射的优化,通过使用委托:这里我们将使用stopwatch对比和上次同样结果的时间:
assembly assmbly = assembly.loadfile(@"e:\测试\studentclass\studentclass\bin\debug\studentclass.dll"); type studenttype = assmbly.gettype("studentclass.student"); object o = assmbly.createinstance(studenttype.fullname, true); type instancetype = o.gettype(); //给属性赋值并检查 stopwatch sw = new stopwatch(); sw.start(); propertyinfo ps = instancetype.getproperty("age",typeof(int)); ps.setvalue(o,5,null); propertyinfo pi2 = instancetype.getproperty("age"); console.writeline(pi2.getvalue(o,null)); console.writeline("属性没启用优化:"+sw.elapsed); //调用方法 sw.reset(); sw.restart(); methodinfo mi = instancetype.getmethod("calculate", bindingflags.instance|bindingflags.public); object obj = mi.invoke(o, new object[] { 1, 2 }); int result = convert.toint32(mi.invoke(o,new object[]{1,2})); console.writeline(result); console.writeline("方法没启用优化:" + sw.elapsed); //给属性赋值并检查 sw.reset(); sw.restart(); propertyinfo pi3 = instancetype.getproperty("age", typeof(int)); var pidele = (action<int>)delegate.createdelegate(typeof(action<int>),o,pi3.getsetmethod()); pidele(5); var result1 = (func<int>)delegate.createdelegate(typeof(func<int>), o, pi3.getgetmethod()); console.writeline(result1()); console.writeline("属性启用优化:"+sw.elapsed); //调用方法 sw.reset(); sw.restart(); methodinfo mi2 = instancetype.getmethod("calculate",bindingflags.instance|bindingflags.public); var midele = (func<int, int, int>)delegate.createdelegate(typeof(func<int,int,int>),o,mi2); int a = midele(1,2); console.writeline(a); console.writeline("方法启用优化:"+sw.elapsed);
这里可以很明显的看到使用优化以后,时间缩短了斤2/3,试想一下,这里只用了很少的代码,如果代码量很多的话就可以节省更多的时间。
当然也可以看出这里的代码量比较大而复杂,可以说不够漂亮简介,用空间换取效率,delegate.createdelegate()方法具体请看: 详情链接
d、现在将最后一种,.net 4.0出现了一个新的关键字:dynamic,和var有点类似的感觉,但实则不同。var是语法糖,在代码编译期就将真正的类型
已经替换了,visual studio可以推断出var的类型,而dynamic不会在编译期检查,被编译为object类型,而会在运行期做检查,并且这个效率虽然没
有优化后的反射快,但比普通的反射也要快一些。
stopwatch watch1 = stopwatch.startnew(); type type = assembly.loadfile(@"e:\c#优化实例\studentclass\studentclass\bin\debug\studentclass.dll").gettype("studentclass.student"); object o1 = activator.createinstance(type,new object[]{12}); var method1 = type.getmethod("add",bindingflags.public|bindingflags.instance); int num1 = (int)method1.invoke(o1,new object[]{1,2}); console.writeline(num1); console.writeline("反射耗时"+watch1.elapsedmilliseconds); stopwatch watch2 = stopwatch.startnew(); type type2 = assembly.loadfile(@"e:\c#优化实例\studentclass\studentclass\bin\debug\studentclass.dll").gettype("studentclass.student"); dynamic o2 = activator.createinstance(type, new object[] { 13 }); int num2 = o2.add(2,3); console.writeline(num2); console.writeline("dynamic耗时:"+watch2.elapsedmilliseconds);
这里看到是比反射要快一些,而且代码精简了很多。综合考虑下来,代码精简度以及耗费时间,建议尽量使用dynamic关键字来处理反射。
这里反射的主要点总结完毕,还有不全的方面请评论留言相告,感激感激 2018-11-08 17:29:28