C#进阶之特性(Attribute)
相信只要是用过.net MVC的开发都用过特性,例如最基础的HttpMethodAttribute特性,用来给接口进行请求方式限定的,等等诸如此类的特性,数不胜数。
那么什么是特性?
特性就是一个类,直接或者间接继承自Attribute,特性本身没有任何作用。
特性的使用方法有两种:
[Range(0,10)] [Test] ///分开单独使用 public int Hight { set; get; } [Test, Range(0, 10)] ///合并使用,通过逗号分隔 public string Name { set; get; }
attribute的使用规则,一般通过在特性类上使用AttributeUsage特性来进行设置
这个特性有三个属性:
AllowMultiple:是否允许重复使用;
Inherited:是否可以被继承;
构造函数有AttributeTargets类型的参数,主要用来设置特性的使用场景(类、接口、属性、字段……)
特性的使用场景:
用来做标记,通过判断类、属性……是否有这个特性,来进行一些业务逻辑上的判断,比如:
具体是通过System.Reflection程序集提供的反射功能来进行特性获取,
譬如判断一个类型是否具有某个特性,使用的是IsDefined方法,
获取一个类的所有特性,使用的是GetCustomAttributes方法,获取的是一个object[],可以通过as进行强转为需要判断的类型;
1、一些预设的特性
ObsoleteAttribute:可以通过添加这个特性,对类、属性、接口等进行代码过期设置;
ConditionalAttribute:通过在方法上设置这个特性,可以屏蔽掉对该方法的所有调用(不过支持返回值是void的方法,毕竟只有这样才不会有在调用的时候有上下文);
经常看到错误日志里面有很详细的信息,具体到了某某文件多少行哪个方法出错等等,其实微软也提供了对应的特性,支持我们自己查看这些内容:
/// <summary> /// /// </summary> /// <param name="filePath">调用的文件物理路径</param> /// <param name="num">被调用的行</param> /// <param name="name">调用的方法</param> public void Call2(string str, [CallerFilePath] string filePath = "", [CallerLineNumber] int num = 0, [CallerMemberName] string name = "") { }
2、接口的模型绑定属性验证
可以通过继承ValidationAttribute,并重写IsValid方法即可实现自定义的属性绑定校验。
/// <summary> /// /// </summary> [AttributeUsage(AttributeTargets.All,AllowMultiple = true,Inherited = true)] public class RangeAttribute : ValidationAttribute { public RangeAttribute(int minNum, int maxNum) { MaxNum = maxNum; MinNum = minNum; } public int? MaxNum { set; get; } public int? MinNum { set; get; } public override bool IsValid(object value) { var result = false; if (MaxNum != null) result = (int)value < MaxNum; if (MinNum != null) result = result && (int)value > MinNum; return result; } }
3、可以用来做属性的标记
举个例子,比如我想要对一个列表进行多个字段的检索,比如这个实体
/// <summary> /// 全局搜索查询demo实体 /// </summary> public class SerachEntity { /// <summary> /// 姓名 /// </summary> [GlobalSerach] [Export("姓名")] public string Name { set; get; } /// <summary> /// 中文名 /// </summary> [GlobalSerach] [Export("中文名")] public string CName { set; get; } /// <summary> /// 英文名 /// </summary> [GlobalSerach] [Export("英文名")] public string EName { set; get; } /// <summary> /// 地址 /// </summary> [GlobalSerach] [Export("地址")] public string Address { set; get; } /// <summary> /// IP /// </summary> [Export("IP地址")] public string IP { set; get; } } /// <summary> /// 全局搜索的标记特性 /// </summary> public class GlobalSerachAttribute : Attribute { }
我想要对这些属性进行一个综合的查询
那么不可能来一个需求我就写一个where吧,这样并不符合封装的思想,所以我需要做的就是寻找共同点,进行封装
public static class BaseSerach { private static List<BinaryExpression> exps = new List<BinaryExpression>(); private static ParameterExpression m_Parameter = null; /// <summary> /// where全局查找语句 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="iEnumerables"></param> /// <param name="str"></param> /// <returns></returns> public static IEnumerable<T> WhereByGlobalSerach<T>(this IEnumerable<T> iEnumerables, string str) where T : class { m_Parameter = Expression.Parameter(typeof(T), "x"); var propertys = typeof(T).GetProperties().Where(t => !(t.IsDefined(typeof(SerializableAttribute), true))).ToList(); propertys.ForEach(t=> { MemberExpression member = Expression.PropertyOrField(m_Parameter, t.Name); Expression exp1 = Expression.NotEqual(member, Expression.Constant(null));//判断是否为null Expression exp2 = Expression.Call(member, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), Expression.Constant(str)); exps.Add(Expression.AndAlso(exp1, exp2)); }); return iEnumerables.Where(GetLambda<T>()); } /// <summary> /// 获取表达式 /// </summary> /// <typeparam name="T"></typeparam> /// <returns></returns> private static Func<T, bool> GetLambda<T>() where T : class { Expression whereExpr = null; foreach (var expr in exps) { if (whereExpr == null) whereExpr = expr; else whereExpr = Expression.Or(whereExpr, expr); } if (whereExpr == null) return null; return Expression.Lambda<Func<T, bool>>(whereExpr, m_Parameter).Compile(); } }
这里的方法就可以用来进行泛型的普遍属性检索,
调用起来也很简单
var beforeList = new List<SerachEntity>() { new SerachEntity() {Name="余成" },new SerachEntity() {Name="余承浩" } }; var lastList = beforeList.WhereByGlobalSerach("余");
我感觉这就是特性最好用的一点了,用于标记。