CLR via C#:定制特性

常见的定制特性:如下所示:
1.DllImport特性应用于函数时,告诉CLR该函数的实现位于指定DLL的非托管代码中。
2.Serializable特性应用于类型时,告诉序列化格式化器(实现System.Runtime.Serialization.IFormatter接口类型)一个实例的字段可以序列化和反序列化。
3.AssemblyVersion特性应用于程序集时,设置程序集的版本号。
4.Flags特性应用于枚举类型时,枚举类型就成了位标志集合。

创建定制特性:定制特性本质上是一个类。创建流程如下所示:
1.创建一个派生自Attribute的定制特性类,命名为"定制特性类名+可选的Attribute"。
2.在定制特性类里面定义公共的字段和属性。建议用属性代替字段,这样可以提供更灵活的控制。
3.定义一个公共的构造函数,用来实例化定制特性类对象。
4.使用AttributeUsageAttribute定制特性来告诉编译器定制特性类的合法应用范围。使用流程如下:
1>.指定定制特性类可以应用的目标元素。其中目标元素可以为程序集,模块,类型(引用类型,结构,枚举,接口,委托),字段,函数(包含构造函数),函数参数,函数返回值,属性,事件,泛型类型参数以及上述所有目标元素。
2>.指定定制特性类是否可以多次应用到目标元素上。
3>.指定定制特性类是否可以被继承。其中只有类型,函数,属性,字段,函数返回值,函数参数等目标元素是可以继承。
4>.如果不使用AttributeUsageAttribute定制特性的话,编译器默认认为定制特性类应用于所有目标元素,每个目标元素只能被应用一次,而且可以被继承。
5.结合上面步骤,定义一个名称为TestAttribute的定制特性类。参考代码如下所示:

[AttributeUsage(AttributeTarget.Class, AllowMultiple = false, Inherited = false)]
public class TestAttribute : Attribute
{
	// 公共字段
	public int m_i;
	// 私有字段
	private double m_d;
	// 公共属性
	private string m_s;
	public string Str
	{
		get
		{
			return m_s;
		}
		set
		{
			m_s = value;
		}
	}
	// 公共无参构造函数
	public TestAttribute()
	{
	}
	// 公共有参构造函数
	public TestAttribute(int i, double d, string s)
	{
		m_i = i;
		m_d = d;
		Str = s;
	}
}

使用定制特性:C#中通过[]来使用定制特性,以上面的TestAttribute为例,使用流程如下所示:
1.可以省略定制特性名称中的Attribute后缀以减少打字量。参考伪代码如下所示:

[TestAttribute()]
// 等价于
[Test()]

2.当定制特性使用无参构造函数时,可以省略定制特性后面的()。参考伪代码如下所示:

[Test()]
// 等价于
[Test]

3.当定制特性使用有参构造函数时,定制特性后面的()里面必须提供定位参数(也就是构造函数所需要的参数)。参考伪代码如下所示:

[Test(1, 2.3, "zzy")]
// 等价于
[Test(i:1, d:2.3, s:"zzy")]

4.定制特性后面的()里面可以选择性的为定制特性提供命名参数(也就是公共字段或者属性)。参考伪代码如下所示:

// 使用无参构造函数时,指定命名参数
[Test(m_i = 1)]
// 使用有参构造函数时,指定命名参数
[Test(2, 2.3, "yy", Str = "zzy")]

5.定制特性后面的()里面传递的参数值的类型只能是以下类型中的一种:Boolean,Char,Byte,SByte,Int16,UInt16,Int32,UInt32,Int64,UInt64,Single,Double,String,Type,Object,enum以及上述类型的数组形式。
6.当多个定制特性应用到同一个目标元素身上时,可以使用"[定制特性1][定制特性2] …",也可以使用"[定制特性1, 定制特性2, …]"。

检测目标元素是否应用了定制特性:在运行时可以使用反射机制从托管模块的元数据中来查询目标元素是否应用定制特性,从而动态改变代码的执行方式。常见的检测方式如下所示:
1.使用CustomAttributeExtensions类中的IsDefined,GetCustomAttribute以及GetCustomAttributes函数来检测指定目标元素身上是否应用了指定定制特性。具有以下特性:
1>.IsDefined函数:当指定目标元素应用了指定的定制特性类型时,就返回true;否则就返回false。该函数性能比较高,因为不会构造定制特性实例。
2>.GetCustomAttribute函数:当指定目标元素没有应用指定的定制特性类型时,就返回null;否则当指定目标元素多次应用指定的定制特性时就抛出AmbiguousMatchException异常;否则就返回指定的定制特性实例(使用编译时提供的定位参数和命名参数来构造定制特性实例) 。
3>.GetCustomAttributes函数:当指定目标元素没有应用指定的定制特性类型时,就返回空集合;否则就返回指定的定制特性实例(使用编译时提供的定位参数和命名参数来构造定制特性实例) 集合。
2.使用Assembly,Module,ParameterInfo,MemberInfo,Type,MethodInfo,ConstructorInfo,FieldInfo,EventInfo,PropertyInfo及其各自的*Builder类中的IsDefined和GetCustomAttributes函数来检测指定目标元素是否应用指定的定制特性。具有以下特性:
1>.IsDefined函数:当指定目标元素应用了指定的定制特性类型时,就返回true;否则就返回false。该函数性能比较高,因为不会构造定制特性实例。
2>.GetCustomAttributes函数:当指定目标元素没有应用指定的定制特性类型或者没有应用定制特性类型时,就返回空集合;否则就返回指定的定制特性实例集合或者返回定制特性实例集合。跟CustomAttributeExtensions类中的GetCustomAttributes函数相比,返回的是Object集合类型,因为这样可以兼容不符合CLS规范的定制特性实例。
3>.只有Type和MethodInfo类中的IsDefined和GetCustomAttributes函数才支持是否继承参数,其他类型中的IsDefined和GetCustomAttributes函数会忽略该参数并且不检查继承层次结构。
3.使用CustomAttributeData类中的GetCustomAttributes函数来检测指定目标元素是否应用定制特性。该函数性能比较高,因为不会构造定制特性实例。具有以下特性:
1>.当指定目标元素应用了定制特性时,函数返回CustomAttributeData类型实例集合;否则返回空集合。
2>.CustomAttributeData实例中的Constructor属性用来指明定制特性类型的构造函数将如何调用。
3>.CustomAttributeData实例中的ConstructorArguments属性用来指明定制特性类型的定位参数。
4>.CustomAttributeData实例中的NamedArguments属性用来指明定制特性类型的命名参数。

检测定制特性的相等性:流程如下:
1.由于Attribute类中的Equals函数内部是通过反射的方式来检测定制特性是否相等,所以最好重写Attribute类中的Equals函数,从而提升性能。参考伪代码如下所示:

// 假设基类为Attribute,子类为MyBaseAttribute,子类中存在一个字段myBaseField
public override Boolean Equals(Object obj)
{
	// 由于this不为空,当object为空时就肯定不相等
	if (obj == null)
	{
		return false;
	}

	// 如果对象属于不同的类型,肯定不相等
	if (this.GetType() != obj.GetType())
	{
		return false;
	}

	// 将obj转型为MyBaseAttribute实例
	MyBaseAttribute other = (MyBaseAttribute)obj;

	// 比较字段,判断this对象是否等于other对象
	if (other.myBaseField != myBaseField)
	{
		return false;
	}
	
	// 对象相等
	return true;
}

2.由于步骤1中重写了Equals函数,所以最好也重写GetHashCode函数,从而可以正确的将定制特性实例应用到哈希表中。参考伪代码如下所示:

public override Int32 GetHashCode()
{
	return 使用算法计算后得到一个能唯一区分当前实例的整形值
}

3.由于Attribute类中的Match函数只是简单的调用了Attribute类中的Equals函数,所以最好重写Attribute类中的Match函数,从而提升性能。参考伪代码如下所示:

// 假设基类为Attribute,子类为MyBaseAttribute,子类中存在一个字段myBaseField
public override Boolean Match(Object obj)
{
	// 由于this不为空,当object为空时就肯定不相等
	if (obj == null)
	{
		return false;
	}

	// 如果对象属于不同的类型,肯定不相等
	if (this.GetType() != obj.GetType())
	{
		return false;
	}

	// 将obj转型为MyBaseAttribute实例
	MyBaseAttribute other = (MyBaseAttribute)obj;

	// 比较字段,判断this对象是否是other对象的子集
	if ((other.myBaseField & myBaseField) != myBaseField)
	{
		return false;
	}
	
	// 对象匹配
	return true;
}

条件特性类:类型为ConditionalAttribute。具有以下特性:
1.条件特性类只能作用于定制特性类型或者返回类型为void的函数。
2.条件特性类的构造函数接收一个条件字符串参数。
3.使用了条件特性类的定制特性类型只有当条件字符串有定义时,该定制特性类型才能应用到目标元素。参考代码如下所示:

#define DEBUG

using System;
using System.Diagnostics;

[Conditional("DEBUG")]
public sealed class CondAttribute : Attribute
{
}

[Cond]
public sealed class Program
{
	public static void Main()
	{
		// 只有当有定义"DEBUG"条件字符串时,CondAttribute定制特性类型才能应用到目标元素Program
		bool isDefine = Attribute.IsDefined(typeof(Program), typeof(CondAttribute));
		if (isDefine)
		{
			Console.WriteLine("CondAttribute is applied to Program type");
		}
		else
		{
			Console.WriteLine("CondAttribute is not applied to Program type");
		}
	}
}

你可能感兴趣的:(.NET)