-
概述 什么是反射
Reflection,中文翻译为反射。
这是.Net中获取运行时类型信息的方式,.Net的应用程序由几个部分:‘程序集(Assembly)’、‘模块(Module)’、‘类型(class)’组成,而反射提供一种编程的方式,让程序员可以在程序运行期获得这几个组成部分的相关信息,例如:
Assembly类可以获得正在运行的装配件信息,也可以动态的加载装配件,以及在装配件中查找类型信息,并创建该类型的实例。
Type类可以获得对象的类型信息,此信息包含对象的所有要素:方法、构造器、属性等等,通过Type类可以得到这些要素的信息,并且调用之。
MethodInfo包含方法的信息,通过这个类可以得到方法的名称、参数、返回值等,并且可以调用之。
诸如此类,还有FieldInfo、EventInfo等等,这些类都包含在System.Reflection命名空间下。
类型 | 作用 |
Assembly | 通过此类可以加载操纵一个程序集,并获取程序集内部信息 |
EventInfo | 该类保存给定的事件信息 |
FieldInfo | 该类保存给定的字段信息 |
MethodInfo | 该类保存给定的方法信息 |
MemberInfo | 该类是一个基类,它定义了EventInfo、FieldInfo、MethodInfo、PropertyInfo的多个公用行为 |
Module | 该类可以使你能访问多个程序集中的给定模块 |
ParameterInfo | 该类保存给定的参数信息 |
PropertyInfo | 该类保存给定的属性信息 |
这些都是废话,我们一起看几个案列就完全学会了,在此说明下,反射用到的一些基础技术有 运行运算符,type 类,这里就不过多的解释了,如有不会可以去园子里面自己去找,本人也写过一篇相关文章,简单的介绍了运行运算符。
-
如何得到一个类的对象
现有工程文件(项目文件)结构如下
People类代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Entity { public class People { public People() { Console.WriteLine("People被创建了"); } public People(String Name) { this.Name = Name; Console.WriteLine("People被创建了,并且people的名字是"+this.Name); } public string Name { get; set; }//自动属性,在程序实例化的过程中会自动创建私有的字段,这个字段在people 内存中开辟控件存储其值(本文称为公有属性)在此感谢ENC博主的支持和评论, public int Age { get; set; } public string Sex { get; set; } public string msg;//公有字段 private string qq;//私有字段 private string address;//私有属性 public string Address { get => Address; set => Address = value; } public override string ToString() { return "{" + $"name:{this.Name},age:{this.Age},sex{this.Sex}" + "}"; } public string Say() { return "hello! " + this.Name; } } }
debug 目录如下:
这里说明下,程序中,并没有引用 Entity 类库,也没有引用Entity..DLL文件,请自行引用,我们如果不实例化得到一个对象呢??正常的时候,我们都是通过new 得到一个对象,如:
using Entity; using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { People p = new People(); Console.WriteLine(p); People peop = new People("张三"); Console.WriteLine(p); Console.Read(); } } }
我们再来看下类的类型是什么?
using Entity; using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { Type t = typeof(People); Console.WriteLine(t); Type type= Type.GetType("People"); Console.WriteLine(type);//这里是得不到的,因为配件装载只能在程序集内部使用 Console.Read(); } } }
我们来学习下,如何根据类类型进行反射。
-
类的反射
对象无参构造函数反射
static void Main(string[] args) { Type type = typeof(People); People people= Activator.CreateInstance(type) as People;//实例化得带一个类 Console.WriteLine(people); Console.Read(); }
对象有构造函数参反射
static void Main(string[] args) { Type type = typeof(People); People people= Activator.CreateInstance(type) as People;//实例化得到一个类 Console.WriteLine(people); //实例化得到一个类,该类有一个参数 People p = Activator.CreateInstance(type, new object[] { "Wbcsky" }) as People; Console.WriteLine(p); Console.Read(); }
对象泛型反射
static void Main(string[] args) { Type type = typeof(People); People p1 = Activator.CreateInstance<People>(); Console.WriteLine(p1); Console.Read(); }
关于对象的反射,就只有这三种形式,分别是泛型反射,泛型反射有且只能得到无参数的实例对象,和普通无参反射像比较,反射反射减少了装箱拆箱的操作。有参数反射我们是按照参数的顺序,传递的object 数组。这些反射都是基于 Activator.CreateInstance 来完成的。
static void Main(string[] args) { Type type = typeof(People); System.Reflection.PropertyInfo[] p = type.GetProperties(); foreach (var item in p) { Console.WriteLine("属性名:" + item.Name + "属性类型" + item.PropertyType.FullName + "属性类型命名空间" + item.PropertyType.Namespace); } Console.Read(); }
我们都知道,在C#中,属性的封装有两种,一种全写,一种简写,全写的在某些工具书中叫做私有属性,简写的在工具书上叫做公有属性。
如:
public int Age { get; set; }
我们称为简写,工具书上叫做公有属性。
则:
private string address;//私有属性
public string Address { get => Address; set => Address = value; }
或
private string iD;
public string ID
{
get { return this.iD; }
set { this.iD = value; }
}
这种写法我们称为私有属性,私有属性中,当使用=>这种运算的,我们称为lambda表达式写法,使用this 关键字的写法,我们称为面向对象写法。不论哪一种属性,我们都叫做属性,我们在反射中获取属性使用的是Type 类的 .GetProperties()方法来获取类的全部属性。我们来看下执行结果。
这里就不过多的介绍获取属性的值了,我们在下面介绍获取属性的值。
-
获取指定名称的属性和值及设置一个值
static void Main(string[] args) { Type type = typeof(People); System.Reflection.PropertyInfo Property = type.GetProperty("Name");//注意属性名称字符串大小写 if (Property == null) Console.Read();//如果属性名称大小写错误或者不存在,我们Property对象将会是null Console.WriteLine("属性名:" + Property.Name + "属性类型" + Property.PropertyType.FullName + "属性类型命名空间" + Property.PropertyType.Namespace); //获取属性的值 People p= Activator.CreateInstance(type) as People;//获取对象 object oName = Property.GetValue(p); //获取值 Console.WriteLine("旧" + oName); Property.SetValue(p, "abc");//设置一个值 oName = Property.GetValue(p); //获取值 Console.WriteLine("新" + oName); Console.Read(); }
看了上面的代码,我们会发现,获取属性使用的是Type类的 GetProperty方法来完成的。获取值和设置值,使用的是 PropertyInfo 类的 GetValue和Set value 来完成的。执行结果如下
因为初始化的时候是空,所以旧就什么也没有输出。有人会说了,这个没有获取到类,进行点写的方便,为什么要这么写呢,告诉你一句话,存在就是有道理的,这里可以简单的告诉,我们很多时候,一个功能更新过于频繁,我们完全可以把这个类写入配置文件中,去配置这个类对象的功能使用。理解即可,不理解清背下来代码。
-
获取对象的所以公有字段和私有字段
在这里说明下,很多人都不明白字段和属性的区别,我这里简单说下,理解即可,不理解不影响学习,我们一个类的变量进行封装,会出现get ,set 设置这个字段的访问权限,这个封装我们称为属性,而这个变量我们叫做字段,字段不指定修饰符的时候默认为私有的。
static void Main(string[] args) { Type type = typeof(People); System.Reflection.FieldInfo[] fi = type.GetFields(); Console.WriteLine(" -------------------- 获取对象的所以公有字段------------------------------- "); foreach (System.Reflection.FieldInfo item in fi) { Console.WriteLine("公有字段名" + item.Name); } Console.WriteLine(" -------------------- 获取对象的所有私有字段------------------------------- "); System.Reflection.FieldInfo[] fiprivate = type.GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); foreach (System.Reflection.FieldInfo item in fiprivate) { Console.WriteLine("私有字段名" + item.Name); } Console.Read(); }
这是一个难点,但是在实际开发过程中很少使用,但是这我们必须要会,否则后期写组件开发等文档,该看不懂了,准备好瓜子,咱们开始听故事了。
看了上面的代码,及字段及属性的介绍,我们会发现,输出的结果,共有的很好理解,我们类里面定义的变量 指定public 以后,我们就可以通过
GetFields ()
方法返回我们想要的公有字段数组,我们输出了名字,这里就不过多的解释了。
反射私有字段,输出的这个是什么啊,乱码七招的。
私有字段名<Name>k__BackingField
私有字段名<Age>k__BackingField
私有字段名<Sex>k__BackingField
私有字段名qq
私有字段名address
其实很好理解,我们在前面说过获取所有属性的时候说过属性分为私有和公有,其中私有属性有两种写法,其实私有属性是对私有变量的封装,也可以说是对私有字段的封装,公有属性是什么呢?
其实公有属性在编译过程中, 为了方便JTL 公共语言运行环境更好的编译,自动生成了一个私有的字段,这个字段是根据操作系统不同生成不同前缀的私有字段,这里生成的是K_前缀的。这样我们就好理解为什么上图会多输出三个字段。
如果此处还不理解,那么请看其他博客吧本文介绍的毕竟都是基础。而实际开发过程中反射这基本使用的都是组件。
- 获取指定的公有字段
在这里就不介绍获取指定公有字段的值了,和属性获取是一样的。
static void Main(string[] args) { Type type = typeof(People); Console.WriteLine(" -------------------- 获取对象的指定公有字段------------------------------- "); Console.WriteLine("字段名" + type.GetField("msg").Name); Console.Read(); }
代码很简单,只有一行。那么有人会问,那字段分为私有和共有的,为啥没有介绍获取私有属性的呢???为啥没有介绍获取指定私有字段的呢???,其实答案很简单,你看过有封装属性的时候有私有的吗,私有的是不是都说在类的内部使用,那我反射类就可以了,我外部也不使用。那私有字段呢,为啥没有,不是没有,是有但是基本不使用,因为共有属性会默认生成私有字段,这个私有字段的前缀不同,所以无法获取,没意义。所以基本没人使用。
-
获取公有方法并调用
Type type = typeof(People); Console.WriteLine(" -------------------- 获取对象的共有方法并且调用------------------------------- "); System.Reflection.MethodInfo mi = type.GetMethod("Say"); People p= Activator.CreateInstance<People>(); p.Name = "张四伙";//为了省事,这里不使用属性反射添加值了 object oReturn = mi.Invoke(p, null);//第一个参数为反射的对象,第二个参数object 数组,为参数,参数按顺序填写 Console.WriteLine(oReturn); Console.Read();
这个没有什么解释的了,前面最难的属性字段反射,我们都会了,这个就不是问题了,自己多看看代码?
-
获取当前类下的所有够着函数
static void Main(string[] args) { Type type = typeof(People); ///获取所有的一般不会使用,这里就不过多介绍了 System.Reflection.ConstructorInfo[] info = type.GetConstructors();//获取当前类下所有够着函数 foreach (System.Reflection.ConstructorInfo item in info) { Console.WriteLine("是否为虚方法"+item.IsVirtual); Console.WriteLine("名称"+item.Name); } Console.WriteLine(" -------------------- 获取当前类下参数类型匹配的够着函数------------------------------- "); System.Reflection.ConstructorInfo con = type.GetConstructor(new Type[] { typeof(string) }); object o = con.Invoke(new object[] { "zhangsan" }); People peo = o as People; Console.WriteLine(peo); Console.Read(); }
大家会说了,够着函数不就是类对象的实例化吗?,我们前面不是讲过反射类对象了吗,为什么这个里面还要获取实例化对象呢?
其实有些时候,我们在使用抽象类和接口的时候,我们通过之前学习的类的反射是一样可以做到得到类的对象,这里之说以这么讲解,因为有一些反射项目在优化的时候,会使用内部查找原则,即从够着函数开始得带类的对象,效率会更高一些。
我们在开发过程中,尽量有内而外,尽量把计算或者声明拿到程序代码执行过程中的最后去做,这样使用内存会少,效率会更高。
下边我们学习这篇文章的第二大核心。程序集反射
什么是程序集反射呢,加入我们三层架构,我不想引用bll层和model 层,也不想引用他们的dll,就能在业务层得带他的对象引用,这个怎么做到呢???我们一起来学习下吧!
首先程序集中删除Entity.dll 程序编译跟目录放置 ectity.dll文件。看下列代码
using System; using System.Collections.Generic; using System.Data; namespace testData { class Program { static void Main(string[] args) { /*装载程序集*/ System.Reflection.Assembly assembly = System.Reflection.Assembly.Load("Entity"); // System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom("Entity.bll");//使用这种方式需要写扩展名 Console.WriteLine(" -------------------- 程序集反射1------------------------------- "); Type peopleType = assembly.GetType("Entity.People");//得到people 类的type 类型 object obj = Activator.CreateInstance(peopleType); System.Reflection.MethodInfo me = peopleType.GetMethod("Say"); object ret = me.Invoke(obj, null); Console.WriteLine(ret); Console.WriteLine(" -------------------- 程序集反射2------------------------------- "); object PeopleObj = assembly.CreateInstance("Entity.People");//直接得到类的实例化对象 Console.WriteLine(PeopleObj); Console.Read(); } } }
代码注释已经很明确了,这里就不过多的解释了,我们来看下执行结果 。
-------------------- 程序集反射1-------------------------------
People被创建了
hello!
-------------------- 程序集反射2-------------------------------
People被创建了
{name:,age:0,sex}
在程序集反射中,我们就没有办法在.属性 .字段 .方法的调用了,这个时候,我们只能通过属性,方法的反射去调用了,这里演示的不多,就两种常用的案列,剩下的程序集有参数,无参数够造函数就不多说了,和前面的是一样的,本文只是介绍了开发过程中常用的案列。
现有泛型类如下
public class GenericClass<T, W, X> { public void Show(T t, W w, X x) { Console.WriteLine("t.type={0},w.type={1},x.type={2}", t.GetType().Name, w.GetType().Name, x.GetType().Name); } }
反射代码如下:
Assembly assembly = Assembly.Load("Entity"); Type genericType = assembly.GetType("Entity.GenericClass`3"); Type typeNew = genericType.MakeGenericType(typeof(int), typeof(int), typeof(int)); Dynamic dGeneric = Activator.CreateInstance(typeNew);
泛型反射,我们有几个泛型参数,我们就在后边补位“`3”,注意符号 ` 可千万别少了,我们泛型反射是使用Type 类的 MakeGenericType()方法进行获取泛型的Type 类型的,通过这个类型进行反射
1.反射一般是用在序列化无法完成的情况下,比如接口返回想xml,而这个xml 经常变动,并没有一个指定的规律,这个时候我们就不能用linq to xml 等反序列化对象了。这个时候就应当使用反射了。
2.真正开发过程中,反射不是是向上面这么写的,真正的反射是使用组件来完成的,一般也不会使用程序集反射,除非这个框架的某个功能模块更新频繁,我们可以使用不同的反射区完成,只需要在xml 文件中配置下就可以了。
3.在这里简单介绍下组件反射,不是说开发过程中不会有程序集等反射,而是大多数的情况下组件反射就已经能满足我们的需求了,如AutoFac组件,等其他的。
4.反射技术点一般对应的技术点有 IOC 翻转,依赖倒置,依赖注入等
下边分享一篇文章,之所以写本文,就是因为下边这篇文文章介绍的太主流,很多人不会使用,Autofac是net core 2.0里面的组件,请看下边的文章
文章链接1 Autofac 解释第一个例子 《第一篇》
文章链接2 Autofac 组件、服务、自动装配 《第二篇》
文章链接3 通过配置的方式Autofac 《第三篇》
以上三篇合起来,我们称为IOC 设计模式