.Net 特性分析与妙用
一.特性是什么
1、想象很多小伙伴们都看过在一个类上方、或者在控制器见过类似的东东,加上之后就可以标识这个类或者方法就具备了某些特点 ,那我们就进入它的内心一探究竟吧。
2.我们进入某个特性之后,可以发现它又单独继承于Attribute 它的意思就是属性、特质的意思。那它到底能干嘛呢?能让我们写代码飞起吗??
二.走进特性
1.我们也写一个自己的特性,说干就来吧来。带着问题一步一步是魔鬼的步伐,兄弟们要我们干就完了,噢力给!!!
2.首先我们创建一个类(特性就是一个很单纯的类,我们一般以Attribute结尾命名),像这样我们的特性类就做好了,接下来怎么做呢?
3.创建完之后当然是使用它,它可以使用在类、方法、属性、枚举、参数、返回值上,使用的时候可以默认的省略Attribute,当我们使用[User]的时候就默认使用了[User()]就是在调用特性类的默认构造函数,特性必须存在一个或者多个构造方法,我们使用的时候就相当于执行特性的构造函数。好了知道怎么使用特性了,但是特性有什么卵用我还是不懂的样子!!!
4.上面我们已经知道了特性的创建和添加,但是还没有体现出有什么好处,在刚才我们创建了一个特性但是我们没有写任何代码,下面我们在创建一个新的特性进行使用。
我们假装一个场景我们需要判断用户支付订单的支付状态,我们会写一个枚举1待支付、2支付成功、3支付失败。
上面应该会是我们经常写的代码了吧,虽然说现在只有几个类型,但是类型多起来之后这里的判断就显得很冗余,又长又臭了。这里就不拐弯抹角我们可以使用特性去实现简单化。
/// <summary> /// 获取类型名称的特性 /// </summary> public class RemarkAttribute : Attribute { /// <summary> /// 接受备注字段 /// </summary> public string remarks { get; set; } /// <summary> /// 构造函数 /// </summary> public RemarkAttribute(string _remarks) { remarks = _remarks; } /// <summary> /// 返回备注字段 /// </summary> /// <returns></returns> public string GetRemarks() { return this.remarks; } }
上面我们创建了一个返回备注字段的特性,注意特性就是一个类,当然就拥有类所有东西,我们定义一个有参构造函数就收传过来的备注存在remarks属性中,然后通过GetRemarks()方法返回备注信息,然后我们怎么使用呢?我们在刚才的枚举中加入我们的特性,上面我们说过特性可以添加在枚举上
/// <summary> /// 定义枚举 /// </summary> public enum TypeEnum { /// <summary> /// 待支付 /// </summary> [Remark("待支付")] ToPay = 1, /// <summary> /// 支付成功 /// </summary> [Remark("支付成功")] PaymentSuccess = 2, /// <summary> /// 支付失败 /// </summary> [Remark("支付失败")] PaymentFailure = 3 }
好了我们现在准备工作都做好了,特性我们也写了,使用特性的地方我们也加上了,但是我们要怎么获取特性的信息呢?一脸懵逼了,别急兄弟们慢慢来。这里就要使用反射,反射的特点你们还记得吗?它可以获取某个类,获取类的特性、属性、字段等等,还可以获取属性的特性。(反射篇)
/// <summary> /// 编写一个枚举的拓展类 /// </summary> public static class EnumExpand { /// <summary> /// 获取枚举字段的返回备注 /// </summary> /// <param name="enum"></param> /// <returns></returns> public static string GetTypeName(this Enum value) { //首先我们获取type类型 Type type = value.GetType(); //这里直接存起来方便返回 string strValue = value.ToString(); //然后我们是获取字段上面的特性,所以这里是一个 FieldInfo field = type.GetField(strValue); ///IsDefined判断类型是不是有这个类型。 ///第二个属性:如果是真会根据继承链找是不是有这个类型 if (field.IsDefined(typeof(RemarkAttribute), true)) { //GetCustomAttribute获取类型的特性.(这个的时候会去获取之前[User("111")]的类,相当于new,这个就是构造函数) //第一个参数是类型 //第二个:如果是真会看自己的祖先有没有这个类型 RemarkAttribute attribute = (RemarkAttribute)field.GetCustomAttribute(typeof(RemarkAttribute), true); return attribute.GetRemarks(); } else { return strValue; } } }
上面是我们写的一个枚举的拓展方法,方便我们的使用。因为我们是在枚举的字段使用特性,这里获取调用方法的类,进行反射获取里面的成员(这里我们是在字段上面使用特性,所以获取枚举在获取里面的字段。)这样是不是比我们以前这么多判断更加方便简洁呢?
class Program { static void Main(string[] args) { TypeEnum typeEnum = TypeEnum.PaymentFailure; Console.WriteLine(typeEnum.GetTypeName()); } }
5.特性的限制
/// <summary> /// AttributeUsage是特性的约束。 /// 第一个是限制这个特性可以作用在什么上面, /// 第二个是显示这个特性可以不可以重复添加到一个上面 /// 第三个参数是限制特性可以不可以被继承到,把特性遗传到继承者上面 /// </summary>
[AttributeUsage(AttributeTargets.All,AllowMultiple =true,Inherited =true)]
特性也可以限制使用范围,AttributeTargets 限制使用的对象上面, AllowMultiple是否可以在同一个对象上面使用多次,默认是false,Inherited表示该特性是否可以被继承,默认情况下Inherited为true。
三、特性进阶
1.看到这里我相信我们的小伙伴已经知道了什么是特性以及编写属于自己的特性啦。为了巩固大家的记忆我们在来一个大家经常遇到的场景
情景剧场开始:我们经常需要判断用户输入的数据是不是正确的,经常需要些大量的判断,代码又长又重复(说到这里使用过EF的小伙伴肯定知道,可以在模型里面做限制哈哈)我们做两个场景需要判断某个字段的长度,以及大小.
/// <summary> /// 继承抽象类,显示抽象类里面的方法 /// </summary> public class ScopeAttribute : VerifyBaseAtttribute { /// <summary> /// 最小值 /// </summary> private int Min { get; set; } = 0; /// <summary> /// 最大值 /// </summary> private int Max { get; set; } = 0; /// <summary> /// 限制长度 /// </summary> /// <param name="min"></param> /// <param name="max"></param> public ScopeAttribute( int max = 0) { this.Max = max; } /// <summary> /// 限制长度 /// </summary> /// <param name="min"></param> /// <param name="max"></param> public ScopeAttribute(int min = 0, int max = 0) { this.Min = min; this.Max = max; } /// <summary> /// 判断长度范围修饰 /// </summary> /// <returns></returns> public override bool Verify(object value) { if (value == null) { return false; } Type type = value.GetType(); string str = value.ToString(); if (string.IsNullOrWhiteSpace(str)) { return false; } int Length = str.Length; if (Min > 0 && Max > 0) { return Length > Min && Length < Max ? true : false; } else if (Min > 0) { return Length > Min ? true : false; } else if (Max > 0) { return Length < Max ? true : false; } return false; } }
/// <summary> /// 判断qq号 /// </summary> public class QQAttribute : VerifyBaseAtttribute { /// <summary> /// 最小值 /// </summary> private int Min { get; set; } /// <summary> /// 最大值 /// </summary> private int Max { get; set; } /// <summary> /// 判断区间数 /// </summary> /// <param name="min"></param> /// <param name="max"></param> public QQAttribute(int min = 0, int max = 0) { this.Min = min; this.Max = max; } /// <summary> /// 判断长度范围修饰 /// </summary> /// <returns></returns> public override bool Verify(object value) { if (value == null) { return false; } Type type = value.GetType(); if (type == typeof(int)) { int valueInt = Convert.ToInt32(value); if (valueInt == 0) return false; return valueInt > Min && valueInt < Max ? true : false; } return false; } }
/// <summary> /// 验证父类特性 /// 主要就是给验证特性提供一个统一的入口 /// 实现方式:接口形式、抽象类形式进行重写(这里我使用抽象类实现) /// </summary> public abstract class VerifyBaseAtttribute : Attribute { /// <summary> /// 统一验证的入口 /// </summary> /// <returns></returns> public abstract bool Verify(object value); }
看了上面三段代码你就会很疑惑为什么,你写了一个抽象的特性?????满脸问号等下就告诉你。
public static class EntityExpands { /// <summary> /// 验证方法 /// </summary> /// <typeparam name="T">泛型</typeparam> /// <param name="model">实体</param> /// <returns></returns> public static bool Verify<T>(this T model) where T : class, new()//限制这个拓展方法只能使用在可以实例化的类上 { //获取实体模型 Type type = model.GetType(); //获取实体的所有属性 foreach (PropertyInfo propertyInfo in type.GetProperties()) { //遍历属性,判断属性是否存在VerifyBaseAtttribute特性,true会去查看继承链是否存在 //这个之所以使用Base特性就是抽象出验证特性共同的东西,所有验证都可以调用这个方法生效 if (propertyInfo.IsDefined(typeof(VerifyBaseAtttribute), true)) { //然后获取属性上面的特性(注意一个属性上面可能会出现多个验证特性,所以这里我们需要获取一个数组) object[] atttribute = propertyInfo.GetCustomAttributes(typeof(VerifyBaseAtttribute), true); //进行遍历 foreach (VerifyBaseAtttribute item in atttribute) { //调用验证方法,传入属性的值 if (!item.Verify(propertyInfo.GetValue(model))) { //失败返回 return false; } } } } return true; } }
四、个人总结
1、首先特性相当于一个补充类(就是一个类),他可以在不该变使用者的前提下,给使用者添加字段或者放法行为 好比我以前用过的拦截器 就是继承了特性然后进行了拓展的,AOP思想.,我们将一些不相干的逻辑可以放到特性里面,简化我们主干代码的逻辑和代码量。
第一次,当它本可进取时,却故作谦卑;
第二次,当它空虚时,用爱欲来填充;
第三次,在困难和容易之间,它选择了容易;
第四次,它犯了错,却借由别人也会犯错来宽慰自己;
第五次,它自由软弱,却把它认为是生命的坚韧;
第六次,当它鄙夷一张丑恶的嘴脸时,却不知那正是自己面具中的一副;
第七次,它侧身于生活的污泥中虽不甘心,却又畏首畏尾。