C#基础系列-反射


一、定义

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

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

二、使用  

  使用反射可以获取如下内容,依据该内容操作程序集、类、对象,如下表格所示:

属性 用途
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 反射
{
    /// 
    /// 反射Demo
    /// 
    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();
        }
    }

    /// 
    /// 接口
    /// 
    public interface IAnimal
    {
        void Eat(string food);
        void Fly();
    }

    /// 
    ////// 
    [Serializable]
    public class Animal : IAnimal
    {
        public  string type;
        /// 
        /// 属性
        /// 
        public string Name { get; set; }

        /// 
        /// 默认构造函数
        /// 
        public Animal()
        {
        }

        /// 
        /// 构造函数
        /// 
        /// 
        public Animal(string name)
        {
            Name = name;
        }

        /// 
        /// 动物吃东西
        /// 
        /// 
        public void Eat(string food)
        {
            Console.WriteLine("动物{0}在吃{1}", Name, food);
        }

        /// 
        /// 动物飞起来
        /// 
        public void Fly()
        {
            Console.WriteLine("动物{0}在飞", Name);
        }
    }
}

  通过System.Reflection.Assembly来动态加载程序集,查找程序集的类,使用类创建对象实例,对象实例访问方法、属性、字段等内容。Assembly  assembly= Assembly.Load("程序集路径dll或者exe");方法加载程序。assembly.CreateInstance("类的完全限定名称,命名空间.类名称")创建类实例,通过实例执行操作。

  反射在实际开发中,比如对于数据操作ORM,通过反射的方式把DataTable转换成关系对象;在执行方法需要依据实际情况进行选择的时候可以动态执行;在asp.net mvc的框架中路由、控制器、方法中也是广泛使用了反射。

三、总结

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

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

C