前言
本篇文章旨在认识C# 中的特性,知道什么是特性,特性的作用,如何使用特性,如何自定义特性
什么是特性
特性即Attribute,可以宣告式地为自己的代码添加注解来实现某些特殊的功能。 它们的把一些附加的信息同目标关联起来,这个目标可以是类,可以是方法,可以是枚举,等等。编译器检查到代码中的特性后,会为其生成对应的元数据。
在.Net的类库中提供了非常多的特性例如比较常见的
[DllImport] 、[Serializable] 、[StructLayout]
如何使用特性
使用方括号,把特性名括起来放到应用的目标前方,特性类名后缀“Attribute”可以省略,例如:
[DllImport("MyLib", CharSet=CharSet.Auto, SetLastError=true)]
public static extern string GetVersion();
如果没有参数则不需要写括号。
特性的本质
特性其实就是一个类的实例,因此需要这个类至少拥有一个公共的构造函数。这个类要派生自System.Attribute
定义自己的特性类
可以把特性想像成一个逻辑状态容器,不需要很复杂,只需要一个公共构造器来接收特性的强制性(或定位性)状态信息,可提供公共字段或属性以接收特性的可选状态信息。类不应提供任何公共方法、事件或其他成员。
实际测试代码:
如果把默认构造器私有化则会报上面的错误
设定特性的合法使用范围
告诉编译器,这个某个特性只能给某个或几个类型元素使用, 例如只能应用于Class和Method,这个时候就需要使用AttributeUsageAttribute了,这个特性作用于我们自定义的特性类,即可限制我们的自定义类的使用范围。
它有三个参数:1.目标类型(AttributeTarget) 2.是否支持重复(AllowMultiple) 3.是否可被子类继承(Inherited)
AttributeTargets是一个位标志枚举类型,见下方代码:
/// <summary>Specifies the application elements on which it is valid to apply an attribute.</summary>
[Flags]
[ComVisible(true)]
[__DynamicallyInvokable]
[Serializable]
public enum AttributeTargets
{
/// <summary>Attribute can be applied to an assembly.</summary>
[__DynamicallyInvokable] Assembly = 1,
/// <summary>Attribute can be applied to a module.</summary>
[__DynamicallyInvokable] Module = 2,
/// <summary>Attribute can be applied to a class.</summary>
[__DynamicallyInvokable] Class = 4,
/// <summary>Attribute can be applied to a structure; that is, a value type.</summary>
[__DynamicallyInvokable] Struct = 8,
/// <summary>Attribute can be applied to an enumeration.</summary>
[__DynamicallyInvokable] Enum = 16, // 0x00000010
/// <summary>Attribute can be applied to a constructor.</summary>
[__DynamicallyInvokable] Constructor = 32, // 0x00000020
/// <summary>Attribute can be applied to a method.</summary>
[__DynamicallyInvokable] Method = 64, // 0x00000040
/// <summary>Attribute can be applied to a property.</summary>
[__DynamicallyInvokable] Property = 128, // 0x00000080
/// <summary>Attribute can be applied to a field.</summary>
[__DynamicallyInvokable] Field = 256, // 0x00000100
/// <summary>Attribute can be applied to an event.</summary>
[__DynamicallyInvokable] Event = 512, // 0x00000200
/// <summary>Attribute can be applied to an interface.</summary>
[__DynamicallyInvokable] Interface = 1024, // 0x00000400
/// <summary>Attribute can be applied to a parameter.</summary>
[__DynamicallyInvokable] Parameter = 2048, // 0x00000800
/// <summary>Attribute can be applied to a delegate.</summary>
[__DynamicallyInvokable] Delegate = 4096, // 0x00001000
/// <summary>Attribute can be applied to a return value.</summary>
[__DynamicallyInvokable] ReturnValue = 8192, // 0x00002000
/// <summary>Attribute can be applied to a generic parameter.</summary>
[__DynamicallyInvokable] GenericParameter = 16384, // 0x00004000
/// <summary>Attribute can be applied to any application element.</summary>
[__DynamicallyInvokable] All = GenericParameter | ReturnValue | Delegate | Parameter | Interface | Event | Field | Property | Method | Constructor | Enum | Struct | Class | Module | Assembly, // 0x00007FFF
}
AllowMultiple如果不为true,那么对于同一个目标,一个特性只能应用一次
Inherited如果是true,则会将特性应用于派生类和重写的方法
如果我们不给自定义特性添加AttributeUsage,则默认为针对所有目标元素都可以使用该特性。
让特性活起来
检测定制特性
光定义特性没用,还要定义这些特性的处理逻辑,这免不了要解决一个问题就是如何发现目标身上的特性,我们可以通过反射来实现。
System.Reflection.CustomAttributeExtensions
有三个静态方法:IsDefined、GetCustomAttributes、GetCustomAttribute
后两个还支持泛型获取:
如果只想判断某个目标是否应用了某个特性,则使用IsDefined是最优解,但如果要构造特性对象,则需要使用后面两个了。每次调用GetCustomAttribute和GetCustomAttributes的时候都会构造指定特性类型的实例,并根据源代码中的指定值来设置特性的属性。
System.Reflection定义了几个类允许检查模块的元数据,包括Assembly、Module、ParameterInfo、MemberInfo、Type、MethodInfo、ConstructorInfo、FieldInfo、EventInfo、PropertyInfo以及其各自的*Builder类,这些类都提供了IsDefined和GetCustomAttributes方法。
参考书籍:CLR via C# 第4版