在.NET框架中,经常用到属性,从定义哪些类是可序列化到选择某个Web服务应用中的哪些方法是可以公开的都会用到属性。使用属性可以在设计时对类、properties和方法添加说明,然后在运行时通过反射信息来检查它们。本文为我们介绍了在开发应用时如何利用C#自定义属性。
本文可以从技术文章下载出获得,其中包含了一个使用定制属性的Visual Studio项目示例文件。
属性类是设计时可应用于类、properties和方法的特殊文类。属性类提供描述元素某些方面属性的方式或决定依附于该元素的其它类的行为,进而在运行时可以访问和检验这些描述与行为。你可以将属性类看作为类成员添加特殊修改器的一种方式。
例如,如果你曾经写过Web服务,那肯定知道要使得方法在整个服务中是公开的,必须要使用WebMethod属性。这是一个演示属性应用的很好的例子,因为我们要用WebMethod属性扩展编程模型。C#中没有内建的方式来指定某个方法通过Web服务是可见的(因为内建有表明一个方法是私有的方式),因此需要添加WebMethod属性来满足这一需要。
设计自定义属性
设计自定义属性的过程十分简单,在设计属性前只需要考虑以下几个方面:
* 使用属性的目的是什么?
属性可以以很多方式使用。你需要定义属性到底要完成什么功能并确保这些特定功能没有内建在.NET框架集中。使用.NET修改器要比使用属性好,因为这将简化同其它装配件的集成过程。
* 属性必须储存什么信息?
属性是打算用来指示某个功能的简单标志吗?或者属性是否要储存信息?一个属性可以拥有设计时赋予的一组信息,并在运行时查看这些信息。例如,看一下示例应用中的别名属性。
* 属性应该驻留在哪个装配件中?
大多数情况下,可以将属性包含在使用该属性的装配件中。不过也有这样的例子,将属性驻留在公共的、轻量级的、共享装配件中会更好些。这种类型的配置允许客户使用属性时不必引用不需要的装配件。
* 哪些装配件将会识别属性?
如果没有模块读取属性,那么它将一文不值。你很可能将读取属性的类放在属性驻留的同一个装配件中。然而,正像前面提到的,也有这样的例子,你想将读取属性的方法与属性自身分别放在不同的装配件中。
使用属性在我们深入了解如何设计自定义属性之前,我们需要先看一下它们是如何使用的。例如,假定我们有一个称为“Hide”的属性它能够有效地隐藏 Properties,因此它们不会显示在屏幕上。如果我们将这个属性应用于“SSN”property,那么代码将会如列表A所示。
列表 A
[Hide ()]
public string SSN
{
get { return _ssn; }
set { _ssn = value; }
}
作为更复杂一点的例子,假设我们将有一个属性称为“Alias”。该属性的任务是检查一个property可能拥有别名。这将允许将一个property 值映射给另一个property即使批roperty的名字不匹配。这个属性接受一系列字符串值作为映射名。(列表B)
列表 B
[Alias ("FirstName", "First")]
public string FName
{
get { return _fName; }
set { _fName = value; }
}
在这个例子中,property“FName”被映射到“FirstName”和“First”,请查看示例应用以更详细的了解这种应用。
创建属性
创建属性是一个简单的过程。你可以定义继承自System.Attribute类的一个包含你想要储存的数据的类。列表C的前半部分显示了如何创建一个名为“Alias”的属性。
列表 C
Class Alias : System.Attribute
{
string[] _names;
public Alias(params string[] names)
{
this.Names = names;
}
public string[] Names
{
get { return _names; }
set { _names = value; }
}
}
正如你所看到的,这就是一个普通的类,唯一的例外就是继承自System.Attribute类。我们不需要作任何特别的事情使它成为一个类。我们只是简单的定义了一个需要使用的构造函数并创建了一个property和一个存储数据的私有成员。
列表D是个更简单的属性——“Hide”属性。这个属性不需要构造函数(使用默认的构建函数),也不储存数据。因为这个属性只是一个简单的标志类型的属性。
列表 D
Class Hide : System.Attribute
{
//This is a simple attribute, that only requires
// the default constructor.
}
从代码中读取属性
读取属性并检查其中的数据比使用属性或创建属性显著地更加复杂。读取属性要求开发人员要对如何使用一个对象的反射信息有个基本了解。如果你不熟悉反射机制,可以阅读“应用反射”系列文章。
假设我们正在查看一个类,我们想知道该类的那个properties使用了Alias属性以及都有哪些别名。列表E实现了这个功能。
列表 E
Private Dictionary<string, string> GetAliasListing(Type destinationType)
{
//Get all the properties that are in the
// destination type.
PropertyInfo[] destinationProperties = destinationType.GetProperties();
Dictionary<string, string> aliases = newDictionary<string, string>();
for each (PropertyInfo property in destinationProperties)
{
//Get the alias attributes.
object[] aliasAttributes =
property. GetCustomAttributes( typeof(Alias), true);
//Loop through the alias attributes and
// add them to the dictionary.
foreach (object attribute in aliasAttributes)
foreach (string name in ((Alias)attribute).Names)
aliases.Add(name, property.Name);
//We also need to add the property name
// as an alias.
aliases.Add(property.Name, property.Name);
}
return aliases;
}
这段代码最重要的地方是对GetCustomAttributes的调用以及循环遍历属性提取别名的地方。
GetCustomAttributes方法可以在我们从对象类型中提取的PropertyInfo类中找到。在上面的应用中,我们将要查询的属性类型作 为参数传给GetCustomAttributes方法,同时还将“true”传递给该方法使得可以列出继承的属性。如何发现匹配的属性, GetCustomAttributes方法将返回一个对象数组。还有另外一种超负荷方法可以列出property上的所有属性,而不管属性类型是什么。
一旦有了属性,我们需要检查它们并从中提取需要的信息。这可以通过遍历由GetCustomAttributes方法得到的对象数组并将每个对象映射成我 们要查询的属性来完成。在完成映射后,我们就可以像访问任意其它类的properties一样来访问属性的properties。
正如我在前面所说,读取属性是最困难的部分。然而,一旦我们写后读取属性的代码后,将来回忆和实施起来就相当容易了。
应用示例
我强烈建议你下载本文包含的这个应用示例。这个应用示例在一个简单的Windows应用中实现了下面的属性,并演示了如何读取和使用它们。
- Alias——这同上面提到的Alias属性一样。当你需要将一种类型的对象翻译为另一种类型时,需要使用该属性。例如,如果你有一个 Customer对象和一个Address对象,你可能需要将它们都翻译为一个合并的包含人名和地址的Person对象,当一个不能使用直接映射时,可以 使用该属性。
- DisplayName——示例代码中包括检查一个类实例并将它的property名称与值输出到屏幕上的代码。这个属性可用于覆 盖送到屏幕显示的property名称。例如,一个名为“FName”property可以使用DisplayName属性,因此它显示为“First Name”。
- Examine——这个属性使得示例应用中的PrintObject方法进入更深一层,并输出使用了Examine属性的 property的值。例如,示例应用中的Customer对象将Examine属性应用到Address property。这将指示PrintObject方法输出address property中的所有信息。
- Hide——这个属性指示PrintObject()方法不要将当前property输出到屏幕。该属性用在Customer对象的SSN property上。
示例应用中包含了实现和读取属性每一步的注释,仔细看一下,我敢保证你会发现一些在自己的应用中可以利用的功能。