第13章 反 射
反射是一个普通术语,描述了在运行过程中检查和处理程序元素的功能。例如,反射允许完成以下任务:
● 枚举类型的成员
● 实例化新对象
● 执行对象的成员
● 查找类型的信息
● 查找程序集的信息
● 检查应用于类型的定制特性
● 创建和编译新程序集
能。但本章不可能介绍反射的所有功能,仅讨论最常用的功能。首先讨论定制特性,定制特性允许把定制的元数据与程序元素关联起来。这些元数据 |
Reflection.Assembly类,它们可以访问反射提供的许多功能。
为了演示定制特性和反射,我们将开发一个示例,说明公司如何定期升级软件,自动解释升级的信息。在这个示例中,要定义几个定制特性,表示程序元素最后修改或创建的日期,以及发生了什么变化。然后使用反射开发一个应用程序,在程序集中查找这些特性,自动显示软件自某个给定日期以来升级的所有信息。
本章要讨论的另一个示例是一个应用程序,该程序读写数据库,并使用定制特性,把类和特性标记为对应的数据库表和列。然后在运行期间从程序集中读取这些特性,使程序可以自动从数据库的相应位置检索或写入数据,无需为每个表或列编写特定的逻辑。
第Ⅰ部分C#语言
13.1定制特性
前面介绍了如何在程序的各个数据项上定义特性。这些特性都是Microsoft定义好的,
作为.NET Framework 类库的一部分,许多特性都得到了C#编译器的支持。对于这些特性, |
存中布置结构。
.NETFramework 也允许用户定义自己的特性。显然,这些特性不会影响编译过程,因
为编译器不能识别它们,但这些特性在应用于程序元素时,可以在编译好的程序集中用作元数据。
这些元数据在文档说明中非常有用。但是,使定制特性非常强大的因素是使用反射,代码可以读取这些元数据,使用它们在运行期间作出决策,也就是说,定制特性可以直接影响代码运行的方式。例如,定制特性可以用于支持对定制许可类进行声明代码访问安全检查,把信息与程序元素关联起来,由测试工具使用,或者在开发可扩展的架构时,允许加载插件或模块。
13.1.1编写定制特性
为了理解编写定制特性的方式,应了解一下在编译器遇到代码中某个应用了定制特性
的元素时,该如何处理。以数据库为例,假定有一个C#属性声明,如下所示。
[FieldName("SocialSecurityNumber")]
publicstring SocialSecurityNumber
{
get{
//etc.
这个名称的后面,形成一个组合名称FieldNameAttribute,然后在其搜索路径的所有命名 |
名称中,而是不修改该特性名。因此,上面的代码实际上等价于:[FieldNameAttribute("SocialSecurityNumber")]
publicstring SocialSecurityNumber
{
get{
//etc.
编译器会找到含有该名称的类,且这个类直接或间接派生自System.Attribute。编译器
还认为这个类包含控制特性用法的信息。特别是属性类需要指定:
● 特性可以应用到哪些程序元素上(类、结构、属性和方法等)
● 它是否可以多次应用到同一个程序元素上
● 特性在应用到类或接口上时,是否由派生类和接口继承322 | |
第12章反射
● 这个特性有哪些必选和可选参数
如果编译器找不到对应的特性类,或者找到一个这样的特性类,但使用特性的方式与特性类中的信息不匹配,编译器就会产生一个编译错误。例如,如果特性类指定该特性只能应用于字段,但我们把它应用到结构定义上,就会产生一个编译错误。
继续上面的示例,假定定义了一个FieldName特性:
[AttributeUsage(AttributeTargets.Property,
AllowMultiple=false,
Inherited=false)]
publicclass FieldNameAttribute : Attribute
{
privatestring name;
publicFieldNameAttribute(string name)
{
this.name= name;
}
}
下面几节讨论这个定义中的每个元素。 |
AttributeUsage主要用于表示定制特性可以应用到哪些类型的程序元素上。这些信息由它
的
第一个参数给出,该参数是必选的,其类型是枚举类型AttributeTargets。在上面的示例中,
指定FieldName特性只能应用到属性(property)上——这是因为我们在前面的代码段中
把它
应用到属性上。AttributeTargets枚举的成员如下:
● All
● Assembly | |
● Constructor
●Delegate
●Enum
●Event
●Field
●GenericParameter(仅.NET2.0 提供)
●Interface
●Method
●Module
● Parameter |
这个列表列出了可以应用该特性的所有程序元素。注意在把特性应用到程序元素上
时,应把特性放在元素前面的方括号中。但是,在上面的列表中,有两个值不对应于任何
程序元素:Assembly和Module。特性可以作为一个整体应用到程序集或模块中,而不是
应用到代码中的一个元素上,在这种情况下,这个特性可以放在源代码的任何地方,但需
要用关键字assembly或module来做前缀:
[assembly:SomeAssemblyAttribute(Parameters)]
[module:SomeAssemblyAttribute(Parameters)]
在指定定制特性的有效目标元素时,可以使用按位OR运算符把这些值组合起来。例
如,如果指定FieldName特性可以应用到属性和字段上,可以编写下面的代码:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple=false, | |
Inherited=false)] | |
publicclass FieldNameAttribute : Attribute
也可以使用AttributeTargets.All指定特性可以应用到所有类型的程序元素上。Attributes
Usage特性还包含另外两个参数AllowMultiple和Inherited。它们用不同的语法来指定:
<AttributeName>=<AttributeValue>,而不是只给出这些参数的值。这些参数是可选的,
如
果需要,可以忽略它们。
AllowMultiple参数表示一个特性是否可以多次应用到同一项上,这里把它设置为
false,表示如果编译器遇到下述代码,就会产生一个错误:
[FieldName("SocialSecurityNumber")]
[FieldName("NationalInsuranceNumber")]
publicstring SocialSecurityNumber
{
//etc.
如果Inherited 参数设置为true,就表示应用到类或接口上的特性也可以自动应用到所 |
//etc.
会检查传送给特性的参数(在本例中,是一个字符串),并查找该特性中带这些参数的
324
第12章反射
构造函数。如果找到一个这样的构造函数,编译器就会把指定的元数据传送给程序集。如果找不到,就生成一个编译错误。如后面所述,反射会从程序集中读取元数据,并实例化它们表示的特性类。因此,编译器需要确保存在这样的构造函数,才能在运行期间实例化指定的特性。
在本例中,仅为FieldNameAttribute提供了一个构造函数,而这个构造函数有一个字
符串参数。因此,在把FieldNameAttribute特性应用到一个属性上时,必须为它提供一个
字符串参数,如上面的代码所示。
如果可以选择特性的参数类型,当然可以提供构造函数的不同重载方法,但一般是仅提供一个构造函数,使用属性来定义其他可选参数,下面将介绍可选参数。 | |
3. 指定特性的可选参数 | |
在AttributeUsage特性中,可以使用另一个语法,把可选参数添加到特性中。这个语
法指定可选参数的名称和值,处理特性类中的公共属性或字段。例如,假定修改
SocialSecurityNumber属性的定义,如下所示:
[FieldName("SocialSecurityNumber",Comment="This is the primary key field")] public stringSocialSecurityNumber
{
//etc.
在本例中,编译器识别第二个参数的语法<ParameterName>=<ParameterValue>,所
以
不会把这个参数传递给FieldNameAttribute构造函数,而是查找一个有该名称的公用属性
或字段(最好不要使用公用字段,所以一般情况下要使用属性),编译器可以用这个属性设
置第二个参数的值。如果希望上面的代码工作,必须给FieldNameAttribute添加一些代
码:
Inherited=false)] public class FieldNameAttribute : Attribute[AttributeUsage(AttributeTargets.Property, |
get
{
returncomment;
}
set
{
comment= value;
}
}
//etc.
325
第Ⅰ部分C#语言
13.1.2定制特性示例:WhatsNewAttributes
本节开始编写前面描述过的示例WhatsNewAttributes,该示例提供了一个特性,表示 最后一次修改程序元素的时间。这个示例比前面所有的示例都复杂,因为它包含3个不同
的程序集:
● WhatsNewAttributes程序集,它包含特性的定义。
●VectorClass 程序集,包含所应用的特性的代码。
● LookUpWhatsNew程序集,包含显示已改变的数据项信息的项目。
当然,只有LookUpWhatsNew是前面使用的一个控制台应用程序,其余两个程序集都
是库文件,它们都包含类的定义,但都没有程序的入口。对于VectorClass程序集,我们使
用了VectorAsCollection示例,但删除了入口和测试代码类,只剩下Vector类。在命令行上编译,以此管理3个相关的程序集要求较高的技巧,所以我们分别给出编
译这3个源文件的命令。也可以编辑代码示例,(可以从WroxPress 网站上下载),组合为
一个VisualStudio 2005 解决方案,详见第14章。下载的文件包含所需的VisualStudio
解决方案文件。1. WhatsNewAttributes 库程序集2005 |
WhatsNewAttributes
项目中。编译为库的语法非常简单:在命令行上,给编译器提供标记target:library即可。
要编译WhatsNewAttributes,键入:
csc/target:library WhatsNewAttributes.cs
WhatsNewAttributes.cs文件定义了两个特性类LastModifiedAttribute和
SupportsWhats
New-Attribute。LastModifiedAttribute特性可以用于标记最后一次修改数据项的时间,它有
两个必选参数(该参数传递给构造函数);修改的日期和包含描述修改的字符串。它还有一 |
在现实生活中,或许想把特性应用到任何对象上。为了使代码比较简单,这里仅允许
将它应用于类和方法,并允许它多次应用到同一项上(AllowMultiple=true),因为可以多次
修改一个项,每次修改都需要用一个不同的特性实例来标记。
SupportsWhatsNew是一个较小的类,表示不带任何参数的特性。这个特性是一个程序
集的特性,用于把程序集标记为通过LastModifiedAttribute维护的文档说明书。这样,以
后查看这个程序集的程序会知道,它读取的程序集是我们使用自动文档说明过程生成的那个程序集。这部分示例的完整源代码如下所示:
usingSystem;
namespaceWrox.ProCSharp.WhatsNewAttributes
{
[AttributeUsage(
AttributeTargets.Class| AttributeTargets.Method,
AllowMultiple=true,Inherited=false)]
326
第12章反射
publicclass LastModifiedAttribute : Attribute
private string changes; private string issues; { |
}
publicDateTime DateModified
{
get
{
returndateModified;
}
}
publicstring Changes
{
get
{
returnchanges;
}
} | |
get
{
returnissues;
}
set
{
issues= value;
}
}
}
[AttributeUsage(AttributeTargets.Assembly)]
publicclass SupportsWhatsNewAttribute : Attribute
{
}
}
从上面的描述可以看出,上面的代码非常简单。但要注意,不必将set访问器提供给
Changes和DateModified属性,不需要这些访问器是因为在构造函数中,这些参数都是
必 |
意这里需要引用刚才创建的WhatsNewAttributes库,还需要使用using语句指定相应的
命
名空间,这样编译器才能识别出这些特性:
usingSystem;
usingSystem.Collections;
usingSystem.Text;
usingWrox.ProCSharp.WhatsNewAttributes;
[assembly:SupportsWhatsNew]
在这段代码中,添加了一行用SupportsWhatsNew特性标记程序集本身的代码。
下面考虑Vector类的代码。我们并不是真的要修改这个类中的任何内容,只是添加两
个LastModified 特性,以标记出本章对Vector 类进行的操作。把Vector 定义为一个类,而 |
是一
个结构,但其枚举器是一个类。于是,这个示例的下一个版本在查看程序集时,必须同时
考虑类和结构。这会使例子比较复杂)。
namespaceWrox.ProCSharp.VectorClass
{
[LastModified("14Feb 2007", "IEnumerable interface implemented " + "SoVector can now be treated as a collection")]
[LastModified("10Feb 2007", "IFormattable interface implemented " + "SoVector now responds to format specifiers N and VE")]
classVector : IFormattable, IEnumerable
{
publicdouble x, y, z;
publicVector(double x, double y, double z)
{
this.x= x;
this.y= y;
this.z= z;
}
public string ToString(string format, IFormatProvider formatProvider) { |
再把包含的VectorEnumerator类标记为new:
[LastModified("14Feb 2007",
"Classcreated as part of collection support for Vector")]
328
第12章反射
privateclass VectorEnumerator : IEnumerator
{
为了在命令行上编译这段代码,应键入下面的命令:
csc/target:library /reference:WhatsNewAttributes.dllVectorClass.cs上面是这个示例的代码。目前还不能运行它,因为我们只有两个库。在描述了反射的工作原理后,就介绍这个示例的最后一部分,查找和显示这些特性。
13.2反射
本节先介绍System.Type 类,通过这个类可以访问任何给定数据类型的信息。然后简 |
程
序集加载到程序中。最后把本节的代码和上一节的代码结合起来,完成
WhatsNewAttributes
示例。
13.2.1System.Type 类
在本书中的许多场合中都使用了Type类,但它只存储类型的引用:
Typet = typeof(double)
我们以前把Type看作一个类,但它实际上是一个抽象的基类。只要实例化了一个Type
对象,就实例化了Type的一个派生类。Type有与每种数据类型对应的派生类,但一般情
况下派生的类只提供各种Type方法和属性的不同重载,返回对应数据类型的正确数据。一
般不增加新的方法或属性。获取指向给定类型的Type引用有3种常用方式:
● 使用C#的typeof运算符,如上所示。这个运算符的参数是类型的名称(不放在引
● 使用GetType()方法,所有的类都会从System.Object 继承这个类。 |
Type对象仍只与该数据类型相关:它不包含与类型实例相关的任何信息。如果有一个对象
引用,但不能确保该对象实际上是哪个类的实例,这个方法也是很有用的。
● 还可以调用Type类的静态方法GetType():
Typet = Type.GetType("System.Double");
Type是许多反射技术的入口。它执行许多方法和属性,这里不可能列出所有的方法和
属性,而主要介绍如何使用这个类。注意,可用的属性都是只读的:可以使用Type确定
329
第Ⅰ部分C#语言
数据的类型,但不能使用它修改该类型!
1.Type 的属性
由 Type 执行的属性可以分为下述3 类: | |
● 有许多属性都可以获取包含与类相关的各种名称的字符串,如表12-1 所示。 | |
表12-1
属性 返 回 值
Name数据类型名
FullName数据类型的完全限定名(包括命名空间名)
Namespace定义数据类型的命名空间名
● 属性还可以进一步获取Type对象的引用,这些引用表示相关的类,如表12-2所示。
表12-2
属性 返回对应的Type引用
BaseType这个Type的直接基本类型
UnderlyingSystemType这个Type在.NET运行库中映射的类型(某些.NET基类实际上映射由IL识别的特定预定义类型)
● 许多Boolean属性表示这个类型是一个类、还是一个枚举等。这些属性包括IsAbstract、
IsArray、IsClass、IsEnum、IsInterface、IsPointer、IsPrimitive(一种预定义的基本数
据类型)、IsPublic、IsSealed和IsValueType
例如,使用一个基本数据类型:
Console.WriteLine(intType.IsClass); // writes false Console.WriteLine(intType.IsEnum); // writes falseType intType = typeof(int); |
TypeintType = typeof(Vector);
Console.WriteLine(intType.IsAbstract);// writes false
Console.WriteLine(intType.IsClass);// writes true
Console.WriteLine(intType.IsEnum);// writes false
Console.WriteLine(intType.IsPrimitive);// writes false
Console.WriteLine(intType.IsValueType);// writes false
也可以获取定义类型的程序集的引用,该引用作为System.Reflection.Assembly类实例
的一个引用来返回:
330
第12章反射
Typet = typeof (Vector);
AssemblycontainingAssembly = new Assembly(t);
2.方法
System.Type 的大多数方法都用于获取对应数据类型的成员信息:构造函数、属性、方 |
型的方法信息:GetMethod()和GetMethods()。GetMethod()方法返回System.Reflection.MethodInfo对象的一个引用,其中包含一个方法的信息。
GetMethods()
返回这种引用的一个数组。其区别是GetMethods()返回所有方法的信息,而GetMethod()
返回一个方法的信息,其中该方法包含特定的参数列表。这两个方法都有重载方法,该重
载方法有一个附加的参数,即BindingFlags枚举值,表示应返回哪些成员,例如,返回公
有成员、实例成员和静态成员等。
例如,GetMethods()最简单的一个重载方法不带参数,返回数据类型所有公共方法的
信息:
Typet = typeof(double);
MethodInfo[] methods = t.GetMethods();
foreach(MethodInfo nextMethod in methods)
{
Type 的成员方法如表12-3 所示遵循同一个模式。// etc. |
FieldInfoGetField(), GetFields()
InterfaceInfoGetInterface(), GetInterfaces()
MemberInfoGetMember(), GetMembers()
MethodInfoGetMethod(), GetMethods()
PropertyInfoGetProperty(), GetProperties()
GetMember()和GetMembers()方法返回数据类型的一个或所有成员的信息,这些成员
可以是构造函数、属性和方法等。最后要注意,可以调用这些成员,其方式是调用Type
的InvokeMember()方法,或者调用MethodInfo,PropertyInfo 和其他类的Invoke()方法。
13.2.2TypeView 示例
下面用一个短小的示例TypeView来说明Type类的一些功能,这个示例可以列出数据类
331 | |
型的所有成员。本例中主要介绍double 型的TypeView 用法,也可以修改该样列中的一行 | |
代
码,使用其他的数据类型。TypeView提供的信息要比在控制台窗口中显示的信息多得多,
所以我们将打破常规,在一个消息框中显示这些信息。运行double型的TypeView示例,
结
果如图12-1所示。
图12-1
该消息框显示了数据类型的名称、全名和命名空间,以及底层类型和基类的名称。然
后迭代该数据类型的所有公有实例成员,显示所声明类型的每个成员、成员的类型(方法、
字段等)以及成员的名称。声明类型是实际声明类型成员的类名(换言之,如果在
System.Double中定义或重载,该声明类型就是System.Double,如果成员继承了某个基类,
该声明类就是相关基类的名称)。
成员的信息,参数信息不能通过MemberInfo 对象来获得。为了获取该信息,需要引用 |
法。把TypeView编译为一个控制台应用程序,可以在控制台应用程序中显示消息框。但
是,使用消息框就意味着需要引用基类程序集System.Windows.Forms.dll,它包含System.Windows.Forms命名空间中的类,在这个命名空间中, 定义了我们需要的
MessageBox类。下面列出TypeView的代码。开始时需要添加两条using语句:
usingSystem;
usingSystem.Text;
usingSystem.Windows.Forms;
usingSystem.Reflection;
332
第12章反射
需要 System.Text 的原因是我们要使用StringBuilder 对象建立在消息框中显示的文本, |
含
两个静态方法和一个静态字段,StringBuilder的一个实例叫作OutputText,用于创建在
消息
框中显示的文本。Main方法和类的声明如下所示:
classMainClass
{
StaticStringBuilder OutputText = new StringBuilder(); static void Main()
{
//modify this line to retrieve details of any
//other data type
Typet = typeof(double);
AnalyzeType(t);
MessageBox.Show(OutputText.ToString(),"Analysis of type " + t.Name);
Console.ReadLine();
}
AnalyzeType(),从Type 对象中提取信息,并使用该信息建立输出文本。最后在消息框中 |
staticvoid AnalyzeType(Type t)
{
AddToOutput("TypeName: " + t.Name);
AddToOutput("FullName: " + t.FullName);
AddToOutput("Namespace:" + t.Namespace);
TypetBase = t.BaseType;
if(tBase != null)
{
AddToOutput("BaseType:" + tBase.Name);
}
TypetUnderlyingSystem = t.UnderlyingSystemType;
if(tUnderlyingSystem != null)
{
AddToOutput("UnderlyingSystem Type:" + tUnderlyingSystem.Name); } | |
AddToOutput("\nPUBLIC MEMBERS:"); | |
MemberInfo[] Members = t.GetMembers();
foreach(MemberInfo NextMember in Members)
333
第Ⅰ部分C#语言
{
AddToOutput(NextMember.DeclaringType+ " " +
NextMember.MemberType+ " " + NextMember.Name);
}
}
执行这个方法,仅需调用Type对象的各种属性,就可以获得我们需要的类型名称的信
息,再调用GetMembers()方法,获得一个MemberInfo对象数组,该数组用于显示每个
成
员的信息。注意这里使用了一个辅助方法AddToOutput(),该方法创建要在消息框中显示
的
文本:
staticvoid AddToOutput(string Text)
} { |
据,它也包含可以加载和执行程序集(假定该程序集是可执行的)的方法。与Type类一样,
Assembly类包含非常多的方法和属性,这里不可能逐一论述。下面仅介绍完成示例
WhatsNewAttributes所需要的方法和属性。
在使用Assembly实例做一些工作前,需要把相应的程序集加载到运行进程中。为此,
可以使用静态成员Assembly.Load()或Assembly.LoadFrom()。这两个方法的区别是Load()
的参数是程序集的名称,运行库会在各个位置上搜索该程序集,这些位置包括本地目录和
全局程序集高速缓存。而LoadFrom()的参数是程序集的完整路径名,不会在其他位置搜索
该程序集: | |
(@"C:\MyProjects\Software\SomeOtherAssembly");
这两个方法都有许多其他重载,它们提供了其他安全信息。加载了一个程序集后,就可以使用它的各种属性,例如查找它的全名:
stringname = assembly1.FullName;
1.查找在程序集中定义的类型
Assembly类的一个特性是可以获得在相应程序集中定义的所有类型的信息,只要调用
Assembly.GetTypes()方法,就可以返回一个包含所有类型信息的System.Type引用数组,然
后就可以按照上一节的方式处理这些Type引用了:
334
第12章反射
Type[]types = theAssembly.GetTypes();
foreach(TypedefinedType in types)
{
DoSomethingWith(definedType);
}
2. 查找定制特性 |
//assembly1 is an Assemblyobject
注意:
这是相当重要的。以前您可能想知道,在定义定制特性时,必须为它们编写类,为什么Microsoft没有更简单的语法。答案就在于此。定制特性与对象一样,加载了程序集后,就可以读取这些特性对象,查看它们的属性,并且调用它们的方法。
GetCustomAttributes()在用于获取程序集的特性时,有两个重载方法:如果在调用它时,除了程序集的引用外,没有指定其他参数,该方法就会返回为这个程序集定义的所有定制特性。当然,也可以通过指定第二个参数来调用它,第二个参数表示特性类的一个Type
对象,在这种情况下,GetCustomAttributes()就返回一个数组,该数组包含该特性类的所有
特性。 | |
注意,所有的特性都作为一般的Attribute 引用来获取。如果要调用为定制特性定义的任 | |
何方法或属性,就需要把这些引用显式转换为相关的定制特性类。调用
Assembly.GetCustomAttributes()的另一个重载方法,可以获得与给定数据类型相关的定制特
性信息,这次传递的是一个Type引用,它描述了要获取的任何相关特性的类型。另一方面,
如果要获得与方法、构造函数和字段等相关的特性,就需要调用GetCustomAttributes()方法,
该方法是类MethodInfo、ConstructorInfo和FieldInfo等的一个成员。
如果只需要给定类型的一个特性,就可以调用GetCustomAttribute()方法,它返回一个
Attribute对象。在WhatsNewAttributes示例中使用GetCustomAttribute()方法,是为了确定程
序集中是否有特性SupportsWhatsNew。为此,调用GetCustomAttributes(),传递对
WhatsNew |
AmbiguousMatchException:
AttributesupportsAttribute =
Attribute.GetCustomAttributes(assembly1,
typeof(SupportsWhatsNewAttribute));
335
第Ⅰ部分C#语言
13.2.4完成WhatsNewAttributes示例
现在已经有足够的知识来完成WhatsNewAttributes示例了。为该示例中的最后一个程
序集LookUpWhatsNew编写源代码,这部分应用程序是一个控制台应用程序,它需要引用
其他两个程序集WhatsNewAttributes 和VectorClass。这是一个命令行应用程序,但仍可 | |
以 | |
象前面的TypeView示例那样在消息框中显示结果,因为结果是许多文本,所以不能显示
在一个控制台窗口屏幕上。
这个文件的名称为LookUpWhatsNew.cs,编译它的命令是:
csc/reference:WhatsNewAttributes.dll /reference:VectorClass.dllLookUpWhatsNew.cs
在这个文件的源代码中,首先指定要使用的命名空间System.Text,因为需要使用一个
StringBuilder对象:
usingSystem;
usingSystem.Reflection;
usingSystem.Windows.Forms;
usingSystem.Text;
usingWrox.ProCSharp.VectorClass;
usingWrox.ProCSharp.WhatsNewAttributes;
namespaceWrox.ProCSharp.LookUpWhatsNew
{
类WhatsNewChecker 包含主程序入口和其他方法。我们定义的所有方法都在这个类 |
想编写这段代码,以免转移读者的注意力。因此,把backDateTo硬编码为日期2007年2
月1日。在下载这段代码时,很容易修改这个日期:
classWhatsNewChecker
{
staticStringBuilder outputText = new StringBuilder(1000); static DateTimebackDateTo = new DateTime(2007, 2, 1); static void Main()
{
AssemblytheAssembly = Assembly.Load("VectorClass");
AttributesupportsAttribute =
Attribute.GetCustomAttribute(
theAssembly,typeof(SupportsWhatsNewAttribute));
string Name = theAssembly.FullName; AddToMessage("Assembly: " + Name); if (supportsAttribute == null) | |
{
336
第12章反射
AddToMessage("This assembly does not support WhatsNew attributes");return;
}
else
AddToMessage("DefinedTypes:");
Type[]types = theAssembly.GetTypes();
foreach(TypedefinedType in types)
DisplayTypeInfo(theAssembly,definedType);
MessageBox.Show(outputText.ToString(),
"What\'sNew since " + backDateTo.ToLongDateString());
Console.ReadLine();
}
Main()方法首先加载VectorClass程序集,验证它是否真的用SupportsWhatsNew特性
来标记。我们知道,VectorClass应用了SupportsWhatsNew特性,虽然才编译了该程
序集, |
信息。最后,显示带有完整文本的消息框。DisplayTypeInfo()方法如下所示:
staticvoid DisplayTypeInfo(Assembly theAssembly, Type type)
{
//make sure we only pick out classes
if(!(type.IsClass))
{
return;
}
AddToMessage("\nclass" + type.Name);
Attribute[] attribs = Attribute.GetCustomAttributes(type);
if(attribs.Length == 0)
{
AddToMessage("Nochanges to this class\n");
} | |
foreach(Attribute attrib in attribs)
{
WriteAttributeInfo(attrib);
}
337
第Ⅰ部分C#语言
}
MethodInfo[] methods = type.GetMethods();
AddToMessage("CHANGESTO METHODS OF THIS CLASS:");
foreach(MethodInfo nextMethod in methods)
{
object[] attribs2 =
nextMethod.GetCustomAttributes(
typeof(LastModifiedAttribute),false);
if(attribs != null)
{
AddToMessage(
nextMethod.ReturnType+ " " + nextMethod.Name + "()");
foreach(Attribute nextAttrib in attribs2)
{ |
定LastModified特性只能应用于类或成员方法,如果该引用不是类(它可能是一个结构、
委
托或枚举),进行任何处理都是浪费时间。
接着使用Attribute.GetCustomAttributes()方法确定这个类是否有相关的LastModified
Attribute实例。如果有,就使用帮助方法WriteAttributeInfo()把它们的信息添加到输出
文本中。
最后使用Type.GetMethods()方法迭代这个数据类型的所有成员方法,然后对类的每个
方法进行相同的处理:检查每个方法是否有相关的LastModifiedAttribute实例,如果有,
用WriteAttributeInfo()显示方法它们。 | |
下面的代码显示了WriteAttributeInfo()方法,它负责确定为给定的LastModifiedAttribute | |
实例显示什么文本,注意这个方法的参数是一个Attribute引用,所以需要先把该引用转换
为LastModifiedAttribute引用。之后,就可以使用最初为这个特性定义的属性获取其参数。
在把该特性添加到要显示的文本中之前,应检查特性的日期是否是最近的:staticvoid WriteAttributeInfo(Attribute attrib)
{
LastModifiedAttributelastModifiedAttrib =
attribas LastModifiedAttribute;
if(lastModifiedAttrib == null)
{
return;
338
第12章反射
}
//check that date is in range
DateTimemodifiedDate = lastModifiedAttrib.DateModified;
if(modifiedDate < backDateTo)
{
AddToMessage(" MODIFIED: " + modifiedDate.ToLongDateString() + ":");return; |
lastModifiedAttrib.Issues);
}
}
最后,是辅助方法AddToMessage():
staticvoid AddToMessage(string message)
{
outputText.Append("\n"+ message);
}
}
}
运行这段代码,得到如图12-2所示的结果。
图12-2
339 | |
注意,在列出VectorClass 程序集中定义的类型时,实际上选择了两个类:Vector 和内 | |
嵌的VectorEnumerator类。还要注意,这段代码把backDateTo日期硬编码为2月1日,实
际上选择的是日期为2月14日的特性(添加集合支持的时间),而不是1月14日(添加
IFormattable接口的时间)。
13.3小结
本章没有介绍反射的全部内容,反射需要一整本书来讨论。我们只介绍了Type和
Assembly类,它们是访问反射所提供的扩展功能的主要入口。
另外,本章还探讨了反射的一个常用方面:定制特性。介绍了如何定义和应用自己的定制特性,以及如何在运行期间检索定制属性的信息。
第13章介绍异常和结构化的异常处理。__
Copyright © 2019- zhuinvhai.com 版权所有
违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com
本站由北京市万商天勤律师事务所王兴未律师提供法律服务