属性是一种新的声明性信息.使用属性既可以定义设计级信息(例如一个帮助文件或一个文档链接)又可以定义运行时信息(例如使一个XML和一个类相关联).也可以使用属性创建"自描述"组件.通过此篇教程,我们将了解如何创建并附加属性到不同的程序实体,和在运行时如何找到属性信息.
定义
MSDN的描述是属性是附加说明的信息,既一个声明的详细说明
使用预定义属性
在C#里有一小部分预定义属性.在学习如何创建我们自定义属性前,我们先看看如何在我们的代码里使用那些属性.
using System;
public class AnyClass
{
[Obsolete("Don't use Old method, use New method", true)]
static void Old( ) { }
static void New( ) { }
public static void Main( )
{
Old( );
}
}
在这个例子里.我们使用Obsolete属性,标记一个不应再使用的程序实体.第一个参数是字符串,用来说明项为什么被作废和用什么代替.事实上,可以在这里写任何内容.第二个参数告诉编译器应将使用这项的地方当作一个错误.该参数的默认值是false,这意味着编译器会生成一个警告.
当我们试图编译上面的程序,我们将得到一个错误:
AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'
开发自定义属性
现在,我们将了解如何开发自己的属性.这里有个创建自定义属性的小窍门.
按C#语言规范,将我们的属性类继承自System. Attribute就完成了我们的自定义属性(继承自抽象类System. Attribute,就直接或间接的成为一个属性类.属性类的声明从理论上定义了一种可被置于声明上的新属性).
using System;
public class HelpAttribute : Attribute
{
}
不管你是否相信,我们已经正确的创建了一个自定义属性.我们可以像使用Obsolete属性一样用上面的属性来装饰我们的类.
[Help()]
public class AnyClass
{
}
Note:使用关键字Attribute作为属性类名的后缀是一个约定.但是,当我们将属性附加到程序实体上时可以不用包含Attribute后缀.编译器首先在System.Attribute里查找属性.如果没有找到,编译器将关键字Attribute加到指定的属性名后然后再查找.
目前这个属性还没有什么用,让我们添加一些东西使它有用些.
using System;
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_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
{
}
在上面的例子里,我们在属性类里添加了一个属性(property),在下一节里我们将在运行时查找这个属性(Description).
定义或控制自定义属性的用法
AttributeUsage是另一个预定义类,这个类可以帮我们控制自定义属性类的使用.也就是说我们可以给自定义属性类定义属性.它说明了该如何使用一个自定义属性类.
在自定义属性上使用AttributeUsage时,可以对AttributeUsage的3个属性(properties)进行赋值.下面我们来讨论这3个属性(properties).
ValidOn
通过这个属性,我们可以定义自定义属性可以使用在那些程序体上. AttributeTargets枚举列出了属性可以使用的所有程序体.使用OR运算符可以联合使用多个AttributeTargets值.
AllowMultiple
该属性(property)标明在同一个程序体上是否可以使用多个自定义属性.
Inherited
此特性标志着属性是否可被从自定义属性类别中获得的类别所继承.
让我们来实践下.我们将在Help属性类上使用AttrubuteUsage属性来控制Help属性类的用法.
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属性只能用在类上.这意味着下面的代码将产生一个错误:
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属性应用在任何程序体上(可能值为: 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. 它规定属性不能多次使用在同一个程序体上.
[Help("this is a do-nothing class")]
[Help("it contains a do-nothing method")]
public class AnyClass
{
[Help("this is a do-nothing method")] //error
public void AnyMethod()
{
}
}
上面的代码产生一个编译时错误: AnyClass.cs: Duplicate 'Help' attribute
现在让我们来讨论下最后一个属性(property). 继承意味着当一个属性被置于基类上时同时也被由基类衍生出来的类别所继承,如果属性类的Inherited为true,改属性可被继承.但是,如果属性类的Inherited为false或没有指定,该属性不能被继承.
让我们来推想下面类关系的结果:
[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 ]
First Case
如果我们查找Derive类的Help属性,我们将找不到它的属性(attribute),因为inherited值为false.
Second Case
与上例没有什么不同, inherited值也为false.
Third Case
为了解释第3和第4个例子,让我们给Derive类添加和Base类相同的属性.
[Help("BaseClass")]
public class Base
{
}
[Help("DeriveClass")]
public class Derive : Base
{
}
如果我们现在查找Help属性,我们将仅得到Drive类的属性,因为继承属性值为true,但是不允许多重属性,所以基类的Help属性被Derive类的Help属性覆盖了.
Fourth Case
在第4个例子里,当我们查找Derive类的Help属性时将得到所有的属性,因为继承和多重属性是被允许的.
Note: AttributeUsage属性使用在继承自System.Attribute的类上才有效, AllowMultiple和Inherited默认都为false.
定位参数对命名参数
定位参数是属性构造函数的参数.它们是一种强制值,无论属性被置于何种程序实体,此参数都必须传递给构造函数.另方面,命名参数实际上使用的是可选的无参构造函数.
为了更详细的解释,让我们添加一些其他的属性(property)到Help属性类.
[AttributeUsage(AttributeTargets.Class,AllowMultiple = false,Inherited = false)]
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
this.description = Description_in;
this.version = "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")]
public class Class2
{
}
[Help("This is Class3", Version = "2.0",Description = "This is do-nothing class")]
public class Class3
{
}
当我们查找Class1的Help属性和它的属性(properties),我们将得到:
Help.Description : This is Class1
Help.Version :No Version is defined for this class
因为我们没有为Version属性(property)指定任何值,所以使用构造函数里给定的值.如果没有指定值,则使用类型的默认值(例如,在本例,默认值为0).
现在查找Class2的自定义属性及其属性(property)会得到如下结果:
Help.Description : This is Class2
Help.Version : 1.0
不要为可选参数使用多个构造函数.应使用命名参数替代.当我们在构造函数里给它们赋值,我们需要他们的名字,这就是我们称之为命名的原因.例如,在第2个例子里,我们这样定义Help属性类.
[Help("This is Class2", Version = "1.0")]
在AttributeUsage例子里, ValidOn是定位参数, Inherited和AllowMultiple是命名参数.
Note:在属性的构造函数里给命名参数赋值,我们需要提供属性(property)的SET访问器.
'Version' : Named attribute argument can't be a read-only property
在Class3里查找Help属性及其属性(property)会得到什么结果呢?结果同样是编译时错误.
'Description' : Named attribute argument can't be a read- only property
现在修改Hele属性类,为Description添加一个Set访问器.现在输出将是:
Help.Description : This is do-nothing class Help.Version : 2.0
在后台发生的事情是:使用定位参数调用第一个构造函数,然后依次调用命名参数的Set访问器.构造器里赋的值被SET访问器里赋的值覆盖了.
参数类型
属性类的参数类型被限制为下列类型:
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
A one-dimensional array involving any of the types listed above
属性标志符
假如我们要将Help属性应用到整个程序集,出现的第一个问题是:我们需要确定Help属性放在哪里编译器才能识别这是应用在整个程序集上的?另一种情况:要将属性应用在一个有返回类型的方法上时.编译器如何识别这是应用在一个返回类型的方法上而不是所有的方法上?
我们使用属性标识符来解决这种模棱两可的问题.通过属性标识符,可以很明确地陈述要将属性应用于哪个实体.
例如:
[assembly: Help("this a do-nothing assembly")]
在Help属性之前的Assembly标志符明确的告诉编译器当前属性应用于整个程序集,可能的标志符是:
assembly
module
type
method
property
event
field
param
return
在运行时查找属性
我们已经了解了如何创建属性并如何将它们应用到程序单元上.现在我们来学习如何在运行时查找这些信息.
我们可用.NET Framework的反射APIS通过程序集的元数据去迭代并生成由程序集所定义的包括所有类别,类型和方法的清单.
还记得前面的Help属性和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.")]
//our custom attribute class
public class HelpAttribute : Attribute
{
public HelpAttribute(String Description_in)
{
//
// TODO: Add constructor logic here
this.description = Description_in;
//
}
protected String description;
public String Description
{
get
{
return this.description;
}
}
}
//attaching the Help attribute to our AnyClass
[HelpString("This is a do-nothing Class.")]
public class AnyClass
{
//attaching the Help attribute to our AnyMethod
[Help("This is a do-nothing Method.")]
public void AnyMethod()
{
}
//attaching Help attribute to our AnyInt Field
[Help("This is any Integer.")]
public int AnyInt;
}
class QueryApp
{
public static void Main()
{
}
}
在下2节我们将在Main方法里添加属性查找代码.
查找程序集属性
在下面的代码里,我们获取当前进程名并使用Assembly类的LoadFrom方法加载程序集.然后,使用GetCustomAttributes方法获取当前程序集里附加的所有自定义属性.其次,遍历所有的属性并尝试将属性转换为Help属性(如果转换是不合法的,使用as关键字转换对象比较有利,我们不用担心会抛出异常,因为转化的结果值为NULL).跟随的行检查转换是否合法和转换结果是否为NULL,然后显示Help属性.
class QueryApp
{
public static void Main()
{
HelpAttribute HelpAttr;
//Querying Assembly Attributes
String assemblyName;
Process p = Process.GetCurrentProcess();
assemblyName = p.ProcessName + ".exe";
Assembly a = Assembly.LoadFrom(assemblyName);
foreach (Attribute attr 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: This Assembly demonstrates custom attributes creation and their run-time query.
Press any key to continue.
查找类,方法,字段属性
在下面的代码里,唯一陌生的东西就是Main方法的第一行.
Type type = typeof(AnyClass);
使用typeof运算符获得AnyClass类的类型.其余的代码和前面的代码一样查找类里的属性,就不用再解释了.
要查找方法和字段的属性,首先需要获得类里定义的所有的方法和字段;然后像查找类属性一样查找相关联的属性.
class QueryApp
{
public static void Main()
{
Type type = typeof(AnyClass);
HelpAttribute HelpAttr;
//Querying Class Attributes
foreach (Attribute attr in type.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of AnyClass:\n{0}",HelpAttr.Description);
}
}
//Querying Class-Method Attributes
foreach(MethodInfo method in type.GetMethods())
{
foreach (Attribute attr in method.GetCustomAttributes(true))
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}",method.Name, HelpAttr.Description);
}
}
}
//Querying Class-Field (only public) Attributes
foreach(FieldInfo field in type.GetFields())
{
foreach (Attribute attr in field.GetCustomAttributes(true))
{
HelpAttr= attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}:\n{1}",field.Name,HelpAttr.Description);
}
}
}
}
}
上面的程序输出的结果是:
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