技术开发 频道

Visual C#中定义和使用自己的特性

    应用自定义特性

   
你现在已经知道在C#代码里,在一个类型声明之前,通过在方括号里使用特性的名字和参数就可以将其附加到目标类型上。

    在下面的代码里,把[DefectTrack]特性附加到一对类和一对方法上。

using System ; using MyAttributeClasses ; namespace SomeClassesToTest { [DefectTrack( "1377", "12/15/02", "David Tansey" ) ] [DefectTrack( "1363", "12/12/02", "Toni Feltman", Origin = "Coding: Unhandled Exception" ) ] public class SomeCustomPricingClass { public double GetAdjustedPrice( double tnPrice, double tnPctAdjust ) { return tnPrice + ( tnPrice * tnPctAdjust ) ; } [DefectTrack( "1351", "12/10/02", "David Tansey", Origin = "Specification: Missing Requirement", FixComment = "Added PriceIsValid( ) function" ) ] public bool PriceIsValid( double tnPrice ) { return tnPrice > 0.00 && tnPrice < 1000.00 ; } } [DefectTrack( "NEW", "12/12/02", "Mike Feltman" ) ] public class AnotherCustomClass { string cMyMessageString ; public AnotherCustomClass( ){ } [DefectTrack( "1399", "12/17/02", "David Tansey", Origin = "Analysis: Missing Requirement" ) ] public void SetMessage( string lcMessageString ) { this.cMyMessageString = lcMessageString ; } } }

    首先,需要确保你可以访问之前创建的自定义特性,所以需要添加这样一行代码,如下:

using MyAttributeClasses ;

    到此,你就可以使用自定义特性[DefectTrack]装饰或点缀你的类声明和方法了。

    SomeCustomPricingClass有两处地方用到了[DefectTrack]特性。第一个[DefectTrack]特性仅仅使用了三个定位参数,而第二个[DefectTrack]特性还包含了一个命名参数Origin的指定。

[DefectTrack( "1377", "12/15/02", "David Tansey" ) ] [DefectTrack( "1363", "12/12/02", "Toni Feltman", Origin = "Coding: Unhandled Exception" ) ] public class SomeCustomPricingClass {}

    PriceIsValid()方法也使用了自定义特性[DefectTrack],并且指定了两个命名参数Origin和FixComment。上述代码包含了[DefectTrack]特性几个额外的用途,你可以检测这些特性。

    一些读者可能会感到惊奇,因为对于源代码修改的信息可以通过使用注释这种传统的做法。.NET已经使用工具,通过在注释里使用XML块,把这些信息很好的组织起来。

    在源代码对应的位置,你可以很容易的看到你的注释。你可以通过文本,分析源代码里的注释,从而处理这些信息,但是这个过程是单调冗长的,并且很容易出现错误。.NET提供了工具来处理注释里的XML块,这样可以消除此类问题。

     使用自定义特性可以使你达到同样的效果,它同样提供了一种可以有效组织的方法,用于记录和处理这些信息,并且它还有一个额外的优势。考虑如下情况,当把源代码编译成二进制代码的时候,你是否已经丢失了代码的注释?毫无疑问,注释已经作为副产品,永远的从可执行代码里移出。相比之下,特性的值已经变成了元数据的一部分,永远的绑定到一个程序集里。在没有源代码的情况下,你依然可以访问这些注释信息。

    另外,在源代码里允许特性构造一个与当初在设计时值一样的实例。

    获取自定义特性的值

    到此,尽管你已经在类和方法上应用了自定义属性,但在实战中你还没有真正的看到它。不管你是否附加了特性,看起来好像什么事情也没有发生。但事实上,事情已经发生了变化,你完全不用理会我的话,你可以用MSIL反编译工具,打开一个包含使用了自定义特性类型的EXE或者DLL文件。MSIL反编译工具能使你看到在IL代码里你定义的特性和它的值。

     尽管通过反编译程序集,看到了特性的值,证明了它们的确存在,但是你仍然没有看到跟它们相关的行为。那么现在,你就可以使用反射API遍历一个程序集包含的类型,查询你自定义的特性,在应用了特性的类型上获取特性的值。

    考虑如下测试代码的一般的做法。程序加载指定的程序集,得到一个包含程序集中所有成员的数组,在它们中间,迭代寻找应用了[DefectTrack]特性的类。对于应用了[DefectTrack]特性的类,测试程序将在控制台上输出特性的值。对于类型中的方法,程序仍然采用了同样的步骤和迭代。这些循环采用它们的方式在整个程序集里“游走”。

using System ; using System.Reflection ; using MyAttributeClasses ; public class TestMyAttribute { public static void Main( ) { DisplayDefectTrack( "MyAttributes" ) ; Console.ReadLine(); } public static void DisplayDefectTrack( string lcAssembly ) { Assembly loAssembly = Assembly.Load( lcAssembly ) ; Type[ ] laTypes = loAssembly.GetTypes( ) ; foreach( Type loType in laTypes ) { Console.WriteLine("*======================*" ) ; Console.WriteLine( "TYPE:\t" + loType.ToString( ) ) ; Console.WriteLine( "*=====================*" ) ; object[ ] laAttributes = loType.GetCustomAttributes( typeof( DefectTrackAttribute ), false ) ; if( laAttributes.Length > 0 ) Console.WriteLine( "\nMod/Fix Log:" ) ; foreach( Attribute loAtt in laAttributes ) { DefectTrackAttribute loDefectTrack = (DefectTrackAttribute)loAtt ; Console.WriteLine( "----------------------" ) ; Console.WriteLine( "Defect ID:\t" + loDefectTrack.DefectID ) ; Console.WriteLine( "Date:\t\t" + loDefectTrack.ModificationDate ) ; Console.WriteLine( "Developer ID:\t" + loDefectTrack.DeveloperID ) ; Console.WriteLine( "Origin:\t\t" + loDefectTrack.Origin ) ; Console.WriteLine( "Comment:\n" + loDefectTrack.FixComment ) ; } MethodInfo[ ] laMethods = loType.GetMethods( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly ) ; if( laMethods.Length > 0 ) { Console.WriteLine( "\nMethods: " ) ; Console.WriteLine( "----------------------" ) ; } foreach( MethodInfo loMethod in laMethods ) { Console.WriteLine( "\n\t" + loMethod.ToString( ) ) ; object[ ] laMethodAttributes = loMethod.GetCustomAttributes( typeof( DefectTrackAttribute ), false ) ; if( laMethodAttributes.Length > 0 ) Console.WriteLine( "\n\t\tMod/Fix Log:" ) ; foreach( Attribute loAtt in laMethodAttributes ) { DefectTrackAttribute loDefectTrack = (DefectTrackAttribute)loAtt ; Console.WriteLine( "\t\t----------------" ) ; Console.WriteLine( "\t\tDefect ID:\t" + loDefectTrack.DefectID ) ; Console.WriteLine( "\t\tDeveloper ID:\t" + loDefectTrack.DeveloperID ) ; Console.WriteLine( "\t\tOrigin:\t\t" + loDefectTrack.Origin ) ; Console.WriteLine( "\t\tComment:\n\t\t" + loDefectTrack.FixComment ) ; } } Console.WriteLine( "\n\n" ) ; } } }

    让我们来看一下比较重要的几行代码。DisplayDefectTrack()方法的第一行代码和第二行代码得到了加载指定程序集的一个引用并且得到了包含在该程序集中类型的一个数组。

Assembly loAssembly = Assembly.Load( lcAssembly ) ; Type[ ] laTypes = loAssembly.GetTypes( ) ;

    使用foreach语句在程序集中的每一个类型上迭代。在控制台上输出当前类型的名称,并使用如下的语句查询当前类型,获取有关[DefectTrack]特性的一个数组。

object[ ] laAttributes = loType.GetCustomAttributes( typeof( DefectTrackAttribute ), false ) ;

    你需要在GetCustomAttributes方法上指定typeof(DefectTrackAttribute) 参数,以限制仅仅返回你创建的自定义特性。第二个参数false指定是否搜索该成员的继承链以查找这些自定义特性。

    使用foreach语句迭代自定义特性数组,并把它们(自定义特性)的值输出到控制台上。你应该认识到第一个foreach语句块会创建一个新的变量,并且对当前的特性作类型转化。

DefectTrackAttribute loDefectTrack = (DefectTrackAttribute)loAtt ;

    这一条语句为什么是必须的呢?GetCustomAttributes()方法会返回一个object数组,你为了访问自定义特性的值,所以必须把这些引用转化为它们真正的具体类的引用。转化完以后,你就可以使用这些特性并且可以把特性的值输出到控制台上。

    因为你可以在任意的类和方法上应用特性,因此程序需要调用当前类型上的方法GetMethods()。

MethodInfo[ ] laMethods = loType.GetMethods( BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly ) ;

    在这个例子里,我给GetMethods()方法传递了一些BindingFlags枚举值。组合使用这三个枚举值,限制仅仅返回在当前的类中直接定义的方法。在这个例子里,之所以这样做,是因为我想限制输出的数量,但是在实际当中,你可能并不需要这样做,因为开发人员可能会在一个重写的方法上应用[DefectTrack]特性。而我的实现代码并没有捕捉应用在这些方法上的特性。

    剩下的代码,从本质上来说,对每一个方法以及每一个类,都在做相同的操作。都是在每一个方法上寻找是否应用了[DefectTrack]特性,如果应用了,就把特性的值输出到控制台上。

    总结

    在这里,我只是利用一个简单的例子,介绍了开发者如何使用.NET特性提高开发进程。自定义特性有点类似于XML,它最大的好处不在于“它做了什么”,它真正最大的好处在于“你可以用它做什么”。这个是真正无止境的,由于自定义特性本身具有开放的特性,这使得它可以拥有更多新颖的用途。

0
相关文章