特性
特性无处不在,在MVC、WCF、ORM、EF等等中都使用到了特性。
比如【HttpGet】、【HttpPost】、【Table】
特性有两个作用:提供额外信息,提供额外功能
特性必须使用反射才能调用
基于IL解读特性原理
什么是特性?
- 增加了特性以后,就可以有一些厉害的功能,甚至可以影响编译器
- 特性就可以增加一些功能
比如系统提供的特性Obsolete,用以报出警告或异常;Serializable用以表示可以序列化与反序列化。
在中间语言IL中,我们发现标记了特性的元素内部都会生成一个.custom,但是在C#无法调用
特性初步探索
特性的本质
特性的本质就是一个类
特性的声明和使用
声明一个类,直接或间接继承Attribute类(这个类的名字约定俗成以Attribute结尾)
这个时候就可以在其他类、属性、字段、方法、方法参数、方法返回值等上进行标记,使用英文中括号包裹
如果是以Attribute结尾的话,尾部的Attribute在标记使用时可以省略
AttributeUsage
可以影响编译器,指定当前特性可以标记的位置、能否重复修饰、是否可以继承
也是一个特性
标记在特性上的特性
特性的生效
需要使用到反射
特性提供额外信息
比如,获取枚举值
-
首先创建特性
[AttributeUsage(AttributeTargets.Field)] public class RemarkAttribute : Attribute { public string Remark { get; private set; } public RemarkAttribute(string remark) { this.Remark = remark; } }
-
创建枚举并使用特性
public enum UserStuta { /// <summary> /// 正常状态 /// </summary> [RemarkAttribute("正常")] Normal = 0, /// <summary> /// 已冻结 /// </summary [RemarkAttribute("已冻结")] Frozen = 1, /// <summary> /// 已删除 /// </summary> [RemarkAttribute("已删除")] Deleted = 2 }
-
使用反射获取特性值
public static string GetRemark(this Enum @enum) { Type type = @enum.GetType(); FieldInfo? filedInfo = type.GetField(@enum.ToString()); if (filedInfo != null) { if (filedInfo.IsDefined(typeof(RemarkAttribute), true)) { RemarkAttribute remarkAttribute = (RemarkAttribute)filedInfo.GetCustomAttribute(typeof(RemarkAttribute), true); return remarkAttribute.Remark; } } return @enum.ToString(); }注意这里使用反射获取特性时,首先需要判断该元素是否标记了特性,使用方法【某元素的Info】.IsDefined(typeof(特性类型), 是否包含继承),例如上述代码中的
filedInfo.IsDefined(typeof(RemarkAttribute), true)然后使用GetCustomAttribute方法得到特性的实例,然后返回特性中的属性/字段。(这里可以使用泛型的
GetCustomeAttribute<特性的类型>(是否包含继承)更简便)如果特性允许多个标记,且相关元素确实标记了多个该特性,那么在使用GetCustomAttribute就肯定会报错了,因为它找到了多个!!!所以这个时候应该使用
fileInfo.GetCustomAttributes<RemarkAttribute>(true);,然后循环得到的特性List
特性提供额外功能
比如,验证数据信息(手机号、邮箱、密码、账户是否符合规范)
特性提供额外功能和特性提供额外信息类似,只是在特性中把属性/字段的读写改成对方法的调用。
比如,验证手机号是否符合长度时:
-
首先定义特性
[AttributeUsage(AttributeTargets.Property)] public class MobileNumAtrribute : AbstractValidateAttribute { public int Count { get; private set; } public MobileNumAtrribute(int count) { if (count<=0) { throw new Exception("手机号长度不可能为负数"); } Count = count; } public override bool Validate(object mobileNum) { return mobileNum != null && mobileNum.ToString().Length == Count; } }
-
执行验证的方法
public static bool Validate<T>(T t) { Type type = t.GetType(); foreach (PropertyInfo prop in type.GetProperties()) { if (prop.IsDefined(typeof(AbstractValidateAttribute), true)) { object oValue = prop.GetValue(t); AbstractValidateAttribute atrribute = prop.GetCustomAttribute<AbstractValidateAttribute>(true); if (!atrribute.Validate(oValue)) { return false; } } } return true; }
- 调用方法即可