现在的位置: 首页 > 综合 > 正文

(翻译)在C#中定义和使用自己的特性

2011年06月15日 ⁄ 综合 ⁄ 共 5120字 ⁄ 字号 评论关闭

原文地址

作者:David Tansey
翻译:today


复杂的,面向组件的业务开发,期待现代的软件开发工程师们具备更多的弹性设计,而不是过去的方法设计。微软的.NET框架通过众所周知的声明式编程,广泛的使用特性来附加额外的功能。在软件系统里,特性可以增强系统的弹性,这是因为,特性使功能的松耦合得到了增强。所以,你可以定制自己的特性类,然后根据你自己的意图,合理的使用这些具有松耦合功效的特性。

 

使用.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的工具箱中,它们(自定义控件)已经使用了一系列特性,用于说明在属性表单中如何处理自定义控件。Table1列举并描述了在属性表单中用到的4种标准的.NET特性。

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

Attribute

Description

Designer

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

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 :Attribute
    
{
        
private 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 ModificationDate
        
{
            
get 
            
{
              
return dModificationDate.ToShortDateString() ; 
            }
 
        }


        
public string DeveloperID
        

            
get return cDeveloperID ; }
        }


        
public string Origin
        
{
            
get return cDefectOrigin ; }
            
set { cDefectOrigin = value ; } 
        }


        
public string FixComment
        

抱歉!评论已关闭.