自定义特性允许把自定义元数据与程序元素关联起来。在.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
共同學習,寫下你的評論
評論加載中...
作者其他優質文章
