运行时类型标识:
运行时类型标识(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); } } }