• 反射简介—类型反射和晚期绑定


    使用元数据完整地描述类型(类、接口、结构、枚举和委托)的能力是.NET平台的一个关键要素,像对象序列化、WCF等技术都需要在运行时通过元数据来发现类型格式。

    通过ildasm.exe这个工具,我们可以查看一个程序集的元数据(Ctrl+M组合键)。在.NET中,利用反射(reflection)服务,我们就可以通过编程方式得到与ildasm.exe显示的相同的元数据信息。

    System.Reflection这个命名空间中包括了我们使用反射中涉及的所有类型,但是开始介绍反射之前,我们要看看System.Type类。

    System.Type类

    System.Type类定义了很多成员,用来检查某个类型的元数据,它们返回的类型大多数都是System.Reflection命名空间中的类型。例如说,Type.GetMethods()返回一个MethodInfo类型的数组,Type.GetFields()返回一个FieldInfo类型的数组等等。

    在.NET中,有三种方式可以得到一个Type类的实例引用。

    使用System.Object.GetType()

    由于Type是一个抽象类,所以不能直接使用new关键字创建一个Type对象,我们的首选方案是使用System.Object定义的GetType()方法,这个方法返回一个表示当前对象元数据的Type类的实例。

    Student stu = new Student();
    Type studentType = stu.GetType();

    通过上面代码也可以看到,使用这个方法获得Type类的实例是有前提的,必须得到类型(Student类)的编译时信息,并且内存中要有该类新的实例(stu实例)。

    使用typeof()

    在程序中,我们还可以通过C#的typeof操作符类获取类型信息。

    Type type = typeof(Student);

    使用typeof操作符,我们就不需要先建立一个实例来提取类型信息,但是,仍然需要知道类型的编译时信息,因为typeof需要通过强类型来获取类型信息。

    使用System.Type.GetType()

    在.NET还有一种更灵活的方式得到类型信息,可以通过System.Type类的静态成员GetType(),然后指定类型的完全限定名。采用这种方法,我们可以不需要知道类型的编译时信息,GetType()方法可以接受任何字符串值(而不是强类型),当然这个字符串就是类型名字的字符串形式。

    Type.GetType()方法有很多重载,我们介绍一个常用的:

    public static Type GetType(string typeName, bool throwOnError, bool ignoreCase);

    使用这个方法我们可以指定两个布尔类型的参数,一个用来表示当类型找不到时是否抛出异常,另一个用来表示是否区分字符串大小写,下面就是第三种获得Student的Type类实例的方式:

    Type type = Type.GetType("ReflectionTest.Student", false, true);

    在上面这个语句中,字符串没有包含类型所在的程序集信息,这种情况就被认为该类型是定义在当前执行的程序集中。当需要得到一个外部程序集的类型元数据时,字符串参数必须使用类型完全限定名,加上类型所在程序集的友好名字。

    Type type = Type.GetType("ReflectionTest.Student, ReflectionTest", false, true);

    反射

    为了进一步了解反射以及System.Type,下面看一个例子,在这个例子中,我们将使用一些System.Reflection中常被用到的类型,例如MethodInfo,FieldInfo和PropertyInfo等。

    namespace ReflectionTest
    {
        class Student : IComparable
        {
            public int Id { get; private set; }
            public string Name { get; set; }
            public int Age { get; set; }
            public string Gender { get; set; }
    
            public void Greeting(string name)
            {
                Console.WriteLine("{0} said Hello to {1}", this.Name, name);
            }
    
            public string GetInfo()
            {
                return string.Format("Basic Info:
     Name: {0}
     Age: {1}
     Gender: {2}", this.Name, this.Age, this.Gender);
            }
    
            public int CompareTo(object obj)
            {
                throw new NotImplementedException();
            }
        }
    
        class Program
        {
            static void Main(string[] args)
            {
                Type type = typeof(Student);
                ListMethods(type);
                ListFields(type);
                ListProps(type);
                ListInterfaces(type);
                ListOhterStatus(type);
                Console.Read();
            }
    
            static void ListMethods(Type t)
            {
                Console.WriteLine("------Methods------");
                var methodInfos = from m in t.GetMethods()
                                  select m;
                foreach (var methodInfo in methodInfos)
                {
                    //Get return type
                    string retVal = methodInfo.ReturnType.FullName;
                    string paramInfo = "(";
                    // Get params
                    foreach (ParameterInfo pi in methodInfo.GetParameters())
                    {
                        paramInfo += string.Format("{0} {1}", pi.ParameterType, pi.Name);
                    }
                    paramInfo += ")";
                    Console.WriteLine("-->{0} {1} {2}", retVal, methodInfo.Name, paramInfo);
                }
                Console.WriteLine();
            }
    
            static void ListFields(Type t)
            {
                Console.WriteLine("------Fields------");
                var fieldNames = from f in t.GetFields()
                                 select f.Name;
                foreach(var fieldName in fieldNames)
                    Console.WriteLine("-->{0}", fieldName);
                Console.WriteLine();
            }
    
            static void ListProps(Type t)
            {
                Console.WriteLine("------Properties------");
                var propNames = from p in t.GetProperties()
                                 select p.Name;
                foreach (var propName in propNames)
                    Console.WriteLine("-->{0}", propName);
                Console.WriteLine();
            }
    
            static void ListInterfaces(Type t)
            {
                Console.WriteLine("------Interfaces------");
                var interfaceNames = from i in t.GetInterfaces()
                                 select i.Name;
                foreach (var interfaceName in interfaceNames)
                    Console.WriteLine("-->{0}", interfaceName);
                Console.WriteLine();
            }
    
            static void ListOhterStatus(Type t)
            {
                Console.WriteLine("------Other status------");
                Console.WriteLine("Full name is: {0}", t.FullName);
                Console.WriteLine("Base class is: {0}", t.BaseType);
                Console.WriteLine("Is type abstract? {0}", t.IsAbstract);
                Console.WriteLine("Is type a class type? {0}", t.IsClass);
                
                Console.WriteLine();
            }
        }
    }

    例子还是比较简单的,我们通过反射查看了Student类型的一些元数据信息,结果如下,通过这个例子大致了解到反射的基本使用。

    动态加载程序集

    在程序的运行中,我们有时会需要加载外部程序集,这个操作被称作动态加载。

    通过System.Reflection中的Assembly类型,我们就可以动态的加载程序集,并找到关于程序集的相关特性。通过Assembly类提供的Load()和LoadFrom()方法,我们可以通过代码动态的加载一个程序集。

    注意,当使用Load()时,需要把要加载的程序集放到ExternalAssemblyTest工程的"bin/debug"目录,这样Load()方法才能找到这个程序集;当我们使用LoadFrom()方法的时候就比较灵活了,我们可以指定要加载程序集的绝对路径。下面看一个简单的例子:

    namespace ExternalAssemblyTest
    {
        class Program
        {
            static void Main(string[] args)
            {
                try
                {
                    Assembly asm = Assembly.Load("ReflectionTest");
                    //Assembly asm = Assembly.LoadFrom(@"C:WorkSpaceVSSharpInDepthExternalAssemblyTestinDebugReflectionTest.exe");
                    DisplayTypesInAssembly(asm);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                Console.Read();
            }
    
            static void DisplayTypesInAssembly(Assembly asm)
            {
                Console.WriteLine("------Types in Assembly------");
                Console.WriteLine("-->{0}", asm.FullName);
                var types = from t in asm.GetTypes()
                                select t;
                foreach (var type in types)
                    Console.WriteLine("Type: {0}", type);
            }
        }
    }

    通过这个例子我们可以到了两种动态加载程序集的方法调用,通过反射,我们可以查看被加载程序集的详细信息。

    晚期绑定

    晚期绑定(late binding)是一种创建一个给定类型的实例,并在运行时调用其成员,而不需要在编译时知道改类型存在的一种技术。

    System.Activator类

    System.Activator类是.NET晚期绑定过程中的一个关键类型。在代码中,可以通过System.Activator类的CreateInstance静态方法(CreateInstance有很多重载形式,下面给出了一个常用形式)来创建一个晚期绑定类型的实例。

    public static object CreateInstance(Type type)

    修改上面的例子,这次先动态加载程序集ReflectionTest,然后通过晚期绑定方式为程序集中的Student类型创建一个实例。

    static void Main(string[] args)
    {
        try
        {
            Assembly asm = Assembly.Load("ReflectionTest");
            //Assembly asm = Assembly.LoadFrom(@"C:WorkSpaceVSSharpInDepthExternalAssemblyTestinDebugReflectionTest.exe");
    
            //Get Student Type
            Type studentType = asm.GetType("ReflectionTest.Student");
            //Create Student object with late binding
            object student = Activator.CreateInstance(studentType);
            Console.WriteLine("Create object {0} with late binding", student);
            ListMethods(studentType);
    
        }
        catch (Exception e)
        {
            Console.WriteLine(e.Message);
        }
        Console.Read();
    }
    
    static void ListMethods(Type t)
    {
        Console.WriteLine("------Methods------");
        var methodInfos = from m in t.GetMethods()
                          select m;
        foreach (var methodInfo in methodInfos)
        {
            //Get return type
            string retVal = methodInfo.ReturnType.FullName;
            string paramInfo = "(";
            // Get params
            foreach (ParameterInfo pi in methodInfo.GetParameters())
            {
                paramInfo += string.Format("{0} {1}", pi.ParameterType, pi.Name);
            }
            paramInfo += ")";
            Console.WriteLine("-->{0} {1} {2}", retVal, methodInfo.Name, paramInfo);
        }
        Console.WriteLine();
    }

    代码的输出为

    对于已经创建的student实例,我们可以通过反射来调用实例的方法。

    调用没有参数的方法

    根据前面了解到,当我们得到一个Type实例后,就可以通过Type.GetMethod()得到一个MethodInfo对象;然后就可以通过MethodInfo对象的Invoke()方法来实现一个方法的调用。

    举例,可以在例子中加入下面代码来调用Student类型的GetInfo()方法,由于该方法不需要参数,所有Invoke()的时候用一个null。

    //Get the method
    MethodInfo getInfoMethod = studentType.GetMethod("GetInfo");
    //call the method without argument
    //null here shows that the method has no argument
    string info = (string)getInfoMethod.Invoke(student, null);
    Console.WriteLine(info);

    代码的输出为,由于没有给Name和Gender属性设置值,所以这里显示为空。

    调用有参数的方法

    在使用晚期绑定调用需要参数的方法时,要将参数打包到一个object类型的数组中。

    下面我们调用属性的set方法给属性赋值。

    //Get the method
    MethodInfo setName = studentType.GetMethod("set_Name");
    //call the method without argument
    //create object array with values of arguments
    setName.Invoke(student, new object[] { "Wilber" });
    MethodInfo setAge = studentType.GetMethod("set_Age");
    setAge.Invoke(student, new object[] { 28 });
    MethodInfo setGender = studentType.GetMethod("set_Gender");
    setGender.Invoke(student, new object[] { "Male" });

    程序输出为

    总结

    通过这篇文章,介绍了反射的一些基本概念,并且通过一些例子简单的演示了反射的使用。通过文章中所有的介绍,应该就可以对反射有个基本的认识了。

    接下来一篇文章会介绍一下C#特性(Attribute)跟反射的结合使用。

  • 相关阅读:
    js 日期插件 datepicker
    Yii 安装二维码扩展Qrcode
    Yii2 验证码
    Yii 通过composer 安装的方法
    JQery icheck 插件
    Yii 设置 flash消息 创建一个渐隐形式的消息框
    Yii2 自动更新时间created_at updated_at
    MySQL 安装与使用(二)
    MySQL 安装与使用(一)
    Nginx使用(配置开机启动)
  • 原文地址:https://www.cnblogs.com/wilber2013/p/4345234.html
Copyright © 2020-2023  润新知