特性【Attribute】是什么?
概念:1. 特性AttriBute:就是一个类,能直接继承/间接继承自AttriBute父类;
2. 约定俗成用Attribute结尾,标记时就可以省略,eg:[CustomAttribute] ---> [Custom];
3. 可以用中括号包裹,标记到元素,其实就是调用构造函数【如果父类用了带参数的构造函数,特性调用只能改为以下结构---->[Custom(0)]】;
4. 然后可以指定属性,字段,修饰参数,返回值,但是方法不可以;
特性无处不在,比如我们经常用在元素上面添加的,[ ] 中括号形式的东西,基本上,我们在工作中遇到的各种的框架里面都有eg :EF-MVC-WCF-Webservice-UniTest-IOC-AOP-SuperSocket,
如果,添加了[Serializable],就表示这个元素可以序列化,那特性究竟是什么呢?我们不妨按F12点进去看看,
可以看到这个特性SerializableAttribute就是一个类,继承于Attribute抽象类,那我们自己动手写一个试试
public class CustomAttribute : Attribute { }
[CustomAttribute] public class Student { public int Id { get; set; } public string Name { get; set; } public void Study() { Console.WriteLine($"{this.Name}"); } public string Answer(string name) { return $"This is {name}"; } }
我们同时又可以修改为以下格式:说明了什么? 说明:用Attribute结尾,标记时就可以省略,eg:[CustomAttribute] ---> [Custom]
那我们再进一步修改试试,继承CustomAttribute类,调用看看:
public class CustomAttribute : Attribute { } public class CustomAttriButeChild : CustomAttribute { }
那我们是不是就可以得到一个:【特性AttriBute:就是一个类,直接继承/间接继承自AttriBute父类】的结论
那特性既然是一个类,那它里面又可以放什么东西呢?
1.无参构造函数;
public class CustomAttribute : Attribute { public CustomAttribute() { Console.WriteLine("这是一个无参数构造函数"); } } public class CustomAttriButeChild : CustomAttribute { }
2.int 类型的参数
public class CustomAttribute : Attribute { public CustomAttribute(int Id) { Console.WriteLine("如果只有当前的这个构造函数,继承当前父类的子类会报错,why?"); } } public class CustomAttriButeChild : CustomAttribute { public CustomAttriButeChild() : base(123) { Console.WriteLine("继承父类的子类报错,因为它继承了父类,但是它只有一个带参数的构造函数,那么调用也必须显示指定调用"); } }
3.无参,int,string 同时存在的情况呢?
public class CustomAttribute : Attribute { public CustomAttribute() { Console.WriteLine("这是一个无参数构造函数"); } public CustomAttribute(int Id) { Console.WriteLine("如果只有当前的这个构造函数,继承当前父类的子类会报错,why?"); } public CustomAttribute(string name) { Console.WriteLine("string类型的构造函数"); } } public class CustomAttriButeChild : CustomAttribute { public CustomAttriButeChild() : base(123) { Console.WriteLine("继承父类的子类报错,因为它继承了父类,但是它只有一个带参数的构造函数,那么调用也必须显示的指定调用"); } }
这是分开调用特性的情况,那我们一起调用呢?
[Custom(0)]--- [Custom]---[Custom()]上面三个分开都可以调用,但是如果同时调用就会提示特性重复,默认情况不允许,那么我怎么可以做到同时使用多个特性呢?我们加"[AttributeUsage]"特性试试
加上这个[AttributeUsage]特性之后编译器,就没有在显示特性重复,是不是说明这个特性影响编译器,
我们进去看看它里面都有些什么元素,
AttributeTargets.All,表示可以修饰任何目标元素 ,那我们更换一个呢?
为什么会报错??因为AttributeTargets.Method----->只能用来修饰方法
那我们希望它又可以修饰方法,又可以修饰属性,又可以修饰类呢?
[AttributeUsage]特性,影响编译器,它能-----指定修饰的对象------能否重复修饰---修饰的特性子类是否继承 ---> [AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]
特性还可以指定属性,字段
public class CustomAttribute : Attribute { public CustomAttribute() { } public CustomAttribute(string name) { } public CustomAttribute(int Id) { } public string Remake; public string Description { get; set; } public void Show() { } }
同时字段还能修饰参数,返回值
/// <summary> /// 特性在方法的参数前面,用来修饰参数的 /// [return:Custom]还可以修饰返回值 /// </summary> [return: Custom] public string Answer([Custom]string name) { return $"This is {name}"; }
特性多重修饰写法:
1 //方法一: 2 [Custom()] 3 [CustomAttriButeChild] 4 [Custom(0) ] 5 [Custom(0, Remake= "字段")]//构造函数的传递方式:是直接传值,字段需要带Remake 6 [Custom(0,Remake ="1115",Description = "属性")]
1 //方式二: 2 [return: Custom, Custom, Custom(0), Custom(0, Remake = "1115", Description = "属性")]
那问题来了,看了这么多,特性到底有什么用???让我们接着往下面探讨
//程序入口 static void Main(string[] args) { Student student = new Student() { Id = 1, Name = "Attribute" }; student.Study(); student.Answer(""); }
跟踪发现写了那么多特性根本就没什么用,我们自定义的特性,看起来好像毫无意义的样子,那框架提供的特性究竟是怎么产生价值的呢??
那我们新建一个studentVip类反编译看看:
public class StudentVip : Student { public string VipGroup { get; set; } public void DoHomeWork() { } }
编译结果展示:
我们加上我们自定义的特性反编译试试:
[Custom("123",Remake ="VIP",Description ="Hello!")] public class StudentVip : Student { [Custom("123", Remake = "VIP", Description = "Hello!")] public string VipGroup { get; set; } [Custom("123", Remake = "VIP", Description = "Hello!")] public void DoHomeWork() { } }
反编译之后得到的结果:
反编译之后,发现特性会在元素的内部生成.custom的东西,那我们看一下框架里面的特性,加上编译以后又有什么变化呢?
框架特性也是一样,我们C#访问不到,是不是可以理解为特性没有产生任何变化,但框架究竟是怎么产生功能的呢?也就是怎么在程序运行的时候,能够找到特性的呢?---反射
我们如何在程序运行中用反射去找到特性?可以从类型 属性 方法 都可以获取特性实例,先IsDefined判断检测,通过反射在构造实例,再获取(实例化)
我们新建一个InvokeCenter类来看看:
public class InvokeCenter { /// <summary> /// 一定要先IsDefined判断检测,通过反射在构造实例,再获取 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="student"></param> public static void MangerStudent<T>(T student) where T : Student { //打印属性 Console.WriteLine($"{student.Id}_{student.Name}"); student.Study(); student.Answer("123"); Type type = student.GetType(); //检查特性是否存在 if (type.IsDefined(typeof(CustomAttribute), true)) { //获取列表找出全部,也可以只找一个type.GetCustomAttribute--这种方式使用场景比较多 Object[] oAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true); foreach (CustomAttribute attribute in oAttributeArray) { attribute.Show(); } //循环所有的属性 foreach (var prop in type.GetProperties()) { //如果这个属性包含这个特性 //那么我们就获取到包含这个特性属性的列表,它是这个数组集合 if (type.IsDefined(typeof(CustomAttribute), true)) { Object[] OAttributeProp = type.GetCustomAttributes(typeof(CustomAttribute), true); foreach (CustomAttribute attribute in OAttributeProp) { attribute.Show(); } } } //把所有的方法找出来 foreach (var method in type.GetMethods()) { //判断是否具有特性 if (type.IsDefined(typeof(CustomAttribute), true)) { Object[] oAttributeMethod = type.GetCustomAttributes(typeof(CustomAttribute), true); foreach (CustomAttribute attribute in oAttributeMethod) { attribute.Show(); } } } } } }
//前面我们自定义的CustomAttribute特性的部分代码修改 [AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)] public class CustomAttribute : Attribute { public CustomAttribute() { Console.WriteLine($"{this.GetType().Name} 无参数构造函数执行"); } public CustomAttribute(string name) { Console.WriteLine($"{this.GetType().Name} string参数构造函数执行"); this._Name = name; } public CustomAttribute(int Id) { Console.WriteLine($"{this.GetType().Name} int参数构造函数执行"); this._Id = Id; } private int _Id = 0; private string _Name = ""; public string Remake; public string Description { get; set; } public void Show() { Console.WriteLine($"{this._Id}_{this._Name}_{this.Remake}_{this.Description}"); } }
//程序入口调用跟踪 static void Main(string[] args) { { Student student = new StudentVip() { Id = 2, Name = "特性" }; InvokeCenter.MangerStudent<Student>(student); } }
跟踪的结果展示:以及为什么会有对应条数截图的说明:
结论:程序运行时可以找到特性,那就可以发挥特性的作用,提供额外的信息,行为,特性本身是没有用的,需要一个第三方InvokeCenter,在这里主动检测并提供特性,才能提供功能,
那么框架的特性方式也是一样的,框架里面已经集成完,自己去检测特性,另外,特性是在编译前就已经确定好了,构造函数/属性/字段,都不能用变量
【所以MVC5-filter 是不能注入的,只有在core里面才提供了注入filter的方式】