资源描述
,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,2020/2/7,#,单击此处编辑母版标题样式,第,15,章 运行时类型标识、反射和特性,运行时类型标识是一种在程序执行期间标识类型的机制。反射功能可以为用户提供类型的信息,从而使开发人员在运行时能够利用这些信息构造和使用对象。特性则用于描述,C#,程序中的元素的某种特性。特性同时使用了运行时类型标识和反射。本章将详细介绍这,3,个相互关联的功能。,15.1,运,行,时类型标识,运行时类型标识(,RTTI,)可以在程序执行期间判定对象的类型。运行时类型标识还可以预先测试某个强制类型转化操作能否成功,从而避免无效的强制类型转换异常。运行时类型标识是反射机制的关键组成部分。在,C#,中支持运行时类型标识的关键字是,is,、,as,和,typeof,。,15.1.1 is,运算符测试类型,通过,is,运算符能够判断对象的类型是否为特定类型。换句话说就是利用,is,运算符判断左右两边的类型是否相同或是兼容。,is,运算符只返回布尔类型。它的通用形式如,下图,所示。,这里测试,expr,表达式描述的对象是否为,type,指定的类型。如果,expr,的类型与,type,指定的类型相同或兼容,那么结果为,true,,否则为,false,。如果结果为,true,,,expr,表达式就能够转换为,type,指定的类型。,【,示例,15-1】,以下使用,is,运算符判断类型是否相同。,其中,由于,b,是,B,类型的对象,而,B,派生于,A,类型,因此,b,类型与,A,类型兼容。反之,就不成立。因为,a,对象属于,A,类型,而,A,类型不派生于,B,类型。所以,a,对象不能转换为,B,类型。,15.1.2 as,运算符,一般我们都希望在程序运行期间执行转换,并且即使转换失败也不会抛出异常。这样,就需要使用,as,运算符。,as,运算符的通用形式如下图所示。,其中,,expr,表达式将转换换为,type,指定的类型。如果转换成功,那么就返回一个,type,类型的引用,否则返回一个空引用。,【,示例,15-2】,以下代码利用,as,运算符避免无效的强制类型转换。,其中,,as,语句先检查强制转换是否有效,如果有效,则执行强制转换过程。如果无效,则不执行强制转换。这样就不会产生异常。这里,由于,a,和,B,类型不兼容,转化失败,所以对象,b,为空引用。,15.1.3 typeof,运算符,typeof,运算符可以返回与具体类型相关的,System.Type,对象。通过,System.Type,对象,可以确定此类型的特性。,typeof,运算符的通用形式如下图所示。,其中,,type,表示获得的类型,返回的,Type,对象封装了与,type,关联的信息。,【,示例,15-3】,以下程序使用,typeof,运算符获取,StreamReader,类型的,Type,对象的相关信息。,其中,此程序调用了它的,2,个属性:,FullName,和,IsClass,。,FullName,属性可以返回类型的全称,,IsClass,属性在类型为一个类时返回真。,15.2,反射,反射是一种允许用户获得类型信息的,C#,功能。它可以获取和使用只在运行时才能知道的类型的功能。许多支持反射的类都位于,System.Reflection,命名空间中。因此,在使用反射的程序中通常包含以下代码:,using System.Reflection;,15.2.1 System.Type,System.Type,类包装了类型,是整个反射子系统的核心。,System.Type,类包含了很多属性和方法,使用这些属性和方法可以在运行时得到类型的信息。,Type,类派生于,System.Reflection.MemberInfo,抽象类。,MemberInfo,类定义的只读属性如下表所示。,属性,功能,Type DeclaringType,获取声明该成员的类或接口的类型,MemberTypes MemberType,获取成员的类型。这个值用于指示该成员是字段、方法、属性、事件或构造函数,int MetadataToken,获取与特定元数据相关的值,Module Module,获取一个代表反射类型所在模块(可执行文件)的,Module,对象,string Name,成员的名称,Type ReflectedType,反射的对象的类型,除了,MemberInfo,类定义的方法和属性,,Type,类还添加了许多自己的方法和属性。,如下表所,示是,Type,类定义的常用方法。,方法,功能,ConstuctInfoGetConstructors(),获取指定类型的构造函数列表,EventInfoGetEvents(),获取指定类型的事件列表,FieldInfoGetFields(),获取指定类型的字段列表,TypeGetGenericArguments(),获取与已构造的泛型类型绑定的类型实参列表,如果指定的类型是泛型类型定义,则获得类型形参。对于在在构造的类型,该列表就可能同时包含类型实参和类型形参,MemberInfoGetMembers(),获取指定类型的成员列表,MethodInfoGetMethods(),获取指定类型的方法列表,PropertyInfoGetProperties(),获取指定类型的属性列表,Type,类定义的只读属性,如下表所,示。,属性,功能,Assembly Assembly,获取指定类型的程序集,TypeAttributes Attributes,获取指定类型的特性,Type BaseType,获取指定类型的直接基类型,string FullName,获取指定类型的全名,bool IsAbstract,如果指定类型是抽象类型,就返回,true,bool IsArray,如果指定类型是数组,就返回,true,bool IsClass,如果指定类型是类,就返回,true,bool IsEnum,如果指定类型是枚举,就返回,true,bool IsGenericParameter,如果指定类型是泛型类型参数,就返回,true,bool IsGenericType,如果指定类型是泛型类型,就返回,true,string Namespace,取得指定类型的命名空间,15.2.2,使用反射获取方法的相关信息,通过使用,Type,类定义的方法和属性,我们能够在运行时获得类型的各种具体信息。一旦有了,Type,对象,就可以使用,GetMethods(),方法获取此类型支持的方法的列表。该方法有以下两种形式:,1.GetMethods(),方法的一种形式,GetMethods(),方法的一种形式为:,MethodInfoGetMethods(),它返回一个,MethodInfo,对象数组,,MethodInfo,对象描述了主调类型所支持的方法,它位于,System.Reflection,命名空间中。,MethodInfo,类派生于,MethodBase,抽象类,而,MethodBase,类继承于,MemberInfo,类。此时,还有一个重要成员,GetParameters(),方法。,GetParameters(),方法返回一个方法的参数列表,它的通用形式如下:,ParameterInfoGetParameters(),其中,参数信息保存在,ParameterInfo,对象中。,ParameterInfo,类定义了大量描述参数信息的属性和方法。,【,示例,15-4,】,使用,反射获得,Myclass,类所支持的方法。对于每个方法,它将显示该方法的,名称、返回,类型以及每个方法可能具有的参数的名称和类型。,注意,:此程序中必须有以下代码:,using System.Reflection;,否则,会,提示错误,。,2.GetMethods(),方法的另一种形式,在,GetMethods(),方法的另一种形式中可以指定各种标记,以筛选相应获取的方法。此形式值获得与指定的条件相匹配的方法。其通用形式如下:,MethodInfoGetMethods(BindingFlags bindingAttr),其中,,BindingFlags,是一个枚举。,bindingAttr,表示,BindingFlags,枚举的值,。下表是,BindingFlags,的一些常用值,。,值,含义,DeclaredOnly,仅获取指定类定义的方法,而不获取类所继承的方法,Instance,获取实例方法,NonPublic,获取非公有方法,Public,获取公有方法,Static,获取,static,方法,可以使用,or,运算符把两个或多个标记连接在一起。一般,至少应包含,Instance,或,Static,与,Public,或,NonPublic,标记,否则将不会获取任何方法。,【,示例,15-5,】,该,程序,使用,GetMethods(),方法的另一种形式重写了前面的程序。,15.2.3,使用反射调用方法,如果知道了一个类型所支持的方法,那么就可以对方法进行调用。在调用时,必须使用包含在,MethodInfo,类中的,Invoke(),方法。其形式如下图所示。,其中,,obj,是一个对象引用,将调用它所指向的对象上的方法。对于,static,方法,,obj,必须为,null,。所有需要传递给方法的参数都必须在,parameters,数组中指定。如果方法不需要参数,则,parameters,必须为,null,。另外,,parameters,数组的元素数量必须等于参数数量。,Invoke(),方法返回被调用方法的返回值。,【,示例,15-6】,以下程序通过调用,GetMethods(),方法获得,MethodInfo,实例,然后在,MethodInfo,实例上调用,Invoke(),方法,。,其中,,使用反射调用方法,Invoke(),,返回被调用方法的返回值。,15.2.4,使用反射获取,Type,对象的构造函数,如果对象是在运行时动态创建的,使用反射的方式会有所不同。首先需获取一个构造函数列表。然后,调用列表中的某个构造函数,创建一个该类型的实例。通过这种机制,可以在运行时实例化任意类型的对象,而不必再声明语句中指定类型。,为了获得某个类型的公有非静态构造函数,需要调用,Type,对象上的,GetConstructors(),方法。该方法的常用形式如下:,ConstructorInfo,GetConstructrs(),其中,该方法返回一个描述构造函数的,ConstructorInfo,对象数组。,ConstructorInfo,类派生于,MethodBase,抽象类,而,MethodBase,类继承了,MemberInfo,类。,ConstructorInfo,类也定义了自己的一些成员。其中主要的成员是,GetParameters(),方法,该方法可以返回给定构造函数的参数列表,它的工作方式与在,MethodInfo,类中定义的,GetParameters(),方法相似。,如果找到了合适的构造函数,就调用,ConstructorInfo,类定义的,Invoke(),方法创建对象。该方法的形式如下:,objet Invoke(object,parameters),其中,需要传递给此方法的所有参数都在,parameters,数组中指定。如果不需要任何参数,那么,parameters,必须为,null,。另外,,parameters,数组必须包含与参数数量相同的元素,并且实参的类型必须与形参的类型兼容。,Invoke(),方法返回的是一个指向新构造对象的引用。,15.2.5,使用反射从程序集获得类型,为了获得程序集的相关信息,首先需要创建一个,Assembly,对象。,Assembly,类并没有定义公有的构造函数,它的对象实例是通过调用类的一个方法获得的。使用,LoadFrom(),方法可以加载由文件名指定的程序集,其形式如下图所示。,其中,,assemblyFile,指定了程序集的文件名。,如果获得了,Assembly,类型的对象,就可以通过在该对象上调用,GetTypes(),方法得到它所定义的类型。该方法的通用形式如下:,Type GetTypes(),其中,此方法返回一个数组,它包含了程序集中的类型。,【,示例,15-7,】,该程序,介绍了如何获取程序中的类型。,其中,在编译“,使用反射从程序集获得类型 示例”前应将,MyClass,项目生成的,MyClass.exe,文件放到“使用反射从程序集获得类型 示例,使用反射从程序集获得类型 示例,binDebug,”文件中。否则会产生,如下图所,示错误。,15.3,特性,C#,允许开发人员以特性的形式为程序添加声明性信息。特性定义了与类、结构、方法等相关的附加信息。特性由,System.Attribute,类的派生类支持。因此,所有的特性类必须是,Attribute,类的子类。,Attribute,类定义了很多的功能,但是在使用特性时一般不会用到全部的功能。特性类名通常使用后缀,Attribute,。,15.3.1,创建特性,在声明特性类时,前面应添加,AttributeUsage,特性。这个内置特性制定了应用该特性的想的类型。在特性类中,可以定义支持该特性的成员。特性类通常非常简单,只包含少量的字段或属性。,【,示例,15-8】,定义一个注解,用于描述应用此特性的项。,这里,声明,AbcAttribute,类,它继承,Attribute,类。在,AbcAttribute,类中只有一个私有字段,abc,,用于支持公有的只读属性,Abc,。,AbcAttribute,类中还有一个构造函数,该构造函数带有一个字符串参数,将参数值赋给,Abc,属性。,15.3.2,连接特性,如果定义了特性类,就可以把该特性连接到项上。可以把特性放置在应用它的项之前,并且通过把它的构造函数包含在方括号中来指定相应的项。,【,示例,15-9】,以下代码应用,AbcAttribute,特性。,其中,此代码构造了一个包含注解“连接特性”的,AbcAttribute,特性,然后将该特性连接到,Program,类。,在连接特性时,也可以不需要指定,Attribute,后缀,。,注意,:虽然可以直接使用名称,Abc,。但是在连接特性时使用全名比较安全,可以避免可能产生的多义性。,15.3.3,获取对象的特性,如果把特性连接到项目上,程序的其他部分就能够获取该特性。为了获取特性,通常需要使用以下两种方法中的一种。,第一个方法是,GetCustomAttributes(),方法,它由,MemberInfo,类定义,并且由,Type,类继承。此方法可以获取连接到某个项的所有特性的列表,它的通用形式如下图所示。,其中,如果,inherit,为,true,,就会包括继承链中所有基类的特性。否则,就只获取为指定另行定义的特性。,第二种方法是由,Attribute,类定义的,GetCustomAttribute(),方法,它的,形式如下图所,示,。,其中,,,element,是一个,MemberInfo,类型的对象,它描述了要获得哪个项的特性。,attributeType,指定要获得的特性。如果已知想要获得的特性的名称,就可以使用此方法。,【,示例,15-10】,以下程序获得一个对,RemarkAttribute,特性的引用,并显示,Abc,属性。,其中,由于程序中使用连接特性的元素,所以可以获得该特性的相关信息。,【,示例,15-11】,以下程序将所有内容组合在一起来介绍,AbcAttribute,特性的用法。,15.3.4 3,个内置特性,C#,定义了多个内置特性,其中最重要的三个特性是,AttributeUsage,、,Conditional,和,Obsolete,。,1.AttributeUsage,特性,AttributeUsage,特性指定了能够应用特性的项的类型。,AttributeUsage,特性是,System.AttributeUsageAttribute,类的别名,它的构造函数如下图所示。,其中,,validOn,指定了可以应用该特性的项。,AttributeTargets,是枚举,它定义了很多值,可以将这些值中的两个或多个执行,or,运算。,AttributeTargets,的值如下表所示。,【,示例,15-12】,以下代码指定一个只能应用于字段和属性的特性。代码如下:,AttributeUsage(AttributeTargets.Field|AttributeTargets.Property),值,说明,Assembly,可以对程序集应用特性,Module,可以对模块应用特性,Class,可以对类应用特性,Struct,可以对结构应用特性,即值类型,Enum,可以对枚举应用特性,Constructor,可以对构造函数应用特性,Method,可以对方法应用特性,Property,可以对属性应用特性,Field,可以对字段应用特性,Event,可以对事件应用特性,Interface,可以对接口应用特性,Parameter,可以对参数应用特性,Delegate,可以对委托应用特性,ReturnValue,可以对返回值应用特性,GenericParameter,可以对泛型参数应用特性,All,可以对任何应用程序元素应用特性,2.Conditional,特性,Conditional,特性允许开发人员创建条件方法。只有在通过,#define,指令定义了特定的符号时才能调用条件方法。否则,将忽略该方法。因此,条件方法可以取代使用,#if,指令执行的条件编译,。要,使用,Conditional,特性,必须使用,System.Diagnostics,命名空间。代码如下:,using System.Diagnostics;,Conditional,特性的通用形式如下:,Conditional(,“,abc,”,),其中,,abc,是决定是否执行该方法的符号。如果已经定义,abc,符号,那么在调用方法时就会执行它。如果未定义此符号,则不执行相应的方法。,【,示例,15-13】,以下程序定义了,ABC,符号,而且使用了,Conditional,特性,。,3.Obsolete,特性,Obsolete,特性是,System.ObsleteAttribute,类的简写形式,它可以把程序中的元素标记为不再使用。其通用形式如下:,Obsolete(,“,message,”,),其中,编译程序元素时将显示,message,。,Obsolete,还有第二种形式,其形式如下:,Obsolete(,“,message,”,error),其中,,error,是一个布尔值。如果它为真,那么当使用不再使用的项时将产生编译错误而不是警告。,【,示例,15-14】,以下程序使用,Obsolete,特性。,15.4,小结,本章详细地为大家介绍了运行时类型标识的相关内容和反射的相关内容,还介绍了特性的相关内容。其中,运行时类型标识比较简单。反射和特性比较难一些。希望大家好好理解。下一章将为大家介绍泛型。,
展开阅读全文