自定义特性允许把自定义元数据与程序元素关联起来。在.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)] public class 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