特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签,可以为程序集、类型,以及类型内部的各种成员添加扩展信息,用于表示一些附加信息。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。通常,表示特性的类都派生自System.Attribute类。下面来看几个特殊的特性:
AttributeUsage
预定义特性 AttributeUsage 描述了如何使用一个自定义特性类。它规定了特性可应用到的项目的类型。
规定该特性的语法如下:
[AttributeUsage(
validon,
AllowMultiple=allowmultiple,
Inherited=inherited
)]
其中:
- 参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All。
- 参数 allowmultiple(可选的)为该特性的 AllowMultiple 属性(property)提供一个布尔值。如果为 true,则该特性是多用的。默认值是 false(单用的)。
- 参数 inherited(可选的)为该特性的 Inherited 属性(property)提供一个布尔值。如果为 true,则该特性可被派生类继承。默认值是 false(不被继承)。
例如:
[AttributeUsage(AttributeTargets.Class| AttributeTargets.Struct| AttributeTargets.Enum| AttributeTargets.Delegate| AttributeTargets.Method| AttributeTargets.Property| AttributeTargets.Field| AttributeTargets.Constructor| AttributeTargets.Event, AllowMultiple=true,Inherited=false)]
再看下面代码:
[Serializable] public class A { }
从上面代码,可以看出,SerializableAttribute特性将应用于A类,在C#中,可以略去“Attribute”,直接写“Serializable”即可。SerializableAttribute类的原型定义如下:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Delegate, Inherited = false)] [ComVisible(true)] public sealed class SerializableAttribute : Attribute { }
从查看SerializableAttribute类的定义我们又发现,特性类除了从Attribute派生外,也可以向其附加特性,而用的最多的就是AttributeUsageAttribute特性,它的定义如下:
[Serializable] [AttributeUsage(AttributeTargets.Class, Inherited = true)] [ComVisible(true)] public sealed class AttributeUsageAttribute : Attribute { }
在定义AttributeUsageAttribute类时也附加了自身作为类的特性,该类指定特性类的适用范围,用AttributeTargets枚举来表示。
如果特性存在带参数的构造函数,可以在特性后用小括号包裹起来,然后传递参数。当然也可以为特性类的属性或字段赋值,也可以同时加多个特性。
自定义特性
定义特性类与定义普通类一样的,既可以声明构造函数、字段、属性、方法等成员,也可以派生子类,但必须从System.Attribute类或System.Attribute的子类派生。
例如:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property)] public class MyAttribute : Attribute { public string Title { get; set; } public string VerNo { get; set; } } //特性用于类 [My(Title="draw" ,VerNo="1.0.2")] public class Drawer { //特性用于属性 [My(Title = "color", VerNo = "1..0.2")] public string Color { get; set; } //特性用于方法 [My(Title = "color", VerNo = "1.0.2")] public void Draw() { } //特性用于字段,编译错误 [My(Title = "thick", VerNo = "1.0.2")] public int a; }
注意,当MyAttribute特性用于字段时,会发生编译错误,因为MyAttribute类在定义是已经指明它只能用于类、方法、属性,但并未指明其可用于字段。
在默认条件下,特性将应用于更随其后的对象,同理,也可以为方法中的参数应用特性。如下代码所示:
public static string Run([In]string pt,[Optional]int x) { return string.Empty; }
为参数应用特性秩序放在参数前面即可。但是,如果要为返回值应用特性,那么是不是把特性放在返回值前面就可以了呢?就像这样:
public [MarshalAs(UnmanagedType.SysInt] int Compute(){}
这样做是错误的,编译无法通过,那又如何实现呢?
在前面也提到,在默认的情况下,特性是应用于跟随其后的对象的,因此,在许多时候,使用特性是都会省略了表示特性目标的关键字。以下是特性应用于目标对象时的完整格式:
[<目标>:<特性列表>]
下面列举特性目标关键字及相关说明:
assembly:表示特性将应用于当前程序集,通常放在程序集中命名空间或所有类型定义之前。
module:用于当前模块。
field:用于字段,如果特性后紧跟着字段的声明代码,则该关键字可以省略。
event:用于事件。
method:用于方法,也可以用于属性中的get和set访问器。
param:用于方法中的参数或属性定义中的set访问器中的参数(value)。
property:用于属性。
type:用于类型。
return:用于方法的返回值,属性中的get访问器的返回值。
通过上面的介绍,相信你已经知道如何为方法的返回值应用特性了。即将特性应用到方法上,并且注明特性的应用目标为return。如下面代码所示:
[return: MarshalAs(UnmanagedType.SysInt)] public int Compute() { return 0; }
通过反射技术检索特性
下面将介绍如何查找特性,需要用到反射技术。前面讲过,特性可以理解为附加在类型上的扩展信息,可以通过在类型中找到指定的特性来验证代码的调用方法是否符合特定的要求。
下面看个例子即可:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace My 8 { 9 [AttributeUsage(AttributeTargets.All)] 10 public class TypeInfoAttribute : Attribute 11 { 12 public string Description { get; set; } 13 } 14 15 [TypeInfo(Description = "这是我们定义的枚举类型。")] 16 enum TestEnum { One = 1, Two, Three } 17 18 [TypeInfo(Description = "这是我们定义的一个类。")] 19 public class Goods { } 20 21 class Program 22 { 23 static void Main(string[] args) 24 { 25 // 用Type类的GetCustomAttributes方法可以获取指定类型上附加的特性列表 26 // 返回一个object类型的数组,数组中的每个元素表示一个特性类的实例 27 // GetCustomAttributes方法的其中一个重载可以将一个Type作为参数传递 28 // 该Type表示要获取的特性的类型,typeof运算符返回某个类型的一个Type 29 // 本例中我们要获取TypeInfoAttribute特性列表 30 // 由于上面定义TestEnum枚举和Goods类时,只应用了一个TypeInfoAttribute特性 31 // 因此获取到的特性实例数组的元素个数总为1 32 33 object[] attrs = typeof(TestEnum).GetCustomAttributes(typeof(TypeInfoAttribute), false); 34 if (attrs.Length > 0) 35 { 36 TypeInfoAttribute ti = (TypeInfoAttribute)attrs[0]; 37 Console.WriteLine("TestEnum枚举的描述信息:{0}", ti.Description); 38 } 39 40 attrs = typeof(Goods).GetCustomAttributes(typeof(TypeInfoAttribute), false); 41 if (attrs.Length > 0) 42 { 43 TypeInfoAttribute ti = (TypeInfoAttribute)attrs[0]; 44 Console.WriteLine("Goods类的描述信息:{0}", ti.Description); 45 } 46 47 Console.Read(); 48 } 49 } 50 }
结果:
TestEnum枚举的描述信息:这是我们定义的枚举类型。
Goods类的描述信息:这是我们定义的一个类。
通过以上的介绍,相信你已经对特性有了一定的了解。博客写到这里,我也不啰嗦了。