在Microsoft.Net平台上,public、private、static等特性可以被应用于各种类型和成员。如果允许创建用户自定义的特性,并将它们应用于类型和方法上对于编程工作显然是一件非常美妙和方便的事情。微软推出的定制特性机制就是用来允许开发人员使用用户自定义的特性。该机制能够保证所有面向通用语言运行时(CLR)的编译器都能够识别定制特性,并将它们存放在生成的元数据中。在本文中,我将解析Microsoft.Net定制特性的相关知识。
一、特性的实质
每次使用该特性就相当于调用DllImportAttribute类型的公有构造器创建了一个实例。如果我们查看DllImportAttribute类型的文档,我们会发现它的构造器只要求一个String参数。例子中,"Kernel32"被作为这样的参数传递进去。一个特性类型的构造器参数被称为定位参数,该参数是强制性的,也就是说在应用特性时必须制定这样的参数。那么其他两个“参数”呢?特殊的语法允许我们在对象构造之后设置它的公有字段或属性。因此,当DllImportAttribute对象被构造时,实际上发射个三件事情:"Kernel32"被传入DllImportAttribute构造器,CharSet被设为CharSet.Auto,SetLastError被设为true。设置字段或属性的“参数”被称作命名参数,它们往往是可选的,并且必须有缺省值。
[Serializable]
public sealed class AttributeUsageAttribute : Attribute
{
internal AttributeTargets m_attributeTarget = AttributeTargets.All;
internal Boolean m_allowMultiple = false;
internal Boolean m_inherited = true;
public AttributeUsageAttribute(AttributeTargets validOn)
{
m_attributeTarget = validOn;
}
public AttribteTargets ValidOn
{
get { return m_attributeTarget; }
}
public Boolean AllowMultiple
{
get { return m_allowMultiple; }
set { m_allowMultiple = value; }
}
public Boolean Inherited
{
get { return m_inherited; }
set { m_inherited = value; }
}
}
AttributeUsageAttribute类型的构造函数需要传入一个AttributeTargets类型的定位参数来表示该特性可以应用的目标对象。两个命名参数AllowMultiple和Inherited,分别标示该特性是否可以被多次应用于同一个目标元素上和该特性是否可以被继承应用于派生类或派生方法上。
二、反射技术
using System.Reflection;
using System.Threading;
class SomeType{
Int32 someField;
public SomeType(ref Int32 x){
x *= 2;
}
public override string ToString()
{
return someField.ToString();
}
public Int32 SomeProp{
get{
return someField;
}
set{
if (value < 1)
throw new ArgumentOutOfRangeException("value", value, "value must be > 0");
someField = value;
}
}
public event ThreadStart SomeEvent;
private void NoCompilerWarnings(){
SomeEvent.ToString();
}
}
class App{
static void Main(){
Type t = typeof(SomeType);
BindingFlags bf = BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
//构造一个对象实例
Object[] args = new Object[]{12};
Console.WriteLine("x before constructor called: " + args[0]);
Object obj = t.InvokeMember(null, bf | BindingFlags.CreateInstance, null, null, args);
Console.WriteLine("Type: " + obj.GetType().ToString());
Console.WriteLine("x after constructor returns: " + args[0]);
//读取或者设置一个字段
t.InvokeMember("someField", bf | BindingFlags.SetField, null, obj, new Object[] { 5 });
Int32 v = (Int32)t.InvokeMember("someField", bf | BindingFlags.GetField, null, obj, null);
Console.WriteLine("someField: " + v);
//调用一个方法
String s = (String)t.InvokeMember("ToString", bf | BindingFlags.InvokeMethod, null, obj, null);
Console.WriteLine("ToString: " + s);
//读取或者设置一个属性
try{
t.InvokeMember("SomeProp", bf | BindingFlags.SetProperty, null, obj, new Object[] { 0 });
}
catch(TargetInvocationException e){
if (e.InnerException.GetType() != typeof(ArgumentOutOfRangeException)) throw;
Console.WriteLine("Property set catch.");
}
t.InvokeMember("SomeProp", bf | BindingFlags.SetProperty, null, obj, new Object[] { 2 });
v = (Int32)t.InvokeMember("SomeProp", bf | BindingFlags.GetProperty, null, obj, null);
Console.WriteLine("SomeProp: " + v);
}
}
三、开发自定义Attribute
有了以上的技术储备,我们就可以定义并使用自己的特性了。
public class HelpAttribute : Attribute
{
}
{
}
注意:按惯例我们是用”Attribute“作为attribute类名的后缀,然而,当我们当我们把attribute绑定到某语言元素时,是不包含“Attribute“后缀的。编译器首先在System.Attribute 的继承类中查找该attribute,如果没有找到,编译器会把“Attribute“追加到该attribute的名字后面,然后查找它。
但是迄今为止,该attribute没有任何用处。为了使它有点用处,让我们在它里面加点东西吧。
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.deescription;
}
}
}
//attaching Help attribute to our AnyClass
[Help("This is a do-nothing Class.")]
public class AnyClass
{
//attaching 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()
{
}
}
在上面的例子中,我们在HelpAttribute类中添加了一个属性,并把它应用到多种元数据对象上。
接下来,我们将在运行时查询该属性。
1)查询程序集的Attributes
在下面的代码中,我们先得到当前的进程名称,然后用Assembly类中的LoadForm()方法加载程序集,再有用GetCustomAttributes()方法得到被绑定至当前程序集的自定义attributes,接下来用foreach语句遍历所有attributes并试图把每个attribute转型为Help attribute(即将转型的对象使用as关键字有一个优点,就是当转型不合法时,我们将不需担心会抛出异常,代之以空值(null)作为结果),接下来的一行就是检查转型是否有效,及是不是为空,跟着就显示Help attribute的“Description”属性。
class QueryApp
{
public static void Main()
{
HelpAttribute HelpAttr;
// Get the class type to access its metadata.
Type clsType = typeof(QueryApp);
// Get the assembly object.
Assembly assembly = clsType.Assembly;
// Store the assembly's name.
String assemblyName = assembly.GetName().Name;
//HelpAttribute hAttr = (HelpAttribute)Attribute.GetCustomAttribute(assembly, typeof(HelpAttribute));
//Console.WriteLine("Description of {0}: {1}\n", assemblyName, hAttr.Description);
var attributes = assembly.GetCustomAttributes(false);
foreach (Attribute attr in attributes)
{
HelpAttr = attr as HelpAttribute;
if (null != HelpAttr)
{
Console.WriteLine("Description of {0}: {1}\n", assemblyName, HelpAttr.Description);
}
else
{
//Console.WriteLine("Attribute : {0}\n", attr.ToString());
}
}
}
}
程序输出如下:
Description of QueryAttribute.exe:
This Assembly demonstrates custom attributes creation and their run-time query.
2)查询类、方法、类成员的Attributes
下面的代码中,用typeof操作符得到了一个与我们AnyClass类相关联的Type型对象。首先我们得到所有在类中存在的方法和成员,然后我们查询与它们相关的所有attributes,这就跟我们查询类attributes一样的方式。
{
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: {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}: {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}: {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.