C#高階程式設計之特性
特性定義
MSDN的描述:使用特性,可以有效地將元資料或宣告性資訊與程式碼(程式集、型別、方法、屬性等)相關聯。將特性與程式實體相關聯後,可以在執行時使用反射這項技術查詢特性。
參考此處作者的解釋
https://www.cnblogs.com/chenxizhaolu/p/9497768.html
1.特性就是為了支援物件新增一些自我描述的資訊,不影響類封裝的前提新增額外資訊。如果你用這個資訊,那特性就有用;如果你不需要這個資訊,那麼這個特性就沒用。
2.特性的基類:Attribute。例如:Obsolete特性,提出警告資訊或錯誤資訊,特性可以影響編譯、影響執行。
3.特性類通常用Attribute結尾,在使用的時候可以用全稱,也可以去掉這個結尾,也可以加上小括號顯示呼叫建構函式,如果不加小括號預設呼叫無參建構函式,也可以在括號內直接給屬性或欄位賦值。
4.特性往往只能修飾一個物件一次,需要設定屬性的屬性的時候,需要給屬性新增AttributeUsage屬性,可以用來設定:是否允許多次修飾、修飾物件的類別(類or欄位等)
5.DLL檔案=IL中間語言+metadata元資料,特性資訊會被編譯到元資料中。我們可以通過使用ILSpy工具看到具體資訊。
特性的使用
使用場景1:
這裡以通過特性獲取類或成員新增描述資訊,然後在使用的時候拿到該資訊為例:
第一步:定義一個特性類。
public class CustomAttribute : Attribute { public CustomAttribute() { Console.WriteLine("無引數建構函式"); } public CustomAttribute(int i) { Console.WriteLine("int 型別的建構函式"); } public void Show() { Console.WriteLine("通過反射呼叫特性中的方法"); } }
第二步:建立特性類例項,【裡面包含著驗證指定粒度的Model模型(型別、屬性、方法)需要的資料,後面資料驗證場景會講到】
此處只是Model的不同粒度上附加資料。這裡分別將特性附加於型別、屬性和方法上。
public class Student { public string Id { get; set; } public string Name { get; set; } public void Answer(string message) { Console.WriteLine(message); } }
[Custom(2018)] class StudentVIP:Student { [Custom(2019)] public string VIPGroup { get; set; } [Custom(2020)] public void HomeWork() { Console.WriteLine("Homework"); } public long Salary { get; set; } }
通過ILSpy反編譯工具可以看到:標記了特性的元素,都會在元素內部生成一個.custom,但是C#不能在元素內部呼叫
.class private auto ansi beforefieldinit LearningAttribute.StudentVIP extends LearningAttribute.Student { .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = ( 01 00 e2 07 00 00 00 00 ) // Fields .field private string '<VIPGroup>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) .field private int64 '<Salary>k__BackingField' .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) .custom instance void [mscorlib]System.Diagnostics.DebuggerBrowsableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggerBrowsableState) = ( 01 00 00 00 00 00 00 00 ) // Methods .method public hidebysig specialname instance string get_VIPGroup () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2441 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld string LearningAttribute.StudentVIP::'<VIPGroup>k__BackingField' IL_0006: ret } // end of method StudentVIP::get_VIPGroup .method public hidebysig specialname instance void set_VIPGroup ( string 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2449 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld string LearningAttribute.StudentVIP::'<VIPGroup>k__BackingField' IL_0007: ret } // end of method StudentVIP::set_VIPGroup .method public hidebysig instance void HomeWork () cil managed { .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = ( 01 00 e4 07 00 00 00 00 ) // Method begins at RVA 0x2452 // Code size 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "Homework" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method StudentVIP::HomeWork .method public hidebysig specialname instance int64 get_Salary () cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2460 // Code size 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldfld int64 LearningAttribute.StudentVIP::'<Salary>k__BackingField' IL_0006: ret } // end of method StudentVIP::get_Salary .method public hidebysig specialname instance void set_Salary ( int64 'value' ) cil managed { .custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) // Method begins at RVA 0x2468 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldarg.1 IL_0002: stfld int64 LearningAttribute.StudentVIP::'<Salary>k__BackingField' IL_0007: ret } // end of method StudentVIP::set_Salary .method public hidebysig specialname rtspecialname instance void .ctor () cil managed { // Method begins at RVA 0x2471 // Code size 8 (0x8) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void LearningAttribute.Student::.ctor() IL_0006: nop IL_0007: ret } // end of method StudentVIP::.ctor // Properties .property instance string VIPGroup() { .custom instance void LearningAttribute.CustomAttribute::.ctor(int32) = ( 01 00 e3 07 00 00 00 00 ) .get instance string LearningAttribute.StudentVIP::get_VIPGroup() .set instance void LearningAttribute.StudentVIP::set_VIPGroup(string) } .property instance int64 Salary() { .get instance int64 LearningAttribute.StudentVIP::get_Salary() .set instance void LearningAttribute.StudentVIP::set_Salary(int64) } }
第三步:使用特性類例項。
將特性與程式實體相關聯後,利用反射來獲取附加在這些Model上的資料。一般是傳入這個實體,然後利用反射判斷其是否貼有資料,如果有,然後呼叫object[] aAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);拿到這個特性的物件(其在這個環節進行了例項化),同時此時type又包含具體的資料資訊,就可以將特性的附加資訊與type進行互操作了。
Student student = new Student(); StudentVIP studentVIP = new StudentVIP() { Id = "1", Name = "HAHAH", VIPGroup = "Super學員" }; InvokeCenter.ManagerStudent<Student>(studentVIP);
public class InvokeCenter { public static void ManagerStudent<T>(T student) where T : Student { Console.WriteLine(student.Id); Console.WriteLine(student.Name); student.Answer("LNW"); Type type = student.GetType(); //這個type類級別上標註了特性 if (type.IsDefined(typeof(CustomAttribute), true))//先判斷 { object[] aAttributeArray = type.GetCustomAttributes(typeof(CustomAttribute), true);//此處為type foreach (CustomAttribute item in aAttributeArray) { item.Show(type.Name); } } //這個type屬性上標註了特性 foreach (var prop in type.GetProperties())//先判斷 { if (prop.IsDefined(typeof(CustomAttribute), true)) { object[] aAttributeArray = prop.GetCustomAttributes(typeof(CustomAttribute), true);//此處為prop foreach (CustomAttribute item in aAttributeArray) { item.Show(prop.Name); } } } //同理,方法上也可以獲取 foreach (var method in type.GetMethods()) { if (method.IsDefined(typeof(CustomAttribute), true)) { object[] aMethodArray = method.GetCustomAttributes(typeof(CustomAttribute), true);//此處為method foreach (CustomAttribute item in aMethodArray) { item.Show(method.Name); } } } } }
結果為:
使用場景2
新增說明資訊並獲取,方便進行拓展。
比如我想根據不同的列舉狀態顯示不同的字串資訊。
定義列舉:
public enum UserStatus { /// <summary> /// 正常狀態 /// </summary> Normal=0, /// <summary> /// 凍結狀態 /// </summary> Frozen =1, /// <summary> /// 刪除狀態 /// </summary> Delted = 2 }
常規判斷操作:
UserStatus userStatus = UserStatus.Normal; if (userStatus == UserStatus.Normal) { Console.WriteLine("正常狀態"); } else if (userStatus == UserStatus.Frozen) { Console.WriteLine("凍結狀態"); } else { Console.WriteLine("已刪除"); }
//.....如果發生文字修改,那麼改動量特別大,if else if 分支特別長。。
使用特性
public enum UserStatus { /// <summary> /// 正常狀態 /// </summary> [Remark("正常狀態")] Normal=0, /// <summary> /// 凍結狀態 /// </summary> [Remark("凍結狀態")] Frozen =1, /// <summary> /// 刪除狀態 /// </summary> [Remark("刪除狀態")] Delted = 2
//可以自由拓展。
}
//拓展方法 string remark3withExtendMethod = UserStatus.Delted.GetRemark();
public static class AttributeExtend
{
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 = field.GetCustomAttribute(typeof(RemarkAttribute), true) as RemarkAttribute; return attribute.Remak; } else { return value.ToString(); } }
}
//其中GetFiled API含義如下:
// 摘要: // 搜尋具有指定名稱的公共欄位。 // // 引數: // name: // 包含要獲取的資料欄位的名稱的字串。 // // 返回結果: // 如找到,則為表示具有指定名稱的公共欄位的物件;否則為 null。 // // 異常: // T:System.ArgumentNullException: // name 為 null。 // // T:System.NotSupportedException: // 此 System.Type 物件是尚未呼叫其 System.Reflection.Emit.TypeBuilder.CreateType 方法的 System.Reflection.Emit.TypeBuilder。 public FieldInfo GetField(string name);
使用場景3
做資料驗證。
public class IntValidateAttribute : Attribute//特性名稱約定俗成是以Attribute結尾,特性命名以具體功能名稱為命名,此處就是整型資料驗證 { /// <summary> /// 最小值 /// </summary> private int minValue { get; set; } /// <summary> /// 最大值 /// </summary> private int maxValue { get; set; } /// <summary> /// 建構函式 /// </summary> /// <param name="minValue"></param> /// <param name="maxValue"></param> public IntValidateAttribute(int minValue, int maxValue) { this.minValue = minValue; this.maxValue = maxValue; } /// <summary> /// 檢驗值是否合法 /// </summary> /// <param name="checkValue"></param> /// <returns></returns> public bool Validate(int checkValue) { return checkValue >= minValue && checkValue <= maxValue; } } public class User { [IntValidate(1, 10)] public int Id { get; set; } public string Name { get; set; } } public class BaseDal { public static string Insert<T>(T model) { Type modelType = typeof(T);//Model模型 //獲取型別的所有屬性 PropertyInfo[] propertyInfos = modelType.GetProperties(); bool boIsCheck = true; //迴圈所有屬性 foreach (var property in propertyInfos) { //獲取屬性的所有特性 object[] attrs = property.GetCustomAttributes(true); if (property.PropertyType.Name.ToLower().Contains("int")) { foreach (var attr in attrs) { if (attr is IntValidateAttribute) { IntValidateAttribute intValidate = (IntValidateAttribute)attr;//拿到追加在Model上的特性例項化物件 //執行特性的驗證邏輯 boIsCheck = intValidate.Validate((int)property.GetValue(model));//特性例項化物件intValidate執行物件方法Validate,同時proprty.GetValue(model)獲取此時傳進來的model實體的屬性資料。進行資料驗證。 } } } if (!boIsCheck) { break; } } if (boIsCheck) { return "驗證通過,插入資料庫成功"; } else { return "驗證失敗"; } } } class Program { public static void Main(string[] args) { string msg = BaseDal.Insert<User>(new User() { Id = 123, Name = "lvcc" });//傳入Model User和User實體物件new User() { Id = 123, Name = "lvcc" }
Console.WriteLine(msg);
}
}
總結:
使用特性,可以有效地將元資料或宣告性資訊與程式碼(程式集、型別、方法、屬性等)相關聯。將特性與程式實體相關聯後,可以在執行時使用反射這項技術查詢特性。
特性具有以下屬性:
- 特性向程式新增元資料。元資料是程式中定義的型別的相關資訊。所有 .NET 程式集都包含一組指定的元資料,用於描述程式集中定義的型別和型別成員。可以新增自定義特性來指定所需的其他任何資訊。
- 可以將一個或多個特性應用於整個程式集、模組或較小的程式元素(如類和屬性)。
- 特性可以像方法和屬性一樣接受自變數。
- 程式可使用反射來檢查自己的元資料或其他程式中的元資料。有關詳細資訊。
同時在MVC---EF--WCF--IOC 都有使用特性,無處不在。下篇就以簡單的ORM模型來講解反射與特性的簡單使用。
參考資料:
https://www.cnblogs.com/chenxizhaolu/p/9497768.html
https://www.cnblogs.com/woadmin/p/9406970.html