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的框架中路由、控制器、方法中也是广泛使用了反射。

四、总结

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

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

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

posted @   tuqunfu  阅读(167)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示