定制特性
-
定制特性 可宣告式的为自己的代码构造添加注解来实现特殊功能。
-
定制特性允许为每一个元数据记录项定义和应用信息。
-
这种可扩展的元数据信息能在运行时查询,从而动态改变代码的执行方式。
先看一个应用了特性的例子:
[Custom("这里是学生", Description = "123456", Remark = "123456")]
public class Student
{
[Custom] //使用特性时可以省略Attribute
public Student()
{
Console.WriteLine("这里是student");
}
[Custom]
public int Id { get; set; }
}
这里是定义特性的类,它继承Attribute。
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)] //参数AttributeTargets -- 目标,AllowMultiple -- 允许多重修饰, Inherited -- 允许继承
public class CustomAttribute : Attribute
{
public CustomAttribute()
{
Console.WriteLine("这里是CustomAttribute");
}
public CustomAttribute(string remark)
{
Console.WriteLine("这里是CustomAttribute带参数");
}
public string Remark { get; set; }
public string Description { get; set; }
public void Show()
{
Console.WriteLine($"This is {this.GetType().Name}");
}
}
public class CustomChildAttribut : CustomAttribute
{
}
上述例子中 对于自己定义的特性也应用了一个特性,也为特性本身就是类。AttributeUsage 特性是一个简单的类,可利用它告诉编译器定制特性的合法应用范围。编译器都内建了对该特性的支持,并会在定制特性应用于无效目标时报错。
The System.AttributeTargets 枚举类型在FCL 中是像下面这样定义的。
[Flags, Serializable]
public enum AttributeTargets {
Assembly = 0x0001,
Module = 0x0002,
Class = 0x0004,
Struct = 0x0008,
Enum = 0x0010,
Constructor = 0x0020,
Method = 0x0040,
Property = 0x0080,
Field = 0x0100,
Event = 0x0200,
Interface = 0x0400,
Parameter = 0x0800,
Delegate = 0x1000,
ReturnValue = 0x2000,
GenericParameter = 0x4000,
All = Assembly | Module | Class | Struct | Enum |
Constructor | Method | Property | Field | Event | Interface | Parameter | Delegate | ReturnValue | GenericParameter
}
特性到底是什么?
-
定制特性其实是一个类型的实例。为符合CLS ,定制特性类必须直接或间接从公共抽象类 system.Attribute 派生。
-
特性是类的实例,类必须有公共构造器才能创建它的实例。所以,将特性应用于目标元素时,语法类似于调用类的某个实例构造器。
特性构造器和字段/属性数据类型*
特性本质是一个类,那它就有自己的构造函数,字段/属性。在定义特性类的实例构造器、字段和属性时,可供选择的数据类型不多,只允许以下类型:
Boolean, Char, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, Single, Double, String, Type, Object
检测定制特性
仅仅定义特性类没有用,将特性应用于类、方法上也只是在程序集生成额外的元数据,也没意义,应用程序的代码不会有任何改变。
类、方法在调用的时候让特性起到作用需要在运行时检查。检查程序执行的类实例或方法是否关联了特性元数据。代码利用一种反射的技术检测特性的存在。下面结合上述的代码举个例子:
public static void Manage(Student student)
{
Type type = student.GetType();
if (type.IsDefined(typeof(CustomAttribute), true))//检测有没有这个特性
{
object item = type.GetCustomAttributes(typeof(CustomAttribute), true)[0];
foreach (var item in type.GetCustomAttributes(typeof(CustomAttribute), true))
{
CustomAttribute attribute = item as CustomAttribute;
attribute.Show();
}
}
}
上述代码中 从student实例中得到类型信息,用IsDefined 方法检测student 实例有没有关联CustomAttribute 特性。如果只想判断目标是否应用了特性,有IsDefined就够了。如果要构造特性对象,必须调用GetCustomAttributes 方法和 GetCustomAttribute 方法。调用这两个方法会构造指定特性类型的新实例。
调用表中任何一种方法,内部都必须扫描托管模块的元数据,执行字符串比较来定位指定的定制特性类。这肯定会消耗一定时间。假如对性能的要求比较高,可考虑缓存这些方法的调用结果。
两个特性实例的相互匹配
除了判断是否像目标应用了一个特性的实例,可能还需要检查特性的字段来确定它们的值。system.Attribute 重写了obejct 的 Equals方法,会在内部比较两个对象的类型。 可以在自己的定制特性类中重写Equals 来移除反射的使用,从而提升性能。system.Attribute 还公开了虚方法 Match,可重写它来提供更丰富的语义。
检测定制特性时不创建从Attribute派生的对象。
如何用另一种技术检测应用于元数据记录项的特性。这适用于比较安全的场合,这个技术保证不会调用从attribute派生的类中的代码。原因是在调用GetCustomAttribute 方法时要调用特性类的构造器。 可用system.Reflection.CustiomAttributeData类在查找特性的同时禁止执行特性类中的代码。1 先用Assembly 的静态方法 ReflectionOnlyLoad加载程序集,再调用CustomAttributeData 类分析这个程序集中的元数据中的特性。
条件特性
应用了System.Diagnostics.ConditionalAttribute 的特性类称为条件特性类。这个类可以只在符合条件的情况下编译器才生成这个特性。
//#define TEST
#define VERIFY
using System;
using System.Diagnostics;
[Conditional("TEST")][Conditional("VERIFY")]
public sealed class CondAttribute : Attribute {
}
[Cond]
public sealed class Program {
public static void Main() {
Console.WriteLine("CondAttribute is {0}applied to Program type.",
Attribute.IsDefined(typeof(Program),typeof(CondAttribute)) ? "" : "not ");
}
}
编译器如果发现向目标元素应用了 CondAttribute 的实例,那么当含有目标元素的代码编译时,只有在定义 TEST 或VERIFY 符号的前提下,编译器才会在元数据中生成特性信息。不过特性类的定义元数据和实现任存在于程序集中。