简介
特性提供功能强大的方法,用以将元数据或声明信息与代码(程序集、类型、方法、属性等)相关联。 特性与程序实体关联后,即可在运行时使用名为“反射”的技术查询特性。
特性具有以下属性:
特性可向程序中添加元数据。 元数据是有关在程序中定义的类型的信息。 所有的 .NET 程序集都包含指定的一组元数据,这些元数据描述在程序集中定义的类型和类型成员。 可以添加自定义特性,以指定所需的任何附加信息。
可以将一个或多个特性应用到整个程序集、模块或较小的程序元素(如类和属性)。
特性可以与方法和属性相同的方式接受参数。
程序可以使用反射检查自己的元数据或其他程序内的元数据。
(以上来自MSDN)
特性的定义
首先看一个例子,通过该例子可以更好地说明怎样使用特性这个具有强大功能的特征。假如您有一个在Windows注册表里存储信息的程序。那么您要解决的一个设计难题是在何处存储注册表的键值信息。在大多的开发环境中,一种典型的方式就是把该信息存储在一个资源文件里, 或者存在常量里,或者把该信息牢固地编写到Registry API的调用中。但是,这种方式会把原本是一个整体的类与该类的定义部分分开来存储。如果使用特性,我们就可以把这部分信息附在类成员上,这样的话,我们就有一个完全自我描述的组件了、下面通过例子看看这些是如何实现的,假定已经在其它地方定义了一个叫做ResgistryKey的特性。
class MyClass
{
[RegistryKey(HKEY_CURRENT_USER,"foo")]
public int Foo;
}
要给一个c#类型或成员附加一个已定义的特性,只要在目标类型或成员的前面加上特性数据,并用括号把它们括起来即可。例如上面的代码中我们给MyClass.Foo字段加了一个名为RegistryKey的特性。在运行时我们就可以立刻看到---对于注册表键值我们要做的就是查询这个字段,然后用这个值把日期保存到注册表里。
在上面的例子中,您会发现给一个类型或成员附加特性的语法看起来有点像类的实例化。这是因为一个特性确切的说就是一个从System.Attribute基类派生的类。现在让我们看一看RegistryKey特性的结构。
public enum RegistryHives
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
public class RegistryKeyAttribute : Attribute
{
public RegistryKeyAttribute(RegistryHives Hive, string valueName)
{
this.valueName = valueName;
this.Hive = Hive;
}
private RegistryHives hive; public RegistryHives Hive
{
get { return hive; }
set { hive = value; }
}
private string valueName; public string ValueName
{
get { return valueName; }
set { valueName = value; }
}
}
我们在这里加了一个enum来声明一个不同的Registry类型,它有一个特性类的构造函数(带有一个Registry类型的参数和一个值参数),两个属性(Registry配置单元和值名)。您可以有很多方式来定义特性,现在我们知道了怎样定义和附加特性,那么怎么在运行时查询特性呢?下面通过一个完整的例子来开始我们的工作,一旦完成这部分,我们就可以继续处理一些定义和附加特性的更高级的问题。
注意:注意在这个例子中特性类名后附加了Attributue。但是,当在以后把这个特性附加给一个类型或成员时,不需要再包括这个Attribute后缀了。这是C#语言设计员们提供的一种快捷方式。当编译器看到一个特性附加给一个类型或成员时,它就会搜索带有制定名称的System.Attribute的派生类。如果没有找到这样的类,编译器就会先给这个特性加上Attribute,然后再搜索这个特性。因此,通常的做法是:定义特性类名时以Attribute结尾,之后常常省略Attribute这部分。
对特性进行查询
我们已经知道怎样来通过System.Attribute派生类定义一个特性,以及怎样把它附加给一个类型成员。那么在代码中我们怎样使用特性呢?换句话说,我们怎么样才能根据所附加的特性(和它的参数)来查询一个类型或一个成员呢?
要查询一个类型或者成员的附加特性,我们需要用到反射(refection)。反射就是这样一种特征:可以在运行时动态地决定应用程序的类型特点。
- 类的特性
怎样获得一个特性取决于您要查询的成员类型。假如您要定义一个特性,这个特性被用来定义创建对象的远程服务器。不使用特性的话,您就要把这个信息存储在一个常量里或者是应用程序的资源文件里。使用特性的话,您只需要简单地(像下面这样)为这个类添加一个远程服务器的注释即可。
public enum RemoteServers
{
JEANVALJEAN,
JAVERT,
COSETTE
}
public class RemoteObjectAttribute : Attribute
{
public RemoteObjectAttribute(RemoteServers server)
{
this.server = server;
}
private RemoteServers server; public RemoteServers Server
{
get { return server; }
set { server = value; }
}
}
[RemoteObject(RemoteServers.COSETTE)]
class MyRemotableClass
{ }
要确定创建的远程服务器,可以使用如下代码:
static void Main(string[] args)
{
Type type=typeof(MyRemotableClass);
foreach (Attribute attr in type.GetCustomAttributes(false))
{
RemoteObjectAttribute remoteAttr = attr as RemoteObjectAttribute;
if (remoteAttr!=null)
{
Console.WriteLine("Create this object on "+remoteAttr.Server);
}
}
Console.Read();
}
应用程序输出结果为:
上面的例子很有代表性,我们可以通过它来测试一下反射是怎样工作的,以及运行时是怎样返回特性的值的。您会注意到主程序的第一行试用了typeof操作符。
Type type=typeof(MyRemotableClass);
这个操作符返回和这个作为它唯一的参数值的类型相关的System.Type对象。一旦您得到这个对象,您就可以对他进行查询
foreach (Attribute attr in type.GetCustomAttributes(false))
Type.GetcustomAttribute方法的调用。这个方法返回Attribute类型的数组,这个数组包含所有的附加给名为MyRemoteableClass类的特性。
//
// 摘要:
// 在派生类中重写时,返回应用于此成员的所有自定义特性的数组。
//
// 参数:
// inherit:
// 搜索此成员的继承链以查找这些属性,则为 true;否则为 false。 属性和事件中忽略此参数,请参见“备注”。
//
// 返回结果:
// 一个包含应用于此成员的所有自定义特性的数组,在未定义任何特性时为包含零个元素的数组。
//
// 异常:
// System.InvalidOperationException:
// 该成员属于加载到仅反射上下文的类型。 请参见如何:将程序集加载到仅反射上下文中。
//
// System.TypeLoadException:
// 未能加载自定义特性类型。
public abstract object[] GetCustomAttributes(bool inherit);
//
// 摘要:
// 在派生类中重写时,返回应用于此成员并由 System.Type 标识的自定义特性的数组。
//
// 参数:
// attributeType:
// 要搜索的特性类型。 只返回可分配给此类型的属性。
//
// inherit:
// 搜索此成员的继承链以查找这些属性,则为 true;否则为 false。 属性和事件中忽略此参数,请参见“备注”。
//
// 返回结果:
// 应用于此成员的自定义特性的数组;如果未应用任何可分配给 attributeType 的特性,则为包含零个元素的数组。
//
// 异常:
// System.TypeLoadException:
// 无法加载自定义特性类型。
//
// System.ArgumentNullException:
// 如果 attributeType 为 null。
//
// System.InvalidOperationException:
// 该成员属于加载到仅反射上下文的类型。 请参见如何:将程序集加载到仅反射上下文中。
public abstract object[] GetCustomAttributes(Type attributeType, bool inherit);
GetCustomAttributes
2.字段的特性
假如有一个包含一些字段的类,您想把这些字段的值存储在注册表中。要想这样,您可以利用构造函数来定义一个特性,并且使该构造函数取两个参数,一个为枚举类型,代表正确的注册表配置单元,一个为字符串类型,代表注册表的值名。下面您就可以根据字段的注册键值在运行时进行字段的查询了。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Wolfy.AttributeDemo
{
public enum RegistryHives
{
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_CURRENT_CONFIG
}
public class RegistryKeyAttribute : Attribute
{
public RegistryKeyAttribute(RegistryHives Hive, string valueName)
{
this.valueName = valueName;
this.Hive = Hive;
}
private RegistryHives hive; public RegistryHives Hive
{
get { return hive; }
set { hive = value; }
}
private string valueName; public string ValueName
{
get { return valueName; }
set { valueName = value; }
}
} }
RegistryKeyAttribute
public class TestClass
{
[RegistryKey(RegistryHives. HKEY_CURRENT_USER,"Foo")]
public string Foo;
public string Bar;
}
TestClass
控制台程序:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks; namespace Wolfy.AttributeDemo
{
class Program
{
static void Main(string[] args)
{
Type type = typeof(TestClass);
foreach (FieldInfo field in type.GetFields())
{
foreach (Attribute attr in field.GetCustomAttributes())
{
RegistryKeyAttribute registryKeyAttr = attr as RegistryKeyAttribute;
if (registryKeyAttr != null)
{
Console.WriteLine("{0} will be saved in {1} \\\\{2}", field.Name, registryKeyAttr.Hive, registryKeyAttr.ValueName);
}
}
}
Console.Read();
}
}
}
结果:
结语
本文来自《c#技术内幕》,觉得本章节内容还是比较容易理解的,不敢独享,特分享在此,希望能帮到别人。未完待续.......