Attribute(特性)
MSDN给出的定义:
Attribute 类将预定义的系统信息或用户定义的自定义信息与目标元素相关联。目标元素可以是程序集、类、构造函数、委托、枚举、事件、字段、接口、方法、可移植可执行文件模块、参数、属性 (Property)、返回值、结构或其他特性(Attribute)。
在.Net程序中,可以使用特性(Attribute)来解决许多问题。如:将WebService中接口函数标记为WebMethod,将类标记为可序列化等等。
此外,我们也可以自定义Attribute,来实现我们需要的功能。但自定义Attribute必须继承自Attribute类。Attribute中有很多方法或者属性,为我们提供了强大的功能。一般我们通过反射的方式来使用Attribute。
下面结合实际项目中的使用谈谈自己的体会。
需求:项目中有一个专门的日志库。现在项目中需要前端用到查询、翻页、按各个字段排序等等功能。由于日志数据可能有无效的数据,并且数据量较大。为了提升统计、翻页性能,所以建立了许多临时表。如果在实际操作中为一个一个不同的需求去建立临时表,造成代码大量重复臃肿,并且不便于统一管理,考虑到的方案是通过Attribute的方式,统一建临时表。
主体思路如下:通过自定义特性来描述要建立临时表的字段的相关类型、长度等等信息,然后通过反射的方式获取这些字段对应的这些特性,然后构造相应的SQL命令就可以完成临时表的建立了。
特性定义如下:
[AttributeUsage(AttributeTargets.Property)] public class TableFieldAttribute : Attribute { /// <summary> /// 字段长度 /// </summary> public int Length { get; set; } /// <summary> /// 字段描述 /// </summary> public string Describe { get; set; } /// <summary> /// 字段类型 /// </summary> public string Type { get; set; } }
以上AttributeUsage,也是一个继承自Attribute的类,他的作用就是标志自定义类的使用范围,如:字段,属性,类,方法。以及使用我们自定义类
标记的类的子类是否继承特性等。
有了这个自定义特性,将它运用到实体上就行了。
public class TableEntity { [TableField(Describe = "Guid主键", Length = 36, Type = "uniqueidentifier")] public Guid Guid { get; set; } [TableField(Describe = "名称", Length = 36, Type = "varchar ")] public string Name { get; set; } }
这样在实体定义上,我们就已经有了定义好了数据类型,长度等等。在实际要建临时表时,通过反射要建临时表的实体,便能构造相应的SQL命令了。
工具类的定义:public class Tools<T> where T : class。实际使用时,将泛型类型换成相应的类型即可。
主要给出如何使用反射来获取临时表具体字段的信息代码:
public static List<Record> GetTableFieldsInformation() { Type t = typeof(T); PropertyInfo[] propertyInfos = t.GetProperties(); List<Record> tableEntities = new List<Record>(); propertyInfos.ToList().ForEach(property => { IList<CustomAttributeData> list = property.GetCustomAttributesData(); if (list.Count == 1) { tableEntities.Add(GetTableEntity(list[0].NamedArguments, property)); } else { throw new Exception(); } }); return tableEntities; }
这样, GetTableFieldsInformation()方法中返回的List就是包含了临时表一个字段的所有信息的列表。我们循环列表,构造Sql命令
主要代码如下:
recordEntities.ForEach(record => { if (i == 0) { sql = "CREATE TABLE [" + record.TableName + "]("; } if (list.All(item => !record.Type.Equals(item, StringComparison.OrdinalIgnoreCase))) { sql += string.Format("{0} {1}({2}),", record.FieldName, record.Type, record.Length); } else { sql += string.Format("{0} {1},", record.FieldName, record.Type); } i++; });
程序最终执行后如下图:
主要的思想就是这些。另外,如果你细心,你会发现AttributeUsage特性使用和我定义的TableFieldAttribute使用方式上有写不一样。
TableFieldAttribute的使用方式:[TableField(Describe = "Guid主键", Length = 36, Type = "uniqueidentifier")]
AttributeUsage的使用方式:[AttributeUsage(AttributeTargets.Property)]。
同样是自定义的特性,为什么我们定义的和Framework库里的使用不一样呢。?通过Reflect看看AttributeUsage的源码,如下图:
从图中标记的地方来看,是由于在AttributeUsage中属性的定义【 AttributeTargets ValidOn】以及构造函数的定义不一样导致的。
有兴趣深入研究的同学可以自己试试。
后记:特性的使用是很强大的一项功能。本例中使用的仅仅是其他很小的一部分。因此只对在实际应用中做了写说明。另:代码是没有经过细致整理