net 反射30分钟速成

 

  • 概述 什么是反射

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("\r\n-------------------- 获取对象的所以公有字段-------------------------------\r\n");
            foreach (System.Reflection.FieldInfo item in fi)
            {
                Console.WriteLine("公有字段名" + item.Name);
            }
            Console.WriteLine("\r\n-------------------- 获取对象的所有私有字段-------------------------------\r\n");
            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("\r\n-------------------- 获取对象的指定公有字段-------------------------------\r\n");
            Console.WriteLine("字段名" + type.GetField("msg").Name);
            Console.Read();
        }

 

代码很简单,只有一行。那么有人会问,那字段分为私有和共有的,为啥没有介绍获取私有属性的呢???为啥没有介绍获取指定私有字段的呢???,其实答案很简单,你看过有封装属性的时候有私有的吗,私有的是不是都说在类的内部使用,那我反射类就可以了,我外部也不使用。那私有字段呢,为啥没有,不是没有,是有但是基本不使用,因为共有属性会默认生成私有字段,这个私有字段的前缀不同,所以无法获取,没意义。所以基本没人使用。

 

方法和构造函数的反射
  •  获取公有方法并调用

  Type type = typeof(People);
            Console.WriteLine("\r\n-------------------- 获取对象的共有方法并且调用-------------------------------\r\n");
            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("\r\n-------------------- 获取当前类下参数类型匹配的够着函数-------------------------------\r\n");
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("\r\n-------------------- 程序集反射1-------------------------------\r\n");
            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("\r\n-------------------- 程序集反射2-------------------------------\r\n");
            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 设计模式

 

 

posted @ 2018-01-22 16:53  王柏成  阅读(846)  评论(10编辑  收藏  举报