zoukankan      html  css  js  c++  java
  • 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("
    -------------------- 获取对象的所以公有字段-------------------------------
    ");
                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 设计模式

     
  • 相关阅读:
    scp命令报错-bash: scp: command not found
    shell比较两个字符串是否相等
    bat脚本:自动压缩n天前的文件【转载】
    shell bash判断文件或文件夹是否存在
    linux文件分割(将大的日志文件分割成小的)【转载】
    TCP/IP模型各个层次的功能和协议
    nginx初级安装配置
    Heartbeat+DRBD+MySQL高可用方案【转】
    【转载】CentOS 6.4下PXE+Kickstart无人值守安装操作系统
    oracle的exp和imp命令的使用【转载】
  • 原文地址:https://www.cnblogs.com/wangbaicheng1477865665/p/8297116.html
Copyright © 2011-2022 走看看