一、特性(Attribute)定义
特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。
特性(Attribute)用于添加元数据,如编译器指令和注释、描述、方法、类等其他信息。.Net 框架提供了两种类型的特性:预定义特性和自定义特性。
二、How (如何使用)
2.1 .Net 框架预定义特性使用:
- AttributeUsage
[AttributeUsage(validon,AllowMultiple=allowmultiple,Inherited=inherited)] 其中:参数 validon 规定特性可被放置的语言元素。它是枚举器 AttributeTargets 的值的组合。默认值是 AttributeTargets.All,表示该特性可以修饰在任何地方。比如:AttributeTargets.Class 则表示该特性只能修饰在类上面。
- Conditional
这个预定义特性标记了一个条件方法,其执行依赖于指定的预处理标识符。它会引起方法调用的条件编译,取决于指定的值,比如 Debug 或 Trace。
- Obsolete
这个预定义特性标记了不应被使用的程序实体。它可以让您通知编译器丢弃某个特定的目标元素。例如,当一个新方法被用在一个类中,但是您仍然想要保持类中的旧方法,您可以通过显示一个应该使用新方法,而不是旧方法的消息,来把它标记为 obsolete(过时的)。规定该特性的语法:[Obsolete(message,iserror)]
参数 message,是一个字符串,描述项目为什么过时以及该替代使用什么。
参数 iserror,是一个布尔值。如果该值为 true,编译器应把该项目的使用当作一个错误。默认值是 false(编译器生成一个警告)。
2.2 自定义特性使用
使用场景:某些时候我们在程序处理过程中为了程序更加健全及安全需要校验一些参数的合法性,比如邮件格式校验等类似的需求,以下以邮件合法及公司CompanyId的范围只能在1000~10000范围为例。刚开始我们最直接也最先想到的就是传统的方式写参数校验。如下所示:
1 #region 传统的方式写参数校验 2 { 3 4 if (string.IsNullOrWhiteSpace(user.Email)) //:这里只判断了邮箱为空,省略了邮箱合法性判断 5 { 6 Console.WriteLine("Email 参数不符合!"); 7 //:这里不执行保存,提示用户参数不合法 8 } 9 if (user.CompanyId < 1000 || user.CompanyId > 10000) 10 { 11 Console.WriteLine("CompanyId 参数不符合,CompanyId范围只能是1000~10000"); 12 //:这里不执行保存,提示用户参数不合法 13 } 14 } 15#endregion
问题分析:假如某一天业务需求发生了变化,新增了一个参数的校验或者CompanyId的范围发生改变,我们就需要修改代码。代码不稳定
解决方案:特性:可以在不破坏类型封装的前提下,为对象增加额外的信息,执行额外的行为。通过特性我们把公共逻辑移出去,只完成私有逻辑。如下所示,考虑到程序后期可能会涉及到多种参数的校验,每种参数要实现的校验规则各不相同,所以我们定义一个抽象类,抽象类中定义一个抽象方法。
1、定义一个抽象类,继承自Attribute,如下所示:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace MyAttribute.AttributeExtend 8 { 9 [AttributeUsage(AttributeTargets.Property)] 10 public abstract class AbstractValidateAttribute : Attribute 11 { 12 public abstract bool Validate(object oValue); 13 } 14 }
2、 校验邮箱的具体实现如下,继承自AbstractValidateAttribute。EmailValidateAttribute 类实现校验邮箱的逻辑
1 [AttributeUsage(AttributeTargets.Property)] 2 public class EmailValidateAttribute : AbstractValidateAttribute 3 { 4 public override bool Validate(object oValue) 5 { 6 if (oValue != null && string.IsNullOrWhiteSpace(oValue.ToString())) 7 { 8 return true;//实际的判断需要正则验证、此处省略 9 } 10 else 11 { 12 return false; 13 } 14 } 15 }
IntValidateAttribute 继承自AbstractValidateAttribute。 验证int类型是否在取值范围内的逻辑
1 /// <summary> 2 /// 验证int类型是否在取值范围内 3 /// </summary> 4 [AttributeUsage(AttributeTargets.Property)] 5 public class IntValidateAttribute : AbstractValidateAttribute 6 { 7 private int _Min = 0; 8 private int _Max = 0; 9 10 public IntValidateAttribute(int min, int max) 11 { 12 this._Min = min; 13 this._Max = max; 14 } 15 16 public override bool Validate(object oValue) 17 { 18 return oValue != null && int.TryParse(oValue.ToString(), out int num) && num >= this._Min && num <= this._Max; 19 } 20 }
3、通过反射调用特性
1 /// <summary> 2 /// 数据库访问基类 3 /// </summary> 4 public class BaseDAL 5 { 6 /// <summary> 7 /// 增加校验 8 /// </summary> 9 /// <typeparam name="T"></typeparam> 10 /// <param name="t"></param> 11 public static void Save<T>(T t) 12 { 13 Type type = t.GetType(); 14 bool isSafe = true; 15 { 16 foreach (var property in type.GetProperties()) 17 { 18 object[] oAttributeArray = property.GetCustomAttributes(typeof(AbstractValidateAttribute), true);//特性类的实例化就在反射发生的时候 19 foreach (var oAttribute in oAttributeArray) 20 { 21 AbstractValidateAttribute validateAttribute = oAttribute as AbstractValidateAttribute; 22 isSafe = validateAttribute.Validate(property.GetValue(t)); 23 if (!isSafe) 24 { 25 break; 26 } 27 } 28 if (!isSafe) 29 { 30 break; 31 } 32 } 33 } 34 35 if (isSafe) 36 { 37 Console.WriteLine("保存到数据库"); 38 //:执行业务数据保存操作 39 } 40 41 else 42 { 43 Console.WriteLine("数据不合法"); 44 //:不执行保存,提示用户参数不合法 45 } 46 } 47 }
4、使用特性
1 public class UserModel 2 { 3 4 #region Model 5 /// <summary> 6 /// EMaill 7 /// </summary> 8 [EmailValidate] //:标记特性 9 public string Email 10 { 11 set; 12 get; 13 } 14 15 /// <summary> 16 /// 企业ID 17 /// </summary> 18 [IntValidate(1000, 10000)] //:标记特性 19 public int CompanyId 20 { 21 set; 22 get; 23 } 24 /// <summary> 25 /// 企业名称 26 /// </summary> 27 public string CompanyName 28 { 29 set; 30 get; 31 } 32 /// <summary> 33 /// 用户状态 0正常 1冻结 2删除 34 /// </summary> 35 public int? State 36 { 37 set; 38 get; 39 } 40 41 #endregion Model 42 43 }
3、演示结果
1、上端调用
2、反射找到所有的属性,及每一个属性的自定义AbstractValidateAttribute标签的特性
3、根据不同的特性执行对应的方法
4、总结:
后期如果新增不同参数类型的校验,只需要新增对应的类,继承自AbstractValidateAttribute ,在新增的类中实现具体的校验逻辑,并且在需要校验的属性上标记对应的特性即可,
方便代码扩展。