1.Attribute的定義與作用:
公共語言運行時允許你添加類似關鍵字的描述聲明,叫做attributes, 它對程序中的元素進行標注,如類型、字段、方法和屬性等。Attributes和Microsoft .NET Framework文件的元數據保存在一起,可以用來向運行時描述你的代碼,或者在程序運行的時候影響應用程序的行為.比如在WCF中常用的序列化。比如添加的[DataMenber]就是一種標簽。以及xml序列化也是一樣的。
2.Reflect的定義與作用
反射可以用于觀察并修改程序在運行時的執行。一個reflection-oriented程序組件可以監測一個范圍內的代碼執行情況,可以根據期望的目標與此相關的范圍修改本身。這通常是通過在運行時動態分配程序代碼實現。在面向對象的編程語言如java中,反射允許在編譯期間不知道接口的名稱,字段、方法的情況下在運行時檢查類、接口、字段和方法。它還允許的實例化新對象和調用的方法。(摘抄至維基百科). 其實可以簡單理解為,通過反射可以很輕易的獲取到一個類中的所有方法屬性字段狀態。
3.為了更方便的認識反射以及Attribute的應用,下面有個例子.
業務背景如下: 有個Person對象,目前只有Name和Num屬性,然后要用方法校驗Person對象的合法性。
判斷規則: 1.Name必填,若Name為空,則提示"Name不能為空";
2.Num可以為空,但是若有數據的話必須由數字組成,若為非數字字符串則彈出"格式不正確".
3.Address可以為空,若有數據的話長度不能超過12,若超過12則提示"Address 長度不能超過12"
需要考慮: 對于Person類,以后熟悉的判斷規則可能會變化,也可能增加屬性,減少屬性。
4.解決方案一:
對于常規解決方案,可能大家會想到。目前只有Name,Address,Num三個屬性,每次我就寫三個代碼段分別對Name,Address,Num驗證。如果增加屬性, 比如增加了公共屬性EmailAddress,那么就必須添加驗證字符串是否是郵箱的代碼,若刪除了某個屬性,就吧判斷該屬性是否合格的代碼刪除掉不進行判斷。這個也是我最開始想到的方法。不過這樣不好的地方是可維護性不好。改動太大了。而且會導致重復代碼。不如如果需求改成Address不能為空了。那么在判斷address是否合法的時候還要去添加判斷不能為空的情況。
5.解決方案二:
為了解決方案一的問題,其實我們可以通過Attribute和反射來實現。思路如下:
5.1 首先我們創建一個PersonCheckAttribute對象,他繼承與System.Attribute.
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ReflectTest.Model{ public class PersonCheckAttribute:Attribute { // 三個bool變量用于確定是有要驗證這些信息 PRivate bool checkEmpty = false; //是否為空 private bool checkMaxLength = false; //最大長度 private bool checkRegex = false; //用正則表達式驗證參數(是郵箱,是否刷數字) private int maxLength = 0; private string regexStr = string.Empty; public bool CheckEmpty { get { return this.checkEmpty; } set { this.checkEmpty = value; } } public bool CheckMaxLength { get { return this.checkMaxLength; } set { this.checkMaxLength = value; } } public bool CheckRegex { get { return this.checkRegex; } set { this.checkRegex = value; } } public int MaxLength { get { return this.maxLength; } set { this.maxLength = value; } } public string RegexStr { get { return this.regexStr; } set { this.regexStr = value; } } }}
5.2 然后我們來添加Person類。注意里面的標簽。
Tips:我在里面打了標簽,對于5.1的PersonCheckAttribute類中bool變量用來確定是否要進行驗證,公共屬性用來存取判斷參數。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;namespace ReflectTest.Model{ public class Person { [PersonCheck(CheckEmpty=true)] public string Name { get; set; } [PersonCheck(CheckRegex = true, RegexStr = "^[0-9]*[1-9][0-9]*$")] public string Num { get; set; } [PersonCheck(CheckMaxLength = true, MaxLength = 12)] public string Address { get; set; } }}
5.3 屬性已經搞定,下面來看看,如何通過反射來獲取類的公共屬性,以及每個屬性的Attribute。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using ReflectTest.Model;using System.Reflection;using System.Text.RegularExpressions;namespace ReflectTest.Business{ public class PersonCheck { // 獲取所有的錯誤提示 public static string GetErrorMessage(Person person) { PropertyInfo[] propertyInfos = person.GetType().GetProperties(); // 獲取一個類的所有屬性 string errorMsg = string.Empty; //對所有公用屬性(Name,Num,Address)遍歷 foreach (PropertyInfo info in propertyInfos) { errorMsg += GetSignalPropertity(info, person); } return errorMsg; } // 獲取單個公共屬性的Attribute.也就是model類里面的[PersonCheck]信息 private static string GetSignalPropertity(PropertyInfo propertyInfo,Person person) { // 因為對于此示例,每個Properties(屬性)只有一個Attribute(標簽),所以用了first()來獲取, // 不過有一點,就是必須在屬性里面添加[PersonCheck]標簽,但是可以不設置表情里面的字段.因為沒有的.GetCustomAttributes()返回為null.指向first會報錯. // PersonCheckAttribute attribute = propertyInfo.GetCustomAttributes().First() as PersonCheckAttribute; string errorMsg = string.Empty; //以下的if語句是判斷標簽里面的設置,設置了什么就執行什么數據校驗 PersonCheckAttribute attribute = propertyInfo.GetCustomAttribute(typeof(PersonCheckAttribute)) as PersonCheckAttribute; if (attribute != null) { if (attribute.CheckEmpty) { string obj = propertyInfo.GetValue(person) as string; if (string.IsNullOrEmpty(obj)) { errorMsg += Environment.NewLine + string.Format("{0} 不能為空", propertyInfo.Name); } } if (attribute.CheckMaxLength) { string obj = propertyInfo.GetValue(person) as string; if (obj != null && obj.Length > attribute.MaxLength) { errorMsg += Environment.NewLine + string.Format("{0} 不能超過最大程度{1}", propertyInfo.Name, attribute.MaxLength); } } // 對于判斷數字郵箱都可以通過正則表達式 if (attribute.CheckRegex) { string obj = propertyInfo.GetValue(person) as string; Regex regex = new Regex(attribute.RegexStr); if (obj != null && !regex.IsMatch(obj)) { errorMsg += Environment.NewLine + string.Format("{0} 格式不對", propertyInfo.Name); } } } return errorMsg; } }}
5.4 下面來看運行結果。
5.4.1 若Person p = new Person(){Num=“abc”};調用GetErrorMessage(Person person)方法后運行結果如下。Person不能為空.

5.4.2 若Person p = new Person(){Num=“123”,Address=“chengdu,Tianfu SoftwarePark”};調用GetErrorMessage(Person person)方法后運行結果如下。Person不能為空.

6.分析反射的好處:
如果需求修改,Address不能為空了,我只需要在Address的標簽里面設置CheckEmpty=true,而不需要重新修改邏輯代碼。同理,如果要給Person類添加屬性EmailAddress,那么只需要在Person類里面添加屬性EmailAddress設置標簽為【PersonCheck(CheckRegexStr = true,regexStr="*")】,*未判斷是否是合法有效的正則表達式即可。
7.總結:
其實小弟也是剛接觸反射。有很多東西也是一知半解。目前對反射的認識也就這么多,有其他大牛應用更多這方面的,如果有什么好的想法和資料也請大家分享一下,相互學習,相互提升。代碼鏈接:https://github.com/FourLeafClover/AttributeAndReflect
新聞熱點
疑難解答