运行时类型标识、反射

时间:2022-01-06 12:56:31

运行时类型标识:

        运行时类型标识(runtime type ID)是一种在程序执行期间标识一个类型的机制。借助运行时类型标识(RTTI),可以在程序执行期间判定对象的类型。另一个作用是预先检测某个强制类型转换能够成功,从而避免非法的强制类型转换异常。

         在C#中有3个支持RTTI的关键字:is as 和typeof。

1.使用is运算符测试类型

   通过is运算符能够判断对象的类型是否为特定类型。

    它的基本形式为: expr is type 

   如果expr的类型与type指定的类型相同或兼容,那么此运算的结果为真。否则,结果为假。

2.使用as运算符

     有时希望在程序运行期间执行转换,并且即使转换失败,也不会产生异常(在使用强制类型转换时就会如此)。为此,需要使用as运算符,其基本形式为:  expr as type

     这里,expr表达式江北转换为type指定的类型。如果转换成功,就返回一个type类型的引用,否则返回一个空引用。as 运算符只可以用于引用、装箱、拆箱、或身份转换。

3.使用typeof运算符

     as和is运算符能够测试两种类型的兼容性。但在大多数情况下,还需要某个类型的具体信息。为此,应使用C#提供的typeof运算符,它可以返回与具体类型相关的System.Type对象。

通过System.Type对象,可以确定此类型的特征。

     typeof运算符的基本形式为:

     typeof(type) 

     其中,type是正在获得的类型,返回的Type对象封装了与type关联的信息。

     一旦获得给定类型的Type对象,就可以通过该对象定义的各种属性、字段、方法来获取类型的具体信息。

           Type t = typeof(Student);
            Console.WriteLine(t.FullName);

反射:

        反射(reflection)是一种允许用户获得类型信息的C#特性。术语“反射”源自于它的工作方式:Type对象映射它所代表的底层对象。对Type对象进行查询可以获得(反射)与类型相关的信息。

反射是一种功能强大的机制,它允许学习和使用只在运行时才能知道的类型的功能。

         许多支持反射的类都位于System.Reflection名称空间,它们是.NET反射应用程序接口(API)的一部分。

          反射的核心:System.Type 

          System.Type封装了类型,因此是整个反射子系统的核心。System.Type包含了很多属性和方法,使用这些属性和方法可以在运行时得到类型的信息。Type 派生于System.Reflection.MemberInfo 抽象类。

 使用反射:

                  通过使用Type类型定义的方法和属性,能够在运行时获得类型的各种具体信息。这是一个非常强大的功能。一旦得到了类型的信息,就可以调用其构造函数、方法、和属性。因此,反射允许使用编译时不可用的代码。

下面分别说明了四种关键的反射技术:获取方法的信息、调用方法、构造对象以及从程序集中装载类型。

1.获取方法的相关信息

一旦有了Type对象,就可以用GetMethods()方法来获取此类型的方法的列表。

它的一种形式为: MethodInfo[] GetMethods()

          它返回一个MethodInfo对象数组,MethodInfo对象描述了主调类型所支持的方法,它位于System.Reflection名称空间中。

           MethodInfo派生于抽象类MethodBase,而MethodBase继承了MemberInfo。因此,能够使用这3个类定义的属性和方法。例如,可以使用Name属性得到方法的名称。此时,还有两个重要的方法,ReturnType和GetParameters()。

           只读属性ReturnType为一个Type类型的对象,它为用户提供方法的返回类型信息。 

          GetParameters()返回一个方法的参数列表,它的基本形式为:

           ParameterInfo[] GetParameters();

           参数信息保存在ParameterInfo对象中。ParameterInfo类定义了大量描述参数信息的属性和方法。其中,两个常用属性是Name(一个包含参数名称信息的字符串)和ParameterType(参数的类型信息)。参数的类型则封装在一个Type对象中。

   public class MyClass
    {
        int x, y;
        public MyClass(int i, int j)
        {
            x = i;
            y = j;
        }
        public int Sum()
        {
            return x + y;
        }
        public bool IsBetween(int i)
        {
            if (x < i & i < y) return true;
            else return false;
        }
        public void Set(int a, int b)
        {
            x = a;
            y = b;
        }
        public void Set(double a, double b)
        {
            x = (int)a;
            y = (int)b;
        }


        public void Show()
        {
            Console.WriteLine(" x:{0},y:{1}", x, y);
        }


    }
    class NTest
    {
        static void Main(string[] args)
        {




            Type t = typeof(MyClass);


            MethodInfo[] methods = t.GetMethods();


            Console.WriteLine("MethodInfo List");


            foreach (MethodInfo m in methods)
            {
                Console.Write("方法:{0}(  ", m.Name);


                ParameterInfo[] pars = m.GetParameters();


                for (int i = 0; i < pars.Length; i++)
                {
                    Console.Write("{0} {1}", pars[i].ParameterType, pars[i].Name);
                    if (i + 1 < pars.Length) Console.Write(",");
                }
                Console.Write(")");
                Console.WriteLine("");
            }




            Console.Read();
        }
    }

输出结果:
    //MethodInfo List
    //方法:Sum(  )
    //方法:IsBetween(  System.Int32 i)
    //方法:Set(  System.Int32 a,System.Int32 b)
    //方法:Set(  System.Double a,System.Double b)
    //方法:Show(  )
    //方法:ToString(  )
    //方法:Equals(  System.Object obj)
    //方法:GetHashCode(  )
    //方法:GetType(  )

2.GetMethods()的另一种形式

           GetMethods()还有另外一种形式。在这种形式中可以制定各种标记,而筛选出想要的方法。

           它的基本形式为:MethodInfo[]  GetMethods(BindingFlags flags);

           可以使用OR运算符把两个或更多的标记连接在一起。实际上,括号中至少应包含Instance与public标记,否则将不会获得任何方法。

            MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);

3.使用反射调用方法

       一旦知道了一个类型所支持的方法,就可以对方法进行调用。调用时,须使用包含在MethodInfo中的Invoke()方法。下面是它的一种形式:

       这里,ob是一个对象引用,将调用它所指向的对象上的方法。对于静态方法,ob必须为null。所有需要传递给方法的参数都必须在args数组中指定。如果方法不需要参数,则args必须为null。另外,数组args的元素数量必须等于参数数量。因此,如果需要两个参数,args就应该具有两个元素的长度,而不能使三个或者四个元素的长度。Invoke()方法返回被调用方法的返回值。

       

  class NTest
    {
        static void Main(string[] args)
        {


            MyClass reflectOb = new MyClass(10, 20);
            Type t = typeof(MyClass);


            MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
            Console.WriteLine("MethodInfo List");
            foreach (MethodInfo m in methods)
            {
                ParameterInfo[] pars = m.GetParameters();
                if (m.Name.CompareTo("Set") == 0 && pars[0].ParameterType == typeof(int))
                {
                    object[] obj = new object[2];
                    obj[0] = 9;
                    obj[1] = 18;
                    m.Invoke(reflectOb, obj);
                }
                if (m.Name.CompareTo("Set") == 0 && pars[0].ParameterType == typeof(double))
                {
                    object[] obj = new object[2];
                    obj[0] = 1.12;
                    obj[1] = 23.4;
                    m.Invoke(reflectOb, obj);
                }
                object num = 0;
                if (m.Name.CompareTo("Sum") == 0)
                {
                    num = m.Invoke(reflectOb, null);
                    Console.WriteLine("sum is " + num.ToString());
                }
                if (m.Name.CompareTo("IsBetween") == 0)
                {
                    object[] obj = new object[1];
                    obj[0] = 14;
                    if ((bool)m.Invoke(reflectOb, obj))
                        Console.WriteLine("14 is between x and y");
                }
                if (m.Name.CompareTo("Show") == 0)
                {
                    m.Invoke(reflectOb, null);
                }
                Console.WriteLine("");
            }
            Console.Read();
        }
    }



结果:
//MethodInfo List
//sum is 30
//14 is between x and y
//Inside Set(int,int).Value are x:9,y:18
//Inside Set(double,double).Value are x:1,y:23
//Value are x:1,y:23

  4. 获取Type对象的构造函数 

             由于MyClass类型的对象是显示创建的,因此使用反射技术来调用MyClass上的方法没有任何优势———以普通的方式调用对象上的方法会简单的多。但是,如果对象是在运行时动态创建的,反射功能的优势就显现出来了。通过这种机制,可以在运行时实例化任意类型的对象而不必在声明语句中指定。

              为了获得某个类型的构造函数,需要调用Type对象上的GetConstructors()方法。它的一种常用形式为:

                 ConstructorInfo[] GetConstructors();

               该方法会返回一个描述构造函数的ConstructorInfo对象数组。它的GetParameters()方法与MethodInfo中定义的GetParameters()方法非常相似。

                一旦找到了合适的构造函数,就调用ConstructorInfo定义的Invoke()方法来创建对象。给方法的一种形式如下所示:

                object Invoke(object[] args)

                 需要传递给此方法的所有参数都在数组args中指定。如果不需要任何参数,那么args必须为null。另外,args必须与包含于参数个数相同的元素,并且实参的类型必须与形参的类型兼容。Invoke()方法返回的是一个指向新构造对象的引用。

             

 static void Main(string[] args)
        {
            Type t = typeof(MyClass);
            ConstructorInfo[] strs = t.GetConstructors();  //取出构造函数列表
            ConstructorInfo con = strs[0];
            object objreflect = con.Invoke(null);          //实例化对象

            MethodInfo method = t.GetMethod("Show");
            method.Invoke(objreflect, null);       //找到方法,并执行
            MethodInfo method2 = null;
            MethodInfo[] methods = t.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
            foreach (MethodInfo m in methods)
            {
                if (m.Name == "Set" && m.GetParameters()[0].ParameterType == typeof(int))
                {
                    method2 = m;           //Set 方法为重载方法,参数类型为int
                    break;
                }
            }
            object[] obj = new object[2];
            obj[0] = 10;
            obj[1] = 20;
            method2.Invoke(objreflect, obj);       //找到方法,并执行

            Console.Read();
        }

 5 从程序集获得类型

         程序集提供了它所包含的类和结构的类型信息。借助反射应用程序接口,可以加载程序集,获取它的相关信息并创建其公共可用类型的实例。

          为了获取程序集的相关信息,首先需要创建一个Assembly对象。Assembly类并没有定义公有的构造函数,它的对象实例是通过类得一个方法获得的。

这里使用的LoadForm()方法可以加载由文件名指定的程序集,其形式为:

          static Assembly LoadFrom(string filename)

          其中,filename制定了程序集的文件名。

          一旦获得了Assembly类型的对象,就可以通过该对象的GetTypes()方法来得到它所定义的类型。

           其基本形式为: Type[] GetTypes()

          此方法返回一个数组,它包含了程序集中的类型。

            

   class NTest
    {
        static void Main(string[] args)
        {
            Assembly asm = Assembly.LoadFrom("ClassLibrary.dll");
            Type[] types = asm.GetTypes();

            foreach (Type t in types)
            {
                Console.WriteLine(t.FullName);
            }

            Type type = types[0];

            Console.WriteLine("当前使用的类为:", type.FullName);

            ConstructorInfo[] stru = type.GetConstructors();
            object objReflection = stru[0].Invoke(null);   //实例化引用对象

            MethodInfo m = type.GetMethod("Show");
            m.Invoke(objReflection, new object[]{"Vincent","9527"});                 //调用方法

            Console.Read();
        }
    }
ClassLibrary.dll中代码为:
namespace ClassLibrary
{
    public class Student
    {
        public string name { get; set; }
        public string code { get; set; }

        public void Show(string name, string code)
        {
            Console.WriteLine("My name is {0},code is {1}", name, code);
        }
    }

    public class Teacher
    {
        public string name { get; set; }
        public string subject { get; set; }

        public void Show(string name, string code)
        {
            Console.WriteLine("The teacher's name is {0},subject is {1}", name, subject);
        }
    }
}