如何较好的获取修饰属性的Attribute

假设我们需要为数据库表或者SharePoint List编写一些实体类,实体类的某些属性会对应于数据库表的字段或者SharePoint中的Field,在执行一些操作时(譬如实体类和数据源的互相转换),我们想要知道实体类的某个属性对应的字段名称是什么,我们可以用肉眼观察并手动返回一个字符串值,譬如下面这种做法:

field1.Value = entity.Property1;

field1.Name = "Field1";

这种关联方式很弱,而且直接指定字符串值也容易产生错误。当实体类和属性变得越来越多时,这种弱关联的对应方式就会变得难以阅读,难以理解,难以维护。

所以我们会想到用Attribute来修饰需要对应字段的属性,同时指定对应的字段名称,下面是这个Attribute类的定义:

public class StaticFieldNameAttribute: Attribute
{
public string Name { get; set; }

public StaticFieldNameAttribute(string name)
{
this.Name = name;
}
}

下面是为属性应用了StaticFieldNameAttribute的实体类的示例:

public class Entity
{
[StaticFieldNameAttribute("Field1")]
public string Property1 { get; set; }

[StaticFieldNameAttribute("Field2")]
public string Property2 { get; set; }
}

接下来编写一个工具方法来获取属性对应的字段名称,需要使用到反射相关的知识:

public static string GetStaticFieldName<T>(T entity, string propertyName)
{
Type type = typeof(T);

PropertyInfo propInfo = type.GetProperty(propertyName);
if (propInfo == null)
throw new ArgumentException(string.Format(
"'{0}' is not a property.",
propertyName));

var attributes = propInfo.GetCustomAttributes(typeof(StaticFieldNameAttribute), true);
if (attributes.Length == 0)
throw new InvalidOperationException(string.Format(
"Property '{0}' does not applied any StaticFieldNameAttribute.",
propInfo.Name));
else
return (attributes.Last() as StaticFieldNameAttribute).Name;
}

调用的方法如下:

field1.Value = entity.Property1;
field1.Name = Utilities.GetStaticFieldName(entity, "Property1");

使用了Attribute之后,实体类属性和字段的关联通过Attribute定义,位置唯一,易于维护并且不会降低实体类的可读性。

但在调用GetStaticFieldName方法时依然需要传递一个字符串类型的属性名称,如果输入了错误的属性名称就会产生错误,或者代码经过重构,更改了属性名称,却没有更改方法调用里传入的字符串形式的属性名称,也会产生错误(这种情况IDE基本上是束手无策的)。

如果能够完全避免使用字符串来传递名称就好了,下面就来看看这个方法的改进版:

public static string GetStaticFieldName<TSource, TProperty>(this TSource entity,
Expression<Func<TSource, TProperty>> propertyLambda)
{
Type type = typeof(TSource);

MemberExpression member = propertyLambda.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' does not refers to a property.",
propertyLambda.ToString()));

PropertyInfo propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' does not refers to a property.",
propertyLambda.ToString()));

var attributes = propInfo.GetCustomAttributes(typeof(StaticFieldNameAttribute), true);
if (attributes.Length == 0)
throw new InvalidOperationException(string.Format(
"Property '{0}' does not applied any StaticFieldNameAttribute.",
propInfo.Name));
else
return (attributes.Last() as StaticFieldNameAttribute).Name;
}

第一个参数没变,仅仅加了一个this关键字来讲方法变成扩展方法,简化调用。

第二个参数变成了Expression<Func<TSource, TProperty>> 类型,它是一个表达式,这个表达式需要从TSource类型的对象中选取一个属性,我们可以通过分析这个表达式来拿到属性的定义,然后进一步获取修饰它的Attribute。

改进过的使用方法:

field1.Value = entity.Property1;
field1.Name = entity.GetStaticFieldName(e => e.Property1);

改进版完全摆脱了使用字符串值传递属性或字段名称的不靠谱方式,如果属性名称发生变化,在编译时就能发现(也能充分享受Visual Studio提供的重构功能)。

发表评论

电子邮件地址不会被公开。 必填项已用*标注