1、什么是特性
特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用反射这项技术查询特性。
特性具有以下属性:
- 特性向程序添加元数据。 元数据是程序中定义的类型的相关信息。 所有 .NET 程序集都包含一组指定的元数据,用于描述程序集中定义的类型和类型成员。 可以添加自定义特性来指定所需的其他任何信息。
- 可以将一个或多个特性应用于整个程序集、模块或较小的程序元素(如类和属性)。
- 特性可以像方法和属性一样接受自变量。
- 程序可使用反射来检查自己的元数据或其他程序中的元数据。
2、如何使用特性和声明特性
2.1 使用特性
C#语言中内置了许多特性,比如SerializableAttribute、ObsoleteAttribute等,前者标识类可以被序列化,后者标识过时的。.NET有一个约定,所有的特性应该都是以Attribute来结尾,在标记的时候,如果没有加Attribute,编译器会自动寻找带有Attribute的版本,所以在标记的时候我们可以省略Attribute。下面我们使用一下这两个特性。
[Serializable] public class AttributeTest { [Obsolete("Don't use Old method, use New method.")] public void OldMethod() { } public void NewMethod() { } }
如上代码,标识了[Obsolete]特性的方法,在编译时就会有警告: 'AttributeTest.OldMethod()' is obsolete: 'Don't use Old method, use New method.' 说明该特性是直接影响了编译器的。而标识了[Serializable]特性的对象可以被序列化,是影响了程序的运行。
2.2 声明特性
C#提供了System.Attribute类,所有特性类都应改直接活间接继承自System.Attribute类。接下来我们自定义一个特性类。
public class CustomAttribute : Attribute { public CustomAttribute() { } public int Id { get; set; } public string Name { get; set; } public string Remark { get; set; } }
很简单吧,上面我们就定义了一个特性类,下面我们使用一下自定义的特性。像上面用系统预定义的特性类一样,我们给NewMethod方法加上我们自定义的特性。
[Serializable] public class AttributeTest { [Obsolete("Don't use Old method, use New method.")] public void OldMethod() { } [Custom(Id = 1, Name = "NewMethod", Remark = "This is a NewMethod.")] public void NewMethod() { } }
3、控制特性的使用
上面我们说了如何定义和使用特性,但有点小问题,如果我们声明的特性只想标记字段和属性,应该怎么办?同一个特性我们想标记两遍怎么办?带着问题我们继续往下看。
3.1 AttributeUsage的定义
AttributeUsage
用于描述可以在其中使用特性类的方式。可以F12看一下这个类,它的全名是AttributeUsageAttribute,说明它也是一个特性类,所以简单直白的来说它就是用来描述特性的特性。
3.2 AttributeUsage的介绍
它里面有三个属性:
- ValidOn:标识指示的属性可应用到的程序元素。默认值是 AttributeTargets.All。
- AllowMultiple:指示能否为一个程序元素指定多个指示属性实例。如果为 true,则该特性是可以重复标记的。默认值是 false。
- Inherited:指示指示的属性能否由派生类和重写成员继承。如果为 true,则该特性可被派生类继承。默认值是 false。
- ValidOn:标识指示的属性可应用到的程序元素。默认值是 AttributeTargets.All。
3.3AttributeUsage的使用
介绍了AttributeUsage,下面我们来解决一下开头提的两个问题;如果项目中有不同的需求,可以根据需求来定义。
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true, Inherited = true)] public class CustomAttribute : Attribute { public CustomAttribute() { } public int Id { get; set; } public string Name { get; set; } public string Remark { get; set; } }
4、通过反射获取特性
看完上面的内容,相信大家都会有些疑惑,我声明了特性,也标记了特性,可它没起什么作用,难道仅仅是为了好看。接下来我们演示如何获取特性,当然,获取到特性后就想做什么都可以啦。
4.1 获取特性
特性必须通过反射进行获取、使用。下面代码演示了获取类、方法、属性上的特性,并调用特性方法。
1 public static void Manager<T>(this T test) where T : AttributeTest 2 { 3 Type type = test.GetType(); 4 5 // 查找类上的特性 6 if (type.IsDefined(typeof(CustomAttribute), true)) 7 { 8 object[] oAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true); 9 foreach (CustomAttribute attribute in oAttributeArray) 10 { 11 attribute.Show(); 12 } 13 } 14 15 // 查找属性上的特性 16 foreach (var prop in type.GetProperties()) 17 { 18 if (prop.IsDefined(typeof(CustomAttribute), true)) 19 { 20 object[] oAttributeArrayProp = prop.GetCustomAttributes(typeof(CustomAttribute), true); 21 foreach (CustomAttribute attribute in oAttributeArrayProp) 22 { 23 attribute.Show(); 24 } 25 } 26 } 27 28 // 查找方法上的特性 29 foreach (var method in type.GetMethods()) 30 { 31 if (method.IsDefined(typeof(CustomAttribute), true)) 32 { 33 object[] oAttributeArrayMethod = method.GetCustomAttributes(typeof(CustomAttribute), true); 34 foreach (CustomAttribute attribute in oAttributeArrayMethod) 35 { 36 attribute.Show(); 37 } 38 } 39 } 40 41 }
5、实际应用
上面说了这么多,可是我还是不会用,咋办呢。下面列出一个实际应用的小栗子。程序中,我们经常会用到枚举,通过枚举值显示文字的时候,通常会if(值){文字},下面我们用特性来获取枚举值对应的文字。
5.1 准备一个枚举
1 /// <summary> 2 /// 用户状态 3 /// </summary> 4 public enum UserState 5 { 6 /// <summary> 7 /// 正常状态 8 /// </summary> 9 [Remark("正常状态")] 10 Normal = 0, 11 /// <summary> 12 /// 已冻结 13 /// </summary> 14 [Remark("已冻结")] 15 Frozen = 1, 16 /// <summary> 17 /// 已删除 18 /// </summary> 19 [Remark("已删除")] 20 Deleted = 2 21 }
5.2 显示备注
public static string GetRemark(this Enum value) { Type type = value.GetType(); var field = type.GetField(value.ToString()); if (field.IsDefined(typeof(RemarkAttribute), true)) { RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true); return attribute.Remark; } else { return value.ToString(); } }
用法:
EnumTest.UserState userState = EnumTest.UserState.Deleted; var remark = userState.GetRemark(); Console.WriteLine(remark);
6、总结
特性可以说是无处不在,EF、MVC、WCF、IOC、AOP等等都会用到特性。特性可以在不修改类的情况下对类新增一些功能。