阅读目录
官方介绍:使用特性,可以有效地将元数据或声明性信息与代码(程序集、类型、方法、属性等)相关联。 将特性与程序实体相关联后,可以在运行时使用 反射 这项技术查询特性。我个人的理解,特性就是给程序(可以是程序集、类、接口、方法、属性等等)标注一些信息,在使用的时候用反射来读取区分,进行特殊处理,使程序去耦合。可以简单理解为,特性是用来标记一种功能,在另一个地方实现这种功能。使用的时候在一个地方用[]来标注,另一个地方反射读取进行特殊处理,例如MVC、ORM、IOC容器等很多地方都是使用特性来实现。
这里我们使用官方的Obsolete特性来介绍怎么使用的。Obsolete特性是标注一个方法已经过时了,不推荐使用。创建一个类TestClassOne,代码如下
1 // TestClassOne.cs 2 using System; 3 4 namespace Demo02_Attribute 5 { 6 public class TestClassOne 7 { 8 [Obsolete] 9 public void TestMethodOne(string msg) 10 { 11 Console.WriteLine($"TestClassOne.TestMethodOne( {msg} )"); 12 } 13 public void TestMethodTwo(string msg) 14 { 15 Console.WriteLine($"TestClassOne.TestMethodTwo( {msg} )"); 16 } 17 } 18 }
如果你创建TestClassOne类的实例,去调用被Obsolete特性标记的TestMethodOne方法的时候,这个调用的下面会有波浪线提示这个方法是被弃用的。
1 // 使用特性 2 namespace Demo02_Attribute 3 { 4 class Program 5 { 6 static void Main(string[] args) 7 { 8 TestClassOne testClassOne = new TestClassOne(); 9 testClassOne.TestMethodOne("轻轻河边草、悠悠天不老"); 10 testClassOne.TestMethodTwo("野火烧不尽、风雨吹不倒"); 11 Console.ReadKey(); 12 } 13 } 14 }
自定义特性分三步:编写特性类,标记特性,用反射读取特性处理。
1 // 创建自定义特性类 2 using System; 3 4 namespace Demo03_Attribute 5 { 6 public class CustomOneAttribute: Attribute 7 { 8 } 9 }
1 // 标记使用特性 2 namespace Demo03_Attribute 3 { 4 [CustomOneAttribute] //可以简写[CustomOne] 5 public class Student 6 { 7 [CustomOne] 8 public string Name { get; set; } 9 public int Age { get; set; } 10 [CustomOne] 11 public void Sing() 12 { 13 } 14 public void Say() 15 { 16 } 17 } 18 }
1 // 使用反射读取被特性标记的类、方法、属性 2 using System; 3 4 namespace Demo03_Attribute 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Student student = new Student(); 11 var type = student.GetType(); 12 //看看类是否被特性标记 13 if(type.IsDefined(typeof(CustomOneAttribute),true)) 14 { 15 //拿去被标记的特性 16 var attributeArr = type.GetCustomAttributes(typeof(CustomOneAttribute), true); 17 foreach (var attr in attributeArr) 18 { 19 //attr就是每个标记的自定义特性CustomOneAttribute 20 21 } 22 23 } 24 25 //遍历查找被自定义特性标记的方法 26 foreach (var method in type.GetMethods()) 27 { 28 if (method.IsDefined(typeof(CustomOneAttribute), true)) 29 { 30 //method就是被自定义特性标记的 31 var attributeArr = method.GetCustomAttributes(typeof(CustomOneAttribute), true); 32 foreach (var attr in attributeArr) 33 { 34 //attr就是每个标记的自定义特性CustomOneAttribute 35 36 } 37 } 38 } 39 40 //遍历查找被自定义特性标记的属性 41 foreach (var prop in type.GetProperties()) 42 { 43 if (prop.IsDefined(typeof(CustomOneAttribute), true)) 44 { 45 //prop就是被自定义特性标记的 46 var attributeArr = prop.GetCustomAttributes(typeof(CustomOneAttribute), true); 47 foreach (var attr in attributeArr) 48 { 49 //attr就是每个标记的自定义特性CustomOneAttribute 50 51 } 52 } 53 } 54 55 Console.ReadKey(); 56 } 57 } 58 }
向上面这样标记一个特性,通过反射去读取,然后找出来编写附件功能,这样太单一(一个特性只能标记一种),我们还可以把参数保存在特性类中,这样即使标记了同一种特性,也可以根据参数不同而差异化处理。例如下面。
1 // 创建有参数特性 2 using System; 3 namespace Demo03_Attribute 4 { 5 public class CustomTwoAttribute:Attribute 6 { 7 public int age; 8 private string _name; 9 public CustomTwoAttribute(string name) 10 { 11 this._name = name; 12 } 13 } 14 }
1 // 标记使用特性 2 namespace Demo03_Attribute 3 { 4 public class Animal 5 { 6 [CustomTwoAttribute("甜美",age = 7)] 7 public void Jump() 8 { 9 } 10 } 11 }
1 // 使用反射把特性属性读出来 2 using System; 3 4 namespace Demo03_Attribute 5 { 6 class Program 7 { 8 static void Main(string[] args) 9 { 10 Animal animal = new Animal(); 11 var type = animal.GetType(); 12 foreach (var method in type.GetMethods()) 13 { 14 if (method.IsDefined(typeof(CustomTwoAttribute), true)) 15 { 16 //method就是被自定义特性标记的 17 var attributeArr = method.GetCustomAttributes(typeof(CustomTwoAttribute), true); 18 foreach (var attr in attributeArr) 19 { 20 //attr就是每个标记的自定义特性CustomTwoAttribute 21 CustomTwoAttribute customTwoAttribute = (CustomTwoAttribute)attr; 22 23 } 24 } 25 } 26 27 Console.ReadKey(); 28 } 29 } 30 }
有的场合,我们定义的特性只能给特定的对象用,比如类、方法、属性这样分类的,这个时候,我们就需要限制自定义特性使用的环境,这个时候就需要使用 AttributeUsage 来标记自定义特性了。
例如:[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)] //这个特性只能标记在类上面,具有继承性,允许同时标记多次
1 // 特性修饰 2 using System; 3 namespace Demo03_Attribute 4 { 5 //AttributeTargets指定这个特性能修饰程序的哪部分 6 //Inherited指定这个特性子类也被继承 7 //AllowMultiple允许重复修饰 8 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property, Inherited = true, AllowMultiple = true)] 9 public class CustomThreeAttribute:Attribute 10 { 11 } 12 }
看了上面的内容你肯定会感觉空洞乏味,不知所云,是不是向问一句,有没有实际的案例举一个呢。现在举个简单的案例,比如有的时候,我们后台定义枚举类型来定义员工的职位(general manager, director, manager, supervisor, employee),前台显示的时候,需要显示一些更具有亲和力的称呼(总经理、总监、经理、主管、员工)
1 // 定义job特性 2 using System; 3 4 namespace Demo03_Attribute 5 { 6 [AttributeUsage(AttributeTargets.Field)] 7 public class JobAttribute: Attribute 8 { 9 private string _job; 10 public string Job { 11 get { return _job; } 12 } 13 public JobAttribute(string job) 14 { 15 this._job = job; 16 } 17 } 18 }
1 // 后台枚举类中标记Job特性 2 namespace Demo03_Attribute 3 { 4 public enum EnumJob 5 { 6 [Job("总经理")] 7 GeneralManager, 8 [Job("总监")] 9 Director, 10 [Job("经理")] 11 Manager, 12 [Job("主管")] 13 Supervisor, 14 [Job("员工")] 15 Employee 16 } 17 }
1 // 新建特性扩展,用来转换 2 namespace Demo03_Attribute 3 { 4 public class AttributeExtend 5 { 6 public static string GetJob(EnumJob value) 7 { 8 var type = value.GetType(); 9 var field = type.GetField(value.ToString()); 10 if (field.IsDefined(typeof(JobAttribute), true)){ 11 JobAttribute jobAttribute = (JobAttribute)field.GetCustomAttribute(typeof(JobAttribute), true); 12 return jobAttribute.Job; 13 } 14 return value.ToString(); 15 } 16 } 17 }
1 // 前台获取 2 var job = EnumJob.Employee; 3 var jobName = AttributeExtend.GetJob(job); 4 Console.WriteLine($"{job}:{jobName}");
特性 (C#) https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/attributes/
创建自定义特性 (C#) https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/attributes/creating-custom-attributes
编写自定义特性 https://docs.microsoft.com/zh-cn/dotnet/standard/attributes/writing-custom-attributes
使用反射访问特性 (C#) https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/attributes/accessing-attributes-by-using-reflection
检索存储在特性中的信息 https://docs.microsoft.com/zh-cn/dotnet/standard/attributes/retrieving-information-stored-in-attributes
利用特性扩展元数据 https://docs.microsoft.com/zh-cn/dotnet/standard/attributes/
C# 保留的特性:Conditional, Obsolete, AttributeUsage https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/attributes/general
C# 保留的特性:跟踪调用方信息 https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/attributes/caller-information