一、反射概念:

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[]{,}));
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,,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[] { , });
int result = Convert.ToInt32(mi.Invoke(o,new object[]{,}));
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,,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[] { , });
int result = Convert.ToInt32(mi.Invoke(o,new object[]{,}));
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();
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(,);
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[]{});
var method1 = type.GetMethod("Add",BindingFlags.Public|BindingFlags.Instance);
int num1 = (int)method1.Invoke(o1,new object[]{,});
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[] { });
int num2 = o2.Add(,);
Console.WriteLine(num2);
Console.WriteLine("dynamic耗时:"+watch2.ElapsedMilliseconds);

这里看到是比反射要快一些,而且代码精简了很多。综合考虑下来,代码精简度以及耗费时间,建议尽量使用dynamic关键字来处理反射。

这里反射的主要点总结完毕,还有不全的方面请评论留言相告,感激感激                2018-11-08     17:29:28

C#反射实现的更多相关文章

  1. 隐私泄露杀手锏 —— Flash 权限反射

    [简版:http://weibo.com/p/1001603881940380956046] 前言 一直以为该风险早已被重视,但最近无意中发现,仍有不少网站存在该缺陷,其中不乏一些常用的邮箱.社交网站 ...

  2. Java学习之反射机制及应用场景

    前言: 最近公司正在进行业务组件化进程,其中的路由实现用到了Java的反射机制,既然用到了就想着好好学习总结一下,其实无论是之前的EventBus 2.x版本还是Retrofit.早期的View注解框 ...

  3. 关于 CSS 反射倒影的研究思考

    原文地址:https://css-tricks.com/state-css-reflections 译者:nzbin 友情提示:由于演示 demo 的兼容性,推荐火狐浏览.该文章篇幅较长,内容庞杂,有 ...

  4. 编写高质量代码:改善Java程序的151个建议(第7章:泛型和反射___建议106~109)

    建议106:动态代理可以使代理模式更加灵活 Java的反射框架提供了动态代理(Dynamic Proxy)机制,允许在运行期对目标类生成代理,避免重复开发.我们知道一个静态代理是通过主题角色(Prox ...

  5. 运用Mono.Cecil 反射读取.NET程序集元数据

    CLR自带的反射机智和API可以很轻松的读取.NET程序集信息,但是不能对程序集进行修改.CLR提供的是只读的API,但是开源项目Mono.Cecil不仅仅可以读取.NET程序集的元数据,还可以进行修 ...

  6. .NET面试题系列[6] - 反射

    反射 - 定义,实例与优化 在面试中,通常会考察反射的定义(操作元数据),可以用反射做什么(获得程序集及其各个部件),反射有什么使用场景(ORM,序列化,反序列化,值类型比较等).如果答得好,还可能会 ...

  7. .NET基础拾遗(4)委托、事件、反射与特性

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理基础 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开 ...

  8. C++的性能C#的产能?! - .Net Native 系列五:.Net Native与反射

    此系列系小九的学堂原创翻译,翻译自微软官方开发向导,一共分为六个主题.本文是第五个主题:.Net Native与反射. 向导文链接:<C++的性能C#的产能?! - .Net Native 系列 ...

  9. [源码]Literacy 快速反射读写对象属性,字段

    Literacy 说明 Literacy使用IL指令生成方法委托,性能方面,在调用次数达到一定量的时候比反射高很多 当然,用IL指令生成一个方法也是有时间消耗的,所以在只使用一次或少数几次的情况,不但 ...

  10. SI与EMI(一) - 反射是怎样影响EMI

    Mark为期两天的EMC培训中大概分成四个时间差不多的部分,简单来说分别是SI.PI.回流.屏蔽.而在信号完整性的书籍中,也会把信号完整性分为:1.信号自身传输的问题(反射,损耗):2.信号与信号之间 ...

随机推荐

  1. Codeforces Round #424 E. Cards Sorting

    题目大意:给你一堆n张牌(数字可以相同),你只能从上面取牌,如果是当前牌堆里面最小的值则拿走, 否则放到底部,问你一共要操作多少次. 思路:讲不清楚,具体看代码.. #include<bits/ ...

  2. gitbook editor教程

    用户首先需要安装 nodejs,以便能够使用 npm 来安装 gitbook.所以我们先安装node.js,安装过程很简单,都是不断按下「Next」按钮就可以了 写node -h可以看看是否安装成功 ...

  3. JsDOM操作

    DOM(文档对象模型) 在JS中,所有的事物都是节点,元素.文本等都是节点.把浏览器中的标签看成树状结构,每个标签看成一个节点(dom元素). 应用场景:可以通过节点进行DOM对象的增删改查 获取DO ...

  4. spring继承Rabbitmq client-----------------------待研究

    一:概述 1.官网 https://spring.io/ 2.进入project 3.找到spring AMQP 二:程序 1.结构 2.pom <?xml version="1.0& ...

  5. PHP给图片加水印

    <?php /** *图片加水印 *@param $srcImg 原图 *@param $waterImg 水印图片 *@param $savepath 保存路径 *@param $savena ...

  6. eclipse中配置server

    打开Eclipse,在打开上面的help--- install new software---- work with 里面点开选择--All Available Sites-- 等下面的pending ...

  7. Saltstack cp.get 模块

    语法 salt '*' cp.get_file salt://rr /etc/rr cp.get_url  可以从一个URL地址下载文件,URL可以是msater上的路径(salt://),也可以是h ...

  8. JUnit accuracy/failure/stress test区别

    accuracy test(结果准确性测试) 例如,Assert.assertEquals(expected, actual). 如果结果不符合期望则产生failure.说明程序逻辑有问题. fail ...

  9. AngularJS中控制器继承

    本篇关注AngularJS中的控制器继承,了解属性和方法是如何被继承的. 嵌套控制器中属性是如何被继承的? ==属性值是字符串 myApp.controller("ParentCtrl&qu ...

  10. CNC系统的多任务并行处理

    (1) CNC系统的多任务性.CNC系统通常作为一个独立的过程控制单元用于工业自动化生产中,因此它的系统软件必须完成管理和控制两大任务.系统的管理部分包括输入.I/O处理.显示和诊断.系统的控制部分包 ...