• C#基础系列反射


    一、定义

      Reflection翻译成反射,在实际生活中比如地质勘探中如何了解地球内部构造情况(地壳、地幔和地核),因为没办法通过设备钻入地球深入勘查,就想出对地球发送“地震波”的方式,“横向波”与“纵向波”穿透液体和固体返回情况构建地球内部的结构。反射类比于此,这是一种对象的外部获取对象内部的构造,并且使用获取的信息来管理对象内部。.

      反射是提供描述程序集(Assembly)、模块和类型的对象(Type类型)。 可以使用反射动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用其方法或访问器字段和属性, 如果代码中使用了特性,还可以利用反射来访问它们。.NET反射可以在运行时(动态)获取程序或程序集、程序集的类、接口、委托、类中的成员。主要使用方式通过程序集(dll)名称动态加载程序集;通过程序集中的类反射动态创建类实例对象,操作实例对象;通过已经创建的对象反射获取对象中的方法、属性、参数进行执行方法,设置属性值等;获取类上描述的特性(Attribute)。

    二、原理

      在面试中突然被问道反射的原理,按照理解反射就是在Reflection命名空间和对象的Type对象实例中获取类的方法、属性、特性等成员信息,但是又被问道为什么可以获取这些成员信息?就是反射机通过如下命名空间的方法为什么可以获取?让其可以使用反射来获取程序集、程序集的类、创建对象、执行方法、获取属性和特性信息。

    命名空间 描述
    System.Reflection.Assembly  程序集
    System.Reflection.MemberInfo 成员信息
    System.Reflection.EventInfo 事件
    System.Reflection.FieldInfo 字段
    System.Reflection.MethodBase 基类方法
    System.Reflection.ConstructorInfo 构造函数
    System.Reflection.MethodInfo 方法
    System.Reflection.PropertyInfo 属性
    System.Type 类、对象的类型对象

      反射是如何通过上述的Reflection命名空间的类与方法获取类和方法名的?主要依据了元数据metadata,在程序高级语言中(C#)元数据的表现形式是一种二进制信息,用以对存储在公共语言运行库(CLR)可移动执行文件(PE)或者存储在内存中程序进行描述,编译器將代码编译成PE文件时便会將元数据插入到该文件的一部分,而将代码转换为 Microsoft 中间语言 (MSIL) 并将其插入到该文件的另一部分中,所以包含元数据和使用中间语言將代码生成的部分。元数据將存储如下信息,程序集(名称、版本、区域性、公钥)、类的说明(名称、可见性、基类和实现的接口)、类的成员(方法、字段、属性、事件、嵌套的类型)等。当执行代码的时候,运行库將元数据加载到内存中,并通过引用它(元数据)来发现有关代码的类、成员、继承等信息。

      反射是审查元数据并收集关于它的类型信息的能力,元数据(编译后的最基本数据单元)一些表,当编译程序集货模块时,编译器会创建如下信息。1、关于程序集的元数据(清单)主要包含如下信息:标识信息(包括程序集的名称、版本、文化和公钥等);文件列表(程序集由哪些文件组成);引用程序集列表(该程序集所引用的其他程序集);一组许可请求(运行这个程序集需要的许可)。2、关于类型的元数据包含一个类定义表、一个字段定义表、一个方法定义表、方法参数表等,System.reflection命名空间包含的几个类,允许你反射(解析)这些元数据表的代码 。

    三、使用  

    属性 用途
    Assembly  程序集,定义和加载程序集、程序集的模块清单、查找程序集的类和创建该类的实例对象
    Module  模块,获取模块上的程序集和类、获取模块的全局方法或者其他特定非全局方法
    ConstructorInfo  构造函数,获取其名称、参数、访问修饰符(public,private,protected)、实现信息(如abstract或virtual)
    MethodInfo  方法,获取其名称、返回类型、参数、访问修饰符(public,private,protected)、实现信息(如abstract或virtual)
    FiedInfo  字段,获取其名称、访问修饰符(public,private,protected)和实现信息(static);设置或获取字段的值。
    EventInfo  事件,获取其名称、事件处理程序数据类型、自定义属性、声明类型和反射类型;加入和移除事件。
    PropertyInfo  属性,获取其名称、数据类型、声明类型、反射类型和只读或可写状态等;设置或获取属性的值
    ParameterInfo  参数、获取其名称、数据类型、是输入参数还是输出参数,以及参数在方法签名中的位置等

      使用反射获取上述内容来操作程序集、类、对象必须获取对象的类型对象(Type类型),获取的类型对象探测内部结构信息,通过查看Type类的定义(源代码)可以看到其继承接口IReflect,MemberInfo,_Type主要包含判断对象的类型,类中的成员,属性、方法的获取等,如下代码所示。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Reflection;
    
    namespace 反射
    {
        /// <summary>
        /// 反射Demo
        /// </summary>
        class Program
        {
            static void Main(string[] args)
            {
                Assembly assembly = typeof(int).Assembly;
    
                // 获取类型对象的方式
                var str = "Reflection";
    
                var t1 = typeof(string);// 获取类的类型对象
    
                var t2 = str.GetType();// 获取对象的类型对象
    
                var t3 = Type.GetType("System.String"); // Type类的静态方法获取
    
                // 通过以上获取类型对象来探测成员信息(所有的)
                foreach (var item in t1.GetMembers())
                {
                    // 输出成员类型(方法、属性、字段、构造函数)和名称
                    Console.WriteLine("{0}/t/t{1}", item.MemberType, item.Name);
                }
    
                var animalType = typeof(Animal);
                foreach (var item in animalType.GetMembers())
                {
                    // 输出自定义类的成员
                    Console.WriteLine("{0}/t/t{1}", item.MemberType, item.Name);
                }
    
                // 1、创建一个对象,获取对象类的方法,通过Invoke的方式执行方法(注意匹配参数)
                var catAnimal = new Animal("cat");
                var eatMethod = animalType.GetMethod("Eat");
                eatMethod.Invoke(catAnimal,new object[] { "food"});
    
                // 2、通过构造函数创建对象(Activator远程创建对象),并且获取类型方法,使用对象执行方法
                var pars = new object[] {"dog" };
                object obj = Activator.CreateInstance(animalType, pars);
                var flyMethod = animalType.GetMethod("Fly");
                flyMethod.Invoke(obj, null);
    
                // 3、通过Animal类的Type对象获取字段type,然后使用构造函数创建的对象赋值这个字段值,并且获取值
                FieldInfo typeFieldInfo = animalType.GetField("type");// 如果字段设置是private修饰符,无法获取对象
                typeFieldInfo.SetValue(obj, "猫科类");
                Console.WriteLine("obj对象的字段type值{0}", typeFieldInfo.GetValue(obj));
    
                // 4、通过Animal类的Type对象获取属性Name
                PropertyInfo namePropertyInfo = animalType.GetProperty("Name");
                namePropertyInfo.SetValue(obj, "bird");
                Console.WriteLine("obj对象的属性Name值{0}", namePropertyInfo.GetValue(obj));
                
                Console.ReadKey();
            }
        }
    
        /// <summary>
        /// 接口
        /// </summary>
        public interface IAnimal
        {
            void Eat(string food);
            void Fly();
        }
    
        /// <summary>
        ////// </summary>
        [Serializable]
        public class Animal : IAnimal
        {
            public  string type;
            /// <summary>
            /// 属性
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 默认构造函数
            /// </summary>
            public Animal()
            {
            }
    
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="name"></param>
            public Animal(string name)
            {
                Name = name;
            }
    
            /// <summary>
            /// 动物吃东西
            /// </summary>
            /// <param name="food"></param>
            public void Eat(string food)
            {
                Console.WriteLine("动物{0}在吃{1}", Name, food);
            }
    
            /// <summary>
            /// 动物飞起来
            /// </summary>
            public void Fly()
            {
                Console.WriteLine("动物{0}在飞", Name);
            }
        }
    }

      通过System.Reflection.Assembly来动态加载程序集查找程序集的类,使用类创建对象实例,使用获取的对象实例访问方法、属性、字段等内容。Assembly  assembly= Assembly.Load("程序集路径dll或者exe");方法加载程序集。assembly.CreateInstance("类的完全限定名称,命名空间.类名称")方法创建类实例,通过实例执行类操作。反射在实际开发中,比如对于数据的ORM,通过反射的方式把DataTable转换成关系对象;在执行方法需要依据实际情况进行选择的时候可以动态执行;在asp.net mvc的框架中路由、控制器、方法中也是广泛使用了反射。

    四、总结

      反射的优缺点,优点是提高程序的灵活性和扩展性;降低耦合,提高自适应能力;对任何类的无需硬编码在运行时进行绑定。缺点是性能问题,反射通过获取元数据的描述内容,查找,创建过程相对直接使用是慢的;反射的模糊程序内部逻辑,没有依据代码,直接获取对象信息影响代码的阅读和维护。

      在实际开发中,由于其缺点问题,所以基本不怎么使用反射的功能,但是必须了解反射的机制,因为在很多封装的框架模块中都应用了反射的特性,对于源码的阅读理解有很大的帮助。

      通过面试被问到反射的原理,基于目前知识面只是知道反射是什么,怎么使用的,因为这件事情促使进一步深究了解到程序集,元数据的内容,了解到反射实现机制。从而对整个知识串联起来,形成一个体系,融会贯通。所以技术的学习过程是一个深入探究的过程,形成体系的过程,融会贯通的过程

  • 相关阅读:
    重装window7系统(使用U盘)
    冒泡排序
    使用jad进行反编译.class文件生成.java
    java代码编写1+2+3+....+100之和
    springboot多模块项目不同模块组件不能@autowired问题
    django CBV和FBV写法总结
    django cookie与session组件
    djjango cookie和session 的几种常用需求使用方法
    django 一个关于分组查询的问题分析
    Django book manage system
  • 原文地址:https://www.cnblogs.com/tuqunfu/p/14768605.html
Copyright © 2020-2023  润新知