自定義特性與應用
自定義特性允許把自定義元數據與程序元素關聯起來。在.NET Framework框架中,微軟定義了許多特性提供給開發人員使用,如StructLayout特性中的信息在內存中布置結構。這些已有的特性得到了C#編譯器的支持,編譯器可以以特殊的方式定制編譯過程。但是,在某些特定場合需要開發人員定義自己的特性,如數據驗證、字段解釋等場景。
自定義特性在很大程度上是依賴於反射,代碼在運行期間讀取這些元數據,使用它們在運行期間做出決策,可以直接影響代碼的運行方式。
1、編寫自定義特性
自定義特性需要繼承自抽象特性類Attribute。假定已定義了一個自定義特性類DescriptionAttribute,其已用於以下屬性:
[Description("姓名")] public string Name { get; set; }
當C#編譯器發現這個屬性應用了Description特性時,會先把字符串Attribute追加到Description名稱後面,形成DescriptionAttribute,然後全局搜索所有命名空間查找對應的類。如果應用特性時後面有Attribute,編譯器就不會把該字符串添加到後面。
編譯器找到含有該名稱的類,且該類直接或者間接派生自Attribute。編譯器會認為該類包含控制特性的信息。特別時屬性類需要指定:
- 特性可以應用到那哪些類型的程序元素上(類、結構、屬性、方法等)
- 是否可以多次應用到同一程序元素上
- 特性應用到類或接口上時,是否由派生類和接口繼承
- 特性有哪些必選和可選元素
如完成上面的自定義特性類DescriptionAttribute:
/// <summary> /// 解釋說明特性 /// </summary> [AttributeUsage(AttributeTargets.Enum | AttributeTargets.Class | AttributeTargets.Field |AttributeTargets.Property,AllowMultiple =false,Inherited =false)] publicclass DescriptionAttribute : Attribute { /// <summary> /// 說明解釋 /// </summary> public string Description { get; private set; } public DescriptionAttribute(string description) { this.Description = description; } }
AttributeUsage特性類用以標記特性類,它只能用於特性類上,不能用於非特性類。AtrributeUsage類主要是標識自定義特性可以用於那些類型的程序元素上,使用AttributeTargets枚舉可以指定用於一個或多個類型元素上。在指定用於多個類型元素上時,使用“|”運算符。該值時必須的,默認為All。特性的另外兩個參數:AllowMultiple和Inherited是可選參數。
2、特性類的應用
建立一個Student類,並對其應用上述特性:
/// <summary> /// 性別 /// </summary> public enum Sex { /// <summary> /// 男 /// </summary> [DescriptionAttribute("男")] Man=1, /// <summary> /// 女 /// </summary> [DescriptionAttribute("女")] Woman =2 } public class Student { /// <summary> /// 學生ID /// </summary> [DescriptionAttribute("學號")] public long ID { get; set; } /// <summary> /// 姓名 /// </summary> [Description("姓名")] public string Name { get; set; } /// <summary> /// 性別 /// </summary> [DescriptionAttribute("性別")] public Sex Sex { get; set; } /// <summary> /// 年齡 /// </summary> [DescriptionAttribute("年齡")] public byte Age { get; private set; } private DateTime birthDate = DateTime.Now.Date; /// <summary> /// 出生日期 /// </summary> [DescriptionAttribute("出生日期")] public DateTime BirthDate { get { return birthDate; } set { birthDate = value; Age = (byte)ComputeAge(birthDate); } } /// <summary> /// 計算年齡 /// </summary> /// <param name="birthDate">出生日期</param> /// <returns>年齡</returns> private int ComputeAge(DateTime birthDate) { if (DateTime.Now.Year > birthDate.Year) { return DateTime.Now.Year - birthDate.Year; } else if (DateTime.Now.Year == birthDate.Year) { return 1; } return 0; } }
現在需要在程序代碼中訪問,使用和獲取對應特性應用的效果。首先,在Program類中編寫一個靜態函數ShowDescription(object[] attributes),用以顯示對應的描述:
/// <summary> /// 顯示解釋 /// </summary> /// <param name="attributes">特性集合</param> public static void ShowDescription(object[] attributes) { if (attributes != null && attributes.Length > 0) { foreach (object obj in attributes) { if (obj is DescriptionAttribute) { string description = (obj as DescriptionAttribute).Description; Console.WriteLine(description); break; } } } }
獲取類上面的描述特性:
Type type = typeof(Student); object[] attributeArray = type.GetCustomAttributes(typeof(DescriptionAttribute), true);//獲取指定類型的特性 //Attribute[] attributeArray =Attribute.GetCustomAttributes(type);//獲取所有的特性 ShowDescription(attributeArray);
獲取字段上的描述特性:
Type type = typeof(Student); FieldInfo[] fields= type.GetFields(); foreach(FieldInfo field in fields) { object[] attributeArray = field.GetCustomAttributes(typeof(DescriptionAttribute), true);//獲取指定類型的特性 //Attribute[] attributeArray =Attribute.GetCustomAttributes(field);//獲取所有的特性 ShowDescription(attributeArray); //上面兩句代碼可用下面代碼替換 field.GetDescripition();//擴展方法顯示特性 }
獲取屬性上的描述特性:
//獲取屬性的特性 Type type = typeof(Student); PropertyInfo[] propertyInfos = type.GetProperties(); foreach (PropertyInfo property in propertyInfos) { object[] attributeArray = property.GetCustomAttributes(typeof(DescriptionAttribute), true);//獲取指定類型的特性 //Attribute[] attributeArray =Attribute.GetCustomAttributes(property);//獲取所有的特性 ShowDescription(attributeArray); //上面兩句代碼可用下面代碼替換 property.GetDescripition();//擴展方法顯示特性 }
獲取枚舉上的描述特性:
獲取特定枚舉上的特性獲取稍微復雜。首先需要獲取其類型,然後獲取該類型中指定的成員信息,再獲取相關的描述特性。在此,使用擴展方法獲取枚舉的特性描述:
首先,創建內部訪問的函數GetDescription(object[] attributes),用以在有描述特性時返回描述信息,沒有描述信息時返回空白:
/// <summary> /// 從特性列表中查找屬性解釋 /// </summary> /// <param name="attributes">已知特性列表</param> /// <returns>解釋</returns> private static string GetDescripition(object[] attributes) { if (attributes != null) { foreach (object obj in attributes) { if (obj is DescriptionAttribute) { return (obj as DescriptionAttribute).Description; } } } return string.Empty; }
其次,建議枚舉類型的擴展方法,以支持獲取和返回對象的描述信息:
/// <summary> /// 獲取枚舉的標記信息 /// </summary> /// <param name="enumValue">枚舉值</param> /// <returns>枚舉值對應的解釋</returns> public static string GetDescripition(this Enum enumValue) { Type type = enumValue.GetType(); if (!type.IsEnum) { throw new ArgumentException("EnumerationValue必須是一個枚舉值", "enumValue"); } MemberInfo[] memberInfo = type.GetMember(enumValue.ToString());//獲取對應的成員 if (memberInfo != null && memberInfo.Length > 0) { object[] attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); string descripition = GetDescripition(attributes); if (string.IsNullOrWhiteSpace(descripition)==false) { return descripition; } } return enumValue.ToString(); }
在程序調用時,僅需要如下使用方式:Sex sex = Sex.Man; sex.GetDescripition();//擴展方法顯示特性
3、特性與擴展方法
以上方法都是在使用時建立相關的靜態方法獲取特性。實際上,可以對屬性、字段等像枚舉一樣,建立對應的靜態方法,以此方便調用,減少代碼:
/// <summary> /// 獲取字段的解釋 /// </summary> /// <param name="fieldInfo">字段信息</param> /// <returns>註釋</returns> public static string GetDescripition(this FieldInfo fieldInfo) { object[] attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true); string descripition = GetDescripition(attributes); if(string.IsNullOrWhiteSpace(descripition)) { return fieldInfo.Name; } else { return descripition; } } /// <summary> /// 獲取屬性的解釋 /// </summary> /// <param name="propertyInfo">屬性信息</param> /// <returns>屬性的解釋</returns> public static string GetDescripition(this PropertyInfo propertyInfo) { object[] attributes = propertyInfo.GetCustomAttributes(typeof(DescriptionAttribute), true); string descripition = GetDescripition(attributes); if (string.IsNullOrWhiteSpace(descripition)) { return propertyInfo.Name; } else { return descripition; } }
相關用法,在前面已使用到。相關源碼下載:https://files.cnblogs.com/files/pilgrim/StudentManage.rar
自定義特性與應用