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

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

1
2
3
field1.Value = entity.Property1;
 
field1.Name = "Field1";

 

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

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

1
2
3
4
5
6
7
8
9
public class StaticFieldNameAttribute: Attribute
{
    public string Name { get; set; }
 
    public StaticFieldNameAttribute(string name)
    {
        this.Name = name;
    }
}

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

1
2
3
4
5
6
7
8
public class Entity
{
    [StaticFieldNameAttribute("Field1")]
    public string Property1 { get; set; }
 
    [StaticFieldNameAttribute("Field2")]
    public string Property2 { get; set; }
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
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;
}

调用的方法如下:

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

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

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

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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。

改进过的使用方法:

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

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

posted @ 2013-07-17 11:24  Li.DK  阅读(123)  评论(0)    收藏  举报