自定义特性与应用

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

posted @ 2018-06-24 16:51  一只独行的猿  阅读(304)  评论(0编辑  收藏  举报