特性【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的方式】