Fork me on GitHub
类型反射和晚期绑定

类型反射和晚期绑定

使用元数据完整地描述类型(类、接口、结构、枚举和委托)的能力是.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:\n Name: {0}\n Age: {1}\n 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:\WorkSpace\VS\SharpInDepth\ExternalAssemblyTest\bin\Debug\ReflectionTest.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:\WorkSpace\VS\SharpInDepth\ExternalAssemblyTest\bin\Debug\ReflectionTest.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)跟反射的结合使用。

 
分类: C#相关
posted on 2015-03-18 11:32  HackerVirus  阅读(973)  评论(0编辑  收藏  举报