给你的.NET代码添加新的强大的功能和行为。 by Steven Yi 技术工具箱:C#
.NET Framework的一个强大的功能之一就是它支持基于特性的编程,这就可以让你在编译时为你的代码创建自定义的元数据。你可以用自定义的元数据给类提供额外的信息,从而进一步定义你的类型的行为。通过给运用特性(attribute)的类添加几行代码,你就可以很快地重用功能。我将讲述如何通过将系统行为放在一个集中的位置,编写自定义的特性来创建更容易阅读的、可重用的、可扩展的代码。通过学习这些特性是如何工作的,并且创建你自己的特性,你就可以更好地了解.NET Framework、.NET对元数据的运用以及C#语言的强大的特征。
.NET在实现特性时,它们是作为特殊的注释出现在代码中的,你可以将它们用于一个类型或成员字段。注释是用方括号括起来的,描述了后面的代码;.NET Framework或其它的程序可以在编译时读取它们,或者你可以用Reflection动态地读取它们。特性可以描述一个特定的类型、一个类型的属性(property)和方法、或者一个完整的集合。.NET已经实现了一些特性以支持其更强大的功能,如串行化和COM+ Enterprise Services。
运用.NET Reflection动态地读取特性值可以让你程序化地查看一个类型并读取它的元数据。Reflection(位于System.Reflection名字空间中)在运行时查找类型,可以让你得到一个特定类型的方法、字段、属性、甚至是类型定义的特性。Reflection也可以让你动态地读取所定义的字段和属性的值。
你可以根据特殊的需求创建自定义的特性,并在特性数据的基础上执行自定义的操作。我将一步步地讲述如何创建一个自定义的特性、动态地访问它、并为一个虚构的电子目录项目创建一个自定义的排序方法。这个例子用了一个基本的类,即CatalogItem。这个类很简单,但是用.NET定义的[Serializable]特性修饰它后就可以让它呈现一些强大的新的功能了,你单独看代码时,这些功能并不很明显。通过添加这个特性,你就可以告诉.NET Framework你的CatalogItem类型可以被保存为一个二进制或基于字符的格式,如XML。
你将创建一个电子目录,在这个电子目录中,CatalogItem类代表每个项目(见下)。
| C# :创建一个电子目录
列表1. 在代码中实现特性是个很简单的过程。下面的代码允许CatalogItem类是可以串行的。注意类声明前方括号中的特殊注释。
using System;
[Serializable]
public class CatalogItem
{
private String strName;
private String strDesc;
private int intItemID;
private double dblPrice;
public String Name
{
get { return strName; }
set { strName = value; }
}
public String Description
{
get { return strDesc; }
set { strDesc = value; }
}
public int ItemID
{
get { return intItemID; }
set { intItemID = value; }
}
public double Price
{
get { return dblPrice; }
set { dblPrice = value; }
}
}
| | 这个类有用于ItemID、Name、Description和Price的属性。你也可以控制各个项目的呈现方式,用不同的排序标准(如名称、价格等,以升序或降序)使目录呈现不同的版本。CatalogItem列表的排序操作标准是由一个你创建的叫做CatalogSortAttribute的自定义的特性控制的,它描述了你想如何排列各个项目,排序规则(升序/降序)是怎样的。
 |
图1. 扩展AbstractCatalog类
| AbstractCatalog类型是基本的目录类型,NameCatalog和PriceCatalog派生于它,它们代表了两种目录类型。每种目录类型都包含一个CatalogItems数组,它派生于AbstractCatalog,你将根据你的目录类型对这个数组进行排序(见下)。
图 1. 扩展 AbstractCatalog类
这个电子目录UML图说明了NameCatalog和PriceCatalog是如何扩展AbstractCatalog抽象类的。DynamicSorter通过运用Reflection读取项目值来排序目录项目。
图 1. 扩展 AbstractCatalog类
这个电子目录UML图说明了NameCatalog和PriceCatalog是如何扩展AbstractCatalog抽象类的。DynamicSorter通过运用Reflection读取项目值来排序目录项目。
CatalogSortAttribute类型是一个自定义的特性,通过将它插入到你的NameCatalog和PriceCatalog类型中,就可以控制每种目录类型的排序结果了。
最后要说明的是,在你的程序中,DynamicSorter类型是很重要的。它用Reflection来读取你自定义的CatalogSortAttribute属性,从而对CatalogItems进行正确的排序。为了进一步说明Reflection的强大性,你可以用它来执行实际的排序。
创建自定义的特性 创建一个CatalogSortAttribute类型;根据C#命名惯例,你可以将它命名为[CatalogSort]或[CatalogSortAttribute](见工具条“创建特性的技巧”)。.NET定义了另外的特性来正确运用你自定义的特性。你可以将对特性的运用限制在类一级上、成员级上或通过运用System.AttributeUsageAttribute来将两个级别结合起来。如果你没有正确地运用特性,.NET编译器就会抛出一个错误。创建你的CatalogSortAttribute,这样它就只可以被用于类或结构(struct)。通过运用[AttributeUsage]特性,并从AttributeTargets枚举传入值来实现这点:
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Struct)]
public class CatalogSortAttribute :
System.Attribute
{ . . .
|
现在添加SortAscending和SortProperty属性来提供关于按什么字段和顺序进行排序的描述符(见下)。
| C# :提供排序字段和方向
列表2. 属性用sortProperty和sortAscending变量为CatalogSortAttribute.cs特性存储参数选项。AttributeUsage特性可以限制我们只能在类和结构上运用这个自定义的特性。
using System;
[AttributeUsage(AttributeTargets.Class |
AttributeTargets.Struct)]
public class CatalogSortAttribute : System.Attribute
{
private bool sortAscending;
private string sortProperty;
public CatalogSortAttribute()
{
sortProperty = "Name";
sortAscending = true;
}
public CatalogSortAttribute(string sortproperty)
{
sortProperty = sortproperty;
sortAscending = true;
}
public bool SortAscending
{
get { return sortAscending; }
set { sortAscending = value; }
}
public string SortProperty
{
get { return sortProperty; }
set { sortProperty = value; }
}
}
| | 你可以在构造器中为属性定义缺省的值,然后你就可以很简单地在你的目录类中以多种方式运用这个特性了:
[CatalogSort]
//default constructor using default values
[CatalogSort("Price")]
//overloaded constructor, set SortProperty
//to "Price"
[CatalogSort("Price", SortAscending=false)]
//overloaded constructor, set SortProperty
//to "Price", and the SortAscending property to false. |
现在你需要创建不同的项目目录,根据可用的CatalogSortAttribute选项对你的CatalogItems进行排序了。并不复杂的AbstractCatalog类型有一个属性,可以用来得到(get)和设置(set) CatalogItem类型的一个数组,用SortItems()方法根据特性值来执行你的排序操作(见下)。
| C#:创建一个AbstractCatalog类型
列表A. AbstractCatalog包含一个CatalogItems数组,并引用了DynamicSorter类,用Reflection来排序项目。GetTestItems()方法提供了一些测试值。
using System;
public abstract class AbstractCatalog
{
private CatalogItem[] catItems;
public AbstractCatalog() { }
public CatalogItem[] CatItems
{
get { return catItems; }
set { catItems = value; }
}
public void SortItems()
{
DynamicSorter sorter = new
DynamicSorter();
sorter.SortCatalog(this);
} //end SortItems
//Test method to create catalog items.
public CatalogItem[] GetTestItems()
{
CatalogItem[] items = new
CatalogItem[10];
//init items
for (int i=0; i<10; i++)
{
items[i] = new CatalogItem();
} //end for
items[0].ItemID = 1;
items[0].Name = "Leather Belt";
items[0].Description = "Fine grain
brown leather";
items[0].Price = 22.99;
.
.
.
return items;
}
}
| |
NameCatalog和PriceCatalog实现了自定义的特性信息:
using System;
[CatalogSort]
public class NameCatalog : AbstractCatalog
{
public NameCatalog() {}
}
[CatalogSort("Price", SortAscending=false)]
public class PriceCatalog : AbstractCatalog
{
public PriceCatalog() {}
}
|
这两个类都扩展了AbstractCatalog,并继承了它的方法和属性。CatalogSort特性为每个类定义了项目排列顺序。用CatalogSortAttribute特性信息来修饰这些类,依靠DynamicSorter这个有用的类和.NET reflection来做更难的工作。NameCatalog根据Name属性以升序排列项目,因为你只用了缺省的值。根据特性描述——[CatalogSortAttribute(“Price”,SortAscending=false)]——PriceCatalog根据价格以降序排列各个项目。
你的目录类看上去很简单,但是通过添加一个自定义的特性,你就可以只用一行代码给它们提供一套新的行为了。
用Reflection访问特性信息 现在让我们来看看我们感兴趣的部分——动态地读取程序中你的自定义的特性。.NET Reflection可以让你通过调用两个方法来读取特性信息。第一个方法是Type.GetCustomAttributes(System.Type,bool),它返回一个特定类型的自定义特性的一个对象数组。Boolean参数指定是否查询类型的继承链(inheritance chain)来找到特性,但在当前.NET版本中这个参数被忽略不计了。第二个方法是Type.GetCustomAttributes(bool),它返回自定义特性的一个对象数组,而不管类型是什么。
DynamicSorter用这个方法分别从你的目录中读取[CatalogSort]特性:
CatalogSortAttribute[] sortAttrib =
(CatalogSortAttribute[])
type.GetCustomAttributes(typeof
(CatalogSortAttribute), false);
|
一旦读取到特性,根据CatalogSortAttribute的Name和SortAscending属性,你就可以知道按什么属性和顺序来排列你的目录项目了。在这个例子中,我们用了一个快速排序算法,和一个以Type为基础的Reflection来进行排序,它说明了.NET的其它的强大的Reflection功能,包括动态地返回一个类实例的属性和字段数据。然后SortCatalog()方法可以重新排序你的CatalogItems数组,这个数组可以通过AbstractCatalog.CatItems属性读取到(见列表B)。
| C# :读取 CatalogSort Attribute
列表B. 排序参数的选择是通过用Reflection读取CatalogSort特性来决定的。然后用一个快速排序算法来排列项目。
using System;
using System.Reflection;
using System.Collections;
public class DynamicSorter
{
Comparer comparer;
public DynamicSorter()
{
comparer = Comparer.Default;
}
public void SortCatalog(AbstractCatalog catalog)
{
Type type = catalog.GetType();
CatalogSortAttribute[] sortAttrib =
(CatalogSortAttribute[])
type.GetCustomAttributes(typeof(CatalogSortAttribute)
, false);
CatalogItem[] catItems = catalog.CatItems;
this.QuickSort(catItems, 0, catItems.Length-1,
sortAttrib[0].SortProperty);
if (!sortAttrib[0].SortAscending)
{
Array.Reverse(catItems);
}
}
.
. //The full code listing is available online.
.
}
| |
现在我们来测试你的电子目录程序。一旦调用SortItems()方法,NameCatalog的CatalogItems列表就打印出来了。测试类——TestSort.cs——创建了NameCatalog的一个实例并打印了排序目录的结果(见列表3和图2)。
| C# :测试目录结果
列表3. TestSort根据在NameCatalog的SortAttribute中定义的排序标准来输出项目。
Public class TestSort
{
public static void Main (String[] args)
{
NameCatalog cat = new NameCatalog()
cat.CatItems = cat.GetTestItems();
cat.SortItems();
CatalogItem[] items = cat.CatItems;
for (int i=0; i<items.Length; i++)
{
Console.WriteLine("ID: " +
items[i].ItemID + ", Name: " +
items[i].Name + ", Price: " +
items[i].Price);
} //end for
Console.ReadLine();
}
| |
图 2. 按名称排序
TestSort.cs的结果显示了按名称来排列目录项目。注意NameCatalog.cs运用的[CatalogSortAttribute]注释为排序设置了缺省的参数,说明排序是按照Name属性、以升序进行的。
注意,你已经成功地按照名称以升序排列了你的目录项目。如果你进一步测试,修改TestSort类,用PriceCatalog进行排序,你会看到目录项目会按照价格以降序进行排列了。运用你的自定义的特性——CatalogSortAttribute——你就可以用一行特性描述符来改变你的目录类型的行为了。
自定义的特性可以给你的代码添加新的强大的功能,尽管它们只不过是给你的代码创建了额外的元数据描述符。我们面临的问题是要识别在什么情况下一个自定义的特性可以使开发项目受益。一旦你找到运用特性的一个机会,通过将自定义的系统行为放在一个集中的位置,就可以使代码开发过程变得更顺利。当你需要添加特性描述符来影响对象的行为时,你的程序会变得更容易阅读和维护。你不再需要每次为类似的情况重写代码了。 关于作者: Steven Yi是位技术架构师,他在一家达拉斯的金融服务公司工作。他曾用J2EE和Microsoft解决方案为财富500的公司做过咨询工作。作为一位MCSD、Java 2程序员和Oracle 8i数据库操作员,Steven在管理信息系统方面也有一定的造诣。他的联系方式是syi@digitalinstance.com。
|