C#中的定制特性(Attributes)

介绍

Attributes是一种新的描述信息,我们既可以使用attributes来定义设计期信息(例如:帮助文件、文档的URL),还可能用attributes定义运行时信息(例如:使XML中的元素的成员字段关联起来)。我们也可以用attributes来创建一个“自描述”的组件。这篇指南中我们将明白怎么创建属性并将其绑定到各种语言元素上,另外我们怎样在运行环境下获取到attributes的一些信息。

定义

MSDN中做如下定义:

“An attribute is a piece of additionaldeclarative information that is specified for a declaration.”

使用预定义Attributes

在C#中已有一小组预定义的attributes,在我们学习怎样创建自定义attributes前,先来了解下在我们的代码中使用那些预定义的attributes。

using System;

public class AnyClass

{

[Obsolete(“Don’t use method, use new method”,true)]

static void Old(){ }

static void New(){ }

public static void Main()

{

Old();

}

}

仔细看下该实例,在该实例中我们用到了“Obsolete” attribute,它标记了一个不该再被使用的语言元素(这里的元素为方法),该属性的第一个参数是string类型,它解释为什么该元素被荒弃,以及我们该使用什么元素来代替它,实际中,我们可以书写任何其它文本来代替这段文本。第二个参数是告诉编译器把依然使用这个标识的元素视为一种错误,这就意味着编译器会因此而产生一个警告。

当我们试图编译上面的程序时,会得到如下错误:

AnyClass.Old() ‘is obsolete: ’Don’t use old method, use new method’

开发自定义Attributes

现在我们即将了解怎么开发自定义的attributes。在C#中,attribute类都派生于System.Attribute类。

using System;

public classHelpAttribute: Attribute

{

}

这样就已经创建了一个自定义attribute。现在就可以用它来装饰我们的类了,就像我们使用obsolete attribute一样。

[Help()]

public classAnyClass

{

}

注意:按惯例是用“Attribute”作为attribute类名的后缀,然而,当把attribute绑定到某语言元素时,是不包含“Attribute”后缀的。编译器首在System.Attribute的继承类中查找该attribute,如果没有找到,编译器会把“Attribute”追加到该attribute的名字后面,然后查找它。

迄今为止,该attribute没有任何用处。为了使它有点用处,我们在它里面加点东西。

using System;

public class HelpAttribute: Attribute

{

public HelpAttribute(String Descrition_in)

{

this.description=Description_in;

}

protected String description;

public String Description

{

get { return this.description; }

}

}

[Help(“this is a do-nothing class”)]

public class AnyClass

{

}

在上面的例子中,我们在attribute类中添加了一个属性,在最后一节中,将以运行时查询该属性。

定义或控制自定义Attribute的用法

AttributeUsage类是另一预定义类(注:attribute类本身用这个attributeSystem.AttributeUsage来标记),它将帮助我们控制我们自定义attribute的用法,这就是,我们能为自定义的attribute类定义attributes。

它描述了一个自定义attribute类能被怎样使用。

AttributeUsage提供三个属性,我们能将它们放置到我们自定义attribute类上,第一个特性是:

ValidOn

通过这个属性,我们能指定我们的自定义attribute可以放置在哪些语言元素之上。这组我们能把自定义attribute类放置其上的语言元素被放在枚举器AttributeTargets中。我们可以使用bitwise(它的意思可这么用:[AttributeUsage((AttributeTargets)4,AllowMultiple=false,Inherited=false)],4代表“Class”元素,其它诸如1代表“assembly”,16383代表“all”等等)或者“.”操作符绑定几个AttributeTargets值。(注:默认值为AttributeTargets.All)

AllowMultiple

该属性标识我们的自定义attribute能在同一语言元素上使用多次。(注:该属性为bool类型,默认值为false,意思是该自定义attribute在同一语言元素上只能使用一次)

Inherited

我们可以使用该属性来控制我们的自定义attribute类的继承规则。该属性标识自定义attribute是否可以由派生类继承。(该属性为bool类型,默认值为false,意思是不能继承)

以下的例子是将AttributeUsageattribute放置在help attribute上并在它的帮助下控制help attribute的用法。

using System;

[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]

public class HelpAttribute: Attribute

{

public HelpAttribute(String Description_in)

{

this.description=Description_in;

}

protected String description;

public String Description

{

get { return this.description;}

}

}

首先注意AttributeTargets.Class,它规定这个help attribute只能放置在语言元素“class”之上。这就意味着,下面的代码将会产生一个错误。

AnyClass.cs: Attribute ‘Help’ is not valid on this declaration type.

It is valid on ‘class’ declarations only.

现在把它绑定到方法:

[Help(“this is a do-nothing class”)]

public class AnyClass

{

[Help(“this is a do-nothing method”)]  //error

public void AnyMethod()

{    }

}

可以使用AttributeTargets.All来允许Help attribute可以放置在任何定义的语言元素上,那些可能的语言元素如下:

Assembly、Module、Class、Struct、Enum、Constructor、Method、Property、Field、Event、Interface、Parameter、Delegate

All=Assembly | Module | Class | Struct | Enum | Constructor | Method |Property | Field | Event | Interface | Parameter | Delegate,

ClassMembers=Class | Struct | Enum | Constructor | Method | Property |Field | Event | Delegate | Interface

考虑AllowMultiple=false,这就规定该attribute不能在同一语言元素上放置多次。

[Help(“thisis a do-nothing class”)]

[Help(“itcontains a do-nothing method”)]

publicclass AnyClass

{

[Help(“thisis a do-nothing method”)]   //error

publicvoid AnyMethos()

{   }

}

它产生了一个编译错误:

AnyClass:Duplicate ‘Help’ attribute

现在讨论最后一个属性”Inherited”,该属性指出当把该attribute放置于一个基类之上,是否派生类也继承了该attribute。如果绑定至某个attribute类的”Inherited”被设为true,那么该attribute就会被继承,然而如果绑定至某个attribute类的”Inherited”被设为false或者没有定义,那么该attribute就不会被继承。

假设有如下类关系:

[Help(“BaseClass”)]

public class Base

{

}

public class Derive: Base

{

}

这里有四种可能的绑定:

[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]

[AttributeUsage(AttributeTargets.Class,AllowMultiple=true,Inherited=false)]

[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=true)]

[AttributeUsage(AttributeTargets.Class,AllowMultiple=true,Inherited=true)]

第一种情况

如果查询(在后面将了解如何在运行时来查询attribute)派生类中的Help attribute,将不可能查询到,因为”Inherited”被设为false。

第二种情况

这种情况没有什么不同,因为其”Inherited”也被设为false。

第三种情况

为了解释第三种和第四种情况,也为派生类绑定同一attribute。

[Help(“BaseClass”)]

public class Base

{         }

[Help(“DeriveClass”)]

public class Derive: Base

{         }

现在我们查询相关的help attribute,我们将仅仅可以得到派生类的attribute,为什么这样,是因为helpattribute虽然允许被继承,但不能多次在同一语言上使用,所以基类中的help attribute被派生类的help attribute重写了。

第四种情况

在这种情况中,当我们查询派生类的helpattribute时,我们可以得到两个attribute,当然是因为help attribute既允许被继承,又允许在同一语言元素上多次使用的结果。

注意:AttributeUsageattribute仅应用在那种是System.Attribute派生的attribute类,而且绑定值在attribute类的AllowMultiple和Inherited均为false上才是有效的。

可选参数vs.命名参数

可选参数是attribute类构造函数的参数,它们是强制的,必须每次在attribute绑定至某语言元素时提供一个值。而另一方面,命名参数倒是真正的可选参数,不是在attribute构造函数的参数。

为了更加详细的解释,让我们在Help类中添加另外的属性。

[AttributeUsage(AttributeTargets.Class,AllowMultiple=false,Inherited=false)]

public class HelpAttribute: Attribute

{

Public HelpAttribute(String Description_in)

{

this.description=Description_in;

this.verion=”No version is defined for this class”;

}

protected String description;

public String Description

{

get { return this.description; }

}

protected String version;

public String Version

{

get { return this.version; }

//if we ever want our attribute user to set this property,

//we must specify set method for it

set { this.version=value; }

}

}

[Help(“This is Class1”)]

public class Class1

{

}

[Help(“This is Class2”,Version=”1.0”)]

publicclass Class2

{          }

[Help(“This is Class3”,Version=”2.0”,Description=”This is do-nothingclass”)]

public class class3

{

}

当我们在Class1中查询Help attribute的属性,我们将得到:

Help.Description:This is Class1\

Help.Version: No Version is defined for this class

因为我们没有为Version这个属性定义任何值,所以在构造函数中设定的值被我们查询出来。如果没有定义任何值,那么就会赋一个该类型的默认值(例如:int型,默认值是0)

查询Class2的结果是:

Help.Description: This is Class2

Help.Version: 1.0

我们不能为了可选参数而使用多个构造函数,应该用已命名参数来代替。我们之所以称它们为已命名的,是因为当我们在构造函数为它们提供值时,我们必须命名它们。例如,在第二个类中,我们如下定义Help。

[Help(“This is Class2”,Version=”1.0”)]

在AttributeUsage例子中,参数”ValidOn”是可选参数,而”Inherited”和”AllowMultiple”是命名参数。

注意:为了在attribute的构造函数中设定命名参数的值,我们必须为相应的属性提供一个set方法,否则会引起编译期错误:

‘Version’: Named attribute argument can’t be a read only property

现在,我们在Class3中查找Help attribute及其属性会产生跟上面提到的相同的编译期错误。

‘Desciption’: Named attribute argument can’t be a read only property

现在修改Help类,为属性”Description”加一个set方法。现在的输出就是:

Help.Description: This is do-nothing class

Help.Version: 2.0

在屏幕后面究竟发生了什么呢?首先带有可选参数的构造函数被调用,然后,每个命名参数的set方法被调用,在构造函数中赋给命名参数的值被set方法所覆写。

参数类型

一个attribute类的参数类型被限定在如下类型中:

bool、byte、char、double、float、int、long、short、string、System.Type、object

An enum type,provided that it and any types in which it is nested are publicly accessible. Aone-dimensional array involving any of the types listed above

Attribute标记

假设,我们想把Help attribute绑定至元素assembly。第一个问题是我们要把Helpattribute放在哪儿才能让编译器确定该attribute是绑定至整个assembly呢?考虑另一种情况,我们想把attribute绑定至一个方法的返回类型上,怎样才能让编译器确定我们是把attribute绑定至方法的返回类型上,而不是整个方法呢?

为了解决诸如些类的含糊问题,我们使用attribute标识符,有了它的帮助,我们就可以确切地申明我们把attribute绑定至哪一个语言元素。

例如:

[assembly:Help(“this a do-nothing assembly”)]

这个在Help attribute前的assembly标识符确切地告诉编译器,该attribute被绑定至整个assembly。可能的标识符有:

Assembly、module、type、method、property、event、field、param、return

在运行时查询Attributes

为了查询一语言元素上绑定的attribute,我们必须使用反射。反射有能力在运行时发现类型信息。

我们可以使用.net FrameworkReflection APIs 通过对整个assembly元数据的迭代,列举出assembly中所有已定义的类、类型、还有方法。

记住那旧的Help attribute和AnyClass类。

using System;

using System.Reflection;

using System.Diagnostics;

//attaching Help attribute to entire assembly

[assembly:Help(“This Assembly demonstrates custom attributes creation and their run-time query.”)]

//ourcustom attribute class

publicclass HelpAttribute: Attribute

{

publicHelpAttribute(String Description_in)

{

//TODO:Add constructor logic here

this.description=Description_in;

}

protected String description;

publicString Description

{

get { return this.description; }

}

}

//attachingHelp attribute to our AnyClass

[HelpString(“This is a do-nothing Class.”)]

publicclass AnyClass

{

//attaching Help attribute to our AnyMethod

[Help(“Thisis a do-nothing Method”)]

publicvoid AnyMethod()

{          }

//attaching Help attribute to our AnyInt Field

[Help(“Thisis any Integer.”)]

publicint AnyInt;

}

在接下来的代码中,我们先得到当前的进程名称,然后用Assembly类中的LoadForm()方法加载程序集,再有用GetCustomAttributes()方法得到被绑定至当前程序集的自定义attributes,接下来用foreach语句遍历所有attributes并试图把每个attribute转型为Help attribute(即将转型的对象使用as关键字有一个优点,就是当转型不合法时,将不需担心会抛出异常,代之以空值(null)作为结果),接下来的一行就是检查转型是否有效,及是不是为空,跟着就显示Help attribute的”Description”属性。

classQueryApp

{

publicstatic void Main()

{

HelpAttribute HelpAtr;

//Querying Assembly Attributes

String assemblyName;

Process p=Process.GetCurrentProcess();

assemblyName=p.ProcessName+”.exe”;

Assembly a=Assembly.LoadFrom(assemblyName);

foreach(Attributeattr in a.GetCustomAttributes(true))

{

HelpAttr=attr as HelpAttribute;

if(null!=HelpAttr)

{

Console.WriteLine(“Description of {0}:\n{1}”,

assemblyName,HelpAttr.Description);

}

}

}

}

程序输出如下:

Description of QueryAttribute.exe:

ThisAssembly demonstrates custom attributes creation and their run-time query.

Press anykey to continue

查询类、方法、类成员的Attributes

下面的代码中,惟一不熟悉的就是Main()方法中的第一行。

Typetype=typeof(AnyClass);

它用typeof操作符得到一个与AnyClass类相关联的Type型对象。剩下的查询类attributes代码就与上面例子相似的。

为查询方法和类成员的attributes,首先我们得到所有在类中存在的方法和成员,然后我们查询与它们相关的所有attributes,这就跟我们查询类attributes一样的方式。

class QueryApp

{

public static void Main()

{

Type type=typeof(AnyClass);

HelpAttribute HelpAttr;

//Querying Class Attributes

foreach(Attribute attr intype.GetCustomAttributes(true))

{

HelpAttr=attr asHelpAttribute;

if(null!=HelpAttr)

{

Console.WriteLine(“Descriptionof AnyClass:\n{0}”,HelpAttr.Description);

}

}

//Querying Class-MethodAttributes

foreach(MethodInfo method intype.GetMethods())

{

foreach(Attribute attr inmethod.GetCustomAttributes(true))

{

HelpAttr=attr asHelpAttribute;

if(null!=HelpAttr)

{

Console.WriteLine(“Descriptionof {0}:\n{1}”,method.Name,

HelpAttr.Description);

}

}

}

//Querying Class-Field (onlypublic) Attributes

foreach(FieldInfo field intype.GetFields())

{

foreach(Attribute attr infield.GetCustomAttributes(true))

{

HelpAttr=attr asHelpAttribute;

if(null!=HelpAttr)

{

Console.WriteLine(“Descriptionof {0}: \n{1}”,field.Name,

HelpAttr.Description);

}

}

}

}

}

The output of the following program is.

Description of AnyClass:

This is a do-nothing Class.

Description of AnyMethod:

This is a do-nothing Method.

Description of AnyInt:

This is any Integer.

Press any key to continue

C#中的定制特性(Attributes)的更多相关文章

  1. unity中的[xxxxxx]特性(Attributes)

    [SerializeField] 在Inspector版面中显示非public属性,并且序列化:若写在public前面,等于没写. [Obsolete("调用提示信息")] [No ...

  2. C# mvc中为Controller或Action添加定制特性实现登录验证

    在本文开始前,先简单讲两个知识点: 1.每个action执行前都会先执行OnActionExecuting方法: 2.FCL提供了多种方式来检测特性的存在,比如IsDefined.GetCustomA ...

  3. 重温CLR(十三) 定制特性

    利用定制特性,可宣告式为自己的代码构造添加注解来实现特殊功能.定制特性允许为几乎每一个元数据表记录项定义和应用信息.这种可扩展的元数据信息能在运行时查询,从而动态改变代码的执行方式.使用各种.NET技 ...

  4. C# 定制特性

    一.初识特性 特性(attribute)是被指定给某一声明的一则附加的声明性信息. 设计类型的时候可以使用各种成员来描述该类型的信息,但有时候我们可能不太愿意将一些附加信息放到类的内部,因为这样,可能 ...

  5. clr via c# 定制特性

    1,特性的应用范围:特性可应用于程序集,模块,类型,字段,方法,方法参数,方法返回值,属性,参数,泛型参数 2,利用前缀告诉编译器表明意图---下面的倾斜是必须的表明了我们的目标元素: [assemb ...

  6. <NET CLR via c# 第4版>笔记 第18章 定制特性

    18.1 使用定制特性 FCL 中的几个常用定制特性. DllImport 特性应用于方法,告诉 CLR 该方法的实现位于指定 DLL 的非托管代码中. Serializable 特性应用于类型,告诉 ...

  7. 编写高质量代码改善C#程序的157个建议——建议55:利用定制特性减少可序列化的字段

    建议55:利用定制特性减少可序列化的字段 特性(attribute)可以声明式地为代码中的目标元素添加注释.运行时可以通过查询这些托管块中的元数据信息,达到改变目标元素运行时行为的目的.System. ...

  8. js中迭代元素特性与DOM中的DocumentFragment类型 笔记

    JS中迭代元素特性 在需要将DOM结构序列化为XML或者HTML字符串时,多数都会涉及遍历元素的特性,这个时候attributes属性就可以派上用场. 以下代码展示了如何迭代元素的每一个特性,然后将他 ...

  9. Java语言中的面向对象特性总结

    Java语言中的面向对象特性 (总结得不错) [课前思考]  1. 什么是对象?什么是类?什么是包?什么是接口?什么是内部类?  2. 面向对象编程的特性有哪三个?它们各自又有哪些特性?  3. 你知 ...

随机推荐

  1. Android 在eclipse中没有出现AVD的解决方法(转载)

    转自:http://frabbit2013.blog.51cto.com/1067958/1243549 本文主要介绍在系统中成功配置好Android开发环境(即SDK is ok and ADT o ...

  2. (数论)51NOD 1136 欧拉函数

    对正整数n,欧拉函数是少于或等于n的数中与n互质的数的数目.此函数以其首名研究者欧拉命名,它又称为Euler's totient function.φ函数.欧拉商数等.例如:φ(8) = 4(Phi( ...

  3. JS数组、outerHtml、className

    //对象转换为数组function obj(){for(var i=0;i<arguments.length;i++){ this[i]=arguments[i]; }} var o2=new ...

  4. Spring-security自定义过滤器

    定义过滤器 public class TokenAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter { publ ...

  5. myBatis逆向生成及使用

    引入数据库驱动 <!-- mybatis逆向生成包 --><dependency> <groupId>org.mybatis.generator</group ...

  6. 用纯函数式思维在Java8下写的一段奇葩程序

    首先说一下什么是纯函数式.在我的理解,"纯函数式"用一句话就可以描述:Anything is value.--我的理解不一定准确,但我就是这么理解的. 就是所有的东西都是值--没有 ...

  7. cookie的应用——浏览记录

    实体类 package entity; public class Product { private String id; private String proName; private String ...

  8. Java 基础入门随笔(4) JavaSE版——程序流程控制

    上一节对于运算符有了大致的了解,这一节针对程序流程控制进行复习!程序流程控制包括顺序结构.判断结构(if).选择结构(switch).循环结构. 1.判断结构 ①if语句的第一种格式:        ...

  9. 用PHP开发自己的独立博客(一)——概述

    开篇废话:因为重新回归朝九晚五的生活,于是就想开始写技术博客,当是做技术文档了.于是试用了各类博客,CSDN.cnblogs都还不错.简单试用了一下,说说各自的特点. CSDN的界面不能定制,使用默认 ...

  10. 【笔记JS/HTML/CSS】CSS3实现鼠标滑动显示动画(transition、transform)

    内容中包含 base64string 图片造成字符过多,拒绝显示