知识大全 在VisualC#中定义和使用自己的特性

Posted

篇首语:生命之灯因热情而点燃,生命之舟因拼搏而前行。本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识大全 在VisualC#中定义和使用自己的特性相关的知识,希望对你有一定的参考价值。

在VisualC#中定义和使用自己的特性  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!

复杂的 面向组件的业务开发 期待现代的软件开发工程师们具备更多的弹性设计 而不是过去的方法设计 微软框架通过众所周知的声明式编程 广泛的使用特性来附加额外的功能 在软件系统里 特性可以增强系统的弹性 这是因为 特性使功能的松耦合得到了增强 所以 你可以定制自己的特性类 然后根据你自己的意图 合理的使用这些具有松耦合功效的特性   使用 NET框架编写Windows程序 在很多方面已经变得很简单 在许多情况下 NET框架使用 NET编译器在编译时绑定到程序集的元数据 使灵活的程序设计变得更容易 事实上 对于 NET而言 使用内嵌的元数据把我们从DLL地狱解脱出来是可能的   值得庆幸的是 NET框架的设计者们并没有选择把这些元数据优雅的隐藏起来 设计者们把反射API给予了我们 通过反射 一个 NET程序可以通过编程查看这个元数据 一个程序可以反射出包含在特定程序集内任意的东西 或者说是包含在其内的所有的类型和成员   把元数据绑定到可执行的程序集里 提供了许多优势 这使得 NET程序集 完全可以自我描述 还允许开发者跨语言共享组件 去除了头文件的需要 (这些头文件会由于相关的实现代码而过期 )  关于 NET元数据所有积极的消息 看起来很难相信 它好像什么也没有 仅仅是个谎言 但是 它确实是存在的 在 NET里 你可以创建自己特定程序的元数据 并且可以把这些元数据应用到你可以想象到的地方   开发者通过使用自定义特性 可以定义他们自己特定程序的元数据 因为这些特性的值将变成另一部分元数据 绑定到一个程序集里 所以这些自定义特性的值可以被反射API检查到并且可以被使用   我们经常提到一个类的属性 这些属性的值可以作为特性来使用 那么属性和自定义特性真正的区别在哪里呢?  通过这篇文章 你将学会如何定制特性 如何把特性应用到你的源代码类和方法上 以及如何使用反射API获取和使用这些特性的值   公共语言运行时是如何使用特性的?  在你开始考虑如何使用你自己定义的特性类之前 让我们查看一些标准的特性 这些已经在公共语言运行时有用到   [WebMethod]特性提供了一个简单的例子 它可以使WebService派生的子类中任意公共的方法转化成Web Service暴露方法的一部分 而这一切 仅仅通过把[WebMethod]附加到方法的定义上就可以做到 public class SomeWebService : System Web Services WebService[WebMethod]public DataSet GetDailySales()//处理请求的代码  你只要把[WebMethod]特性添加到一个方法上 NET就会在后台为你处理其它所有的事情   在给定的方法上使用[Conditional]特性 那么此方法是否可调用将取决于指定的预处理标识符是否被定义 举个例子 看如下的代码 public class SomeClass[Conditional( DEBUG )]public void UnitTest()//单元测试代码  这段代码说明 该类的方法UnitTest()是否有效 将取决于预处理标识符 DEBUG 是否被定义(译注 在编译调试版本时 DEBUG常数已经被定义) 我们可能更感兴趣的是 使用[Conditional]后真正发生了什么 当条件失效时 编译器将会停止所有对该方法的调用 相比有同样功能的预处理指令#if #endif 此方法显得更简洁 而且 使用这项功能 我们不需要多做任何事情   特性使用了定位参数和命名参数 在使用了[Conditional]特性的例子中 特定的符号就是定位参数 定位参数是强制性的 你必须提供   让我们回到使用了[WebMethod]特性的例子 来看一下命名参数 这个特性有一个Description的命名参数 可以像下面这样使用 [WebMethod(Description = Sales volume )]

  命名参数是可选择的 参数的值要紧跟着写在参数名字的后面 如果存在定位参数 那么你需要先书写定位参数 然后在定位参数的后面书写命名参数   我将会在文章的后面讲述更多关于定位参数和命名参数的内容 这将在我向你展示如何创建和使用你自己的特性类时提到   特性可用于运行时 设计时  在这篇文章里 我提供的都是与运行时的行为相关的例子 但是二进制文件(程序集)并不只是用于运行时 在 NET里 你所定义的元数据也不只是局限于运行时 相反 当你编译成程序集后 在任何时候你都可以查阅这些元数据   考虑在设计时 使用元数据的一些可能的情况 在Visual Studio Net里 使用IDE可以构建工具(使用 NET语言) 方便开发和设计(向导 构建器等等) 这样 一个模块的运行时的环境(如 IDE工具)就成了另一个模块的设计时环境(被开发的源代码) 这里提供了一个使用定制特性很好的例子 IDE工具将会反射你编写的类和类型 然后遵照你的代码行事 不幸的是 由于没有IDE工具的代码 探究这样的例子 已经超出了该文章所阐述的范围   标准的 NET特性包含了一个类似的例子 当一个开发者创建自定义控件并把它放到Visual Studio Net IDE的工具箱中 它们(自定义控件)已经使用了一系列特性 用于说明在属性表单中如何处理自定义控件 Table 列举并描述了在属性表单中用到的 种标准的 NET特性

  Table : 在Visual Studio NET IDE里设计时属性表单用到的标准的 NET特性

  Attribute

  Description

  Designer

  指定用于为组件实现设计时服务的类。WinGWIt

  DefaultProperty

  指定在属性表单中,组件的默认的属性。

  Category

  指定在属性表单中,属性的类别。

  Description

  指定在属性表单中,有关属性的描述。

  这些与表单相关的特性,让我们认识到,可以在设计时使用特性以及它们的值,就像在运行时一样。

  自定义特性vs.类的属性  在特性和类的属性之间存在着明显相似的地方。这给我们何时,何处应该使用自定义特性带来了困惑。开发者们通常引用一个类的属性,并把属性的值作为自己“特性”,那么属性和特性之间真正的区别在哪里呢?  当你定义特性的时候,它和属性没有根本的区别,使用时,可以以相同的方式把它附加到程序集不同的类型上,而不仅仅在类上使用。Table2列举了可以应用特性的所有程序集类型。  Table 2:可以应用特性的所有程序集类型。

  Type

  Assembly

  Class

  Delegate

  Enum

  Event

  Interface

  Method

  Module

  Parameter

  Constructor

  Field

  Property

  ReturnValue

  Structure

  让我们从清单中挑选一个作为例子。你可以在参数上应用特性,这看起来很微小,好像是在给参数添加属性?其实,这是一个新颖的,非常不错的主意,因为你不会用类的属性做这件事。这里也突出了特性和属性之间很大的不同之处,因为属性仅仅是类的一个成员而已。它们不能与一个参数,或者清单中列举的其他类型关联起来,当然,这要把类排除在外。  在另外的方面,类的属性被限制在运行的环境下,而特性却没有被限制。通过定义,一个属性就依赖于特定的类,这个属性仅仅可以通过类的实例访问,或者通过该类派生类的实例访问。另一方面,特性却可以应用到任何地方。在assembly类型上应用特性,以检验是否与自定义特性中的相匹配,这对于assembly类型来说,是最适合的了。在下一部分,我将更多的讨论自定义特性类中的ValidOn属性。在面向组件的开发中,这是非常有用的,因为特性的这个特征将更加促进松耦合。  特性和属性之间另外的一个不同的地方将涉及到它们各自存储的值。属性成员的值是一个实例化的值,在运行时,是可以被改变的。而特性的值,是在设计时(在源代码里)设定,然后直接把这些特性的值编译成元数据保存到程序集里。之后,你将不能改变这些特性的值。实际上,你已经把这些特性的值,变成硬编码的、只读的数据。  考虑一下,你应用特性的时候。举个例子,在一个类定义的时候,给其附加了一个特性,那么该类的每一个实例都会拥有相同的分配给此特性值,而不论你实例化该类的多少个实例。你不能把特性附加到一个类的实例上,你只可以在类型/类的定义上应用特性。  创建一个自定义特性类  现在,综合以上的描述,我们将演示一个更实际的实现过程。让我们创建一个自定义特性类。该特性会保存一些关于代码修改的跟踪信息,在源代码里,这些都将作为注释。在这个例子里,我们将仅仅记录一些条目:缺陷id,开发者id,修改的日期,导致缺陷的原因,以及有关修正的注释。为了保持例子足够的简单,我们将关注于如何创建一个自定义特性类(DefectTrackAttribute),而该特性类仅被用于类和方法上。  DefectTrackAttribute定义的代码如下: using System;namespace MyAttributeClasses[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple = true)]public class DefectTrackAttribute :Attributeprivate string cDefectID ;private DateTime dModificationDate ;private string cDeveloperID ;private string cDefectOrigin ;private string cFixComment ;public DefectTrackAttribute ()public DefectTrackAttribute( string lcDefectID, string lcModificationDate, string lcDeveloperID )this.cDefectID = lcDefectID ;this.dModificationDate = System.DateTime.Parse( lcModificationDate ) ;this.cDeveloperID = lcDeveloperID ;public string DefectID get return cDefectID ; public string ModificationDateget return dModificationDate.ToShortDateString() ; public string DeveloperID get return cDeveloperID ; public string Originget return cDefectOrigin ; set cDefectOrigin = value ; public string FixCommentget return cFixComment ; set cFixComment = value ;

  如果你之前没有接触过特性,那么你将对下面的代码有点陌生。

[AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple = true)]  这一行代码,把特性[AttributeUsage]附加到特性类的定义上。方括号的语法表明一个特性的构造器被调用。所以,特性类也可以拥有它们自己的特性,这看起来可能有点混淆,但是随着我给你展示可以用特性类来做些什么,你对它的认识,将会越来越清晰。  [AttributeUsage]特性具有一个定位参数和两个命名参数。定位参数指定了特性类将被用于何种类型,定位参数的值是枚举AttributeTargets的组合。在我的例子里,我仅仅把特性类应用在类和方法上,所以通过组合两个AttributeTargets的值的满足了我的要求。  [AttributeUsage]特性的第一个命名参数是AllowMultiple,该参数指定了是否可以在同一个类型上应用多次(你所定义的)特性类。默认值是false,即不允许应用多次。但是,根据这个例子的实际情况,你将会在某一类型上不止一次的应用特性(DefectTrackAttribute),所以应该使用[AttributeUsage]的命名参数AllowMultiple,并将其设置为true。这是因为,一个特定的类和方法在其生命周期里会经历多次修订,所以你需要使用[DefectTrackAttribute]特性记录每一次变化。  [AttributeUsage]特性的第二个命名参数是Inherited,它指定了派生类(使用此特性类的子类)是否继承此特性。我使用了此参数的默认的值false。因为我使用的是默认值,所以也就不需要指定该命名参数。为什么不需要继承呢?我想获取源代码的修改信息是跟每一个具体的类和方法有关的。如果把Inherited设为true,那么开发者将会混淆一个类的[DefectTrackAttribute]特性,无法辨别[DefectTrackAttribute]特性是它自己的还是从父类继承的。  上面的代码展示了特性类(DefectTrackAttribute)的定义。它继承于System.Attribute,事实上,所有的特性均直接或间接的继承于System.Attribute。  上面的代码里,还定义了特性的5个私有的字段,这些字段均用于保存与特性相关的值。  在我们特性类中第一个方法是构造器,它是带有3个参数的签名。构造器的参数对于特性类而言,就是这个特性的定位参数,这些参数是强制性的。如果你愿意,你可以重载构造器,使其可以拥有更多的有关定位参数配置的选择。  我们的特性类中剩下的部分就是一些公有属性的声明,这些属性与类中的私有字段相对应。当你查阅元数据的时候,你可以使用这些属性访问该特性的值。需要说明的是,对应定位参数的属性没有set语句,只有get语句。这就导致了这些属性是只读的,这也与它们是定位参数而不是命名参数的含义相一致。应用自定义特性  你现在已经知道在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 SomeCustomPricingClasspublic 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 AnotherCustomClassstring 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]特性几个额外的用途,你可以检测这些特性。

  一些读者可能会感到惊奇,因为对于源代码修改的信息可以通过使用注释这种传统的做法已经使用工具,通过在注释里使用XML块,把这些信息很好的组织起来。  在源代码对应的位置,你可以很容易的看到你的注释。你可以通过文本,分析源代码里的注释,从而处理这些信息,但是这个过程是单调冗长的,并且很容易出现错误。.NET提供了工具来处理注释里的XML块,这样可以消除此类问题。  使用自定义特性可以使你达到同样的效果,它同样提供了一种可以有效组织的方法,用于记录和处理这些信息,并且它还有一个额外的优势。考虑如下情况,当把源代码编译成二进制代码的时候,你是否已经丢失了代码的注释?毫无疑问,注释已经作为副产品,永远的从可执行代码里移出。相比之下,特性的值已经变成了元数据的一部分,永远的绑定到一个程序集里。在没有源代码的情况下,你依然可以访问这些注释信息。  另外,在源代码里允许特性构造一个与当初在设计时值一样的实例。  获取自定义特性的值  到此,尽管你已经在类和方法上应用了自定义属性,但在实战中你还没有真正的看到它。不管你是否附加了特性,看起来好像什么事情也没有发生。但事实上,事情已经发生了变化,你完全不用理会我的话,你可以用MSIL反编译工具,打开一个包含使用了自定义特性类型的EXE或者DLL文件。MSIL反编译工具能使你看到在IL代码里你定义的特性和它的值。图一是使用ILDASM工具,打开本文中例子编译的EXE文件所看到的。 图一:C#特性  尽管通过反编译程序集,看到了特性的值,证明了它们的确存在,但是你仍然没有看到跟它们相关的行为。那么现在,你就可以使用反射API遍历一个程序集包含的类型,查询你自定义的特性,在应用了特性的类型上获取特性的值。  考虑如下测试代码的一般的做法。程序加载指定的程序集,得到一个包含程序集中所有成员的数组,在它们中间,迭代寻找应用了[DefectTrack]特性的类。对于应用了[DefectTrack]特性的类,测试程序将在控制台上输出特性的值。对于类型中的方法,程序仍然采用了同样的步骤和迭代。这些循环采用它们的方式在整个程序集里“游走”。 using System ;using System.Reflection ;using MyAttributeClasses ;public class TestMyAttributepublic 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,它最大的好处不在于“它做了什么”,它真正最大的好处在于“你可以用它做什么”。这个是真正无止境的,由于自定义特性本身具有开放的特性,这使得它可以拥有更多新颖的用途。 cha138/Article/program/net/201311/12373

相关参考

知识大全 9i新特性之——在线表格重定义研究1

  前言     在Oraclei出现之前你只能通过MOVE或导出和导入的方式来进行表格重定义因此表格重定义的过程可能相当漫长或者说是一个离线过程在此期间应用程序对该表的操作将失败特别是大型

知识大全 在VisualC#中使用XML指南之读取XML

在VisualC#中使用XML指南之读取XML  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!对于X

用电性质的定义是什么?

  用电性质指用电负荷具有的电特性,用电负荷的重要程度,用电负荷的用电时间、场合、目的和允许停电时间等。用电性质不同对供电质量的要求和影响不同,在电网用电负荷曲线中所处的位置也有差异。  用电负荷的电

用电性质的定义是什么?

  用电性质指用电负荷具有的电特性,用电负荷的重要程度,用电负荷的用电时间、场合、目的和允许停电时间等。用电性质不同对供电质量的要求和影响不同,在电网用电负荷曲线中所处的位置也有差异。  用电负荷的电

知识大全 用VisualC#中轻松浏览数据库记录

用VisualC#中轻松浏览数据库记录  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  用Delp

知识大全 编程中Visual C#常用的快捷键

编程中VisualC#常用的快捷键  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!  .F转到定义S

知识大全 9i新特性之——在线表格重定义研究4

  创建我们需要重新定义的中间表这个是一个分区表以后我们将把原表的所有数据在线转移到该表上来  SQL>createtableint_test(aintbint)partitionbyrange

知识大全 Visual C# 3.0 新特性概览

VisualC#3.0新特性概览  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!在发布VisualS

知识大全 9i新特性之——在线表格重定义研究5

  所有的工作都准备完成我们执行重构完成的过程这个过程将执行表的交换  SQL>executeDBMS_REDEFINITIONFINISH_REDEF_TABLE(MYTESTTESTINT_

知识大全 VisualC#多线程参数传递浅析

VisualC#多线程参数传递浅析  以下文字资料是由(全榜网网www.cha138.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!我们在写Remot