Hello World
Spiga

一个利用扩展方法的实例:AttachDataExtensions

2009-01-07 14:05 by 老赵, 16512 visits

扩展方法是C# 3.0(老赵对VB不熟)中最简单,也是最常用的语言特性之一。这是老赵自以为的一个简单却不失经典的实例:

[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
public class AttachDataAttribute : Attribute
{
    public AttachDataAttribute(object key, object value)
    {
        this.Key = key;
        this.Value = value;
    }

    public object Key { get; private set; }

    public object Value { get; private set; }
}

public static class AttachDataExtensions
{
    public static object GetAttachedData(
        this ICustomAttributeProvider provider, object key)
    {
        var attributes = (AttachDataAttribute[])provider.GetCustomAttributes(
            typeof(AttachDataAttribute), false);
        return attributes.First(a => a.Key.Equals(key)).Value;
    }

    public static T GetAttachedData<T>(
        this ICustomAttributeProvider provider, object key)
    {
        return (T)provider.GetAttachedData(key);
    }

    public static object GetAttachedData(this Enum value, object key)
    {
        return value.GetType().GetField(value.ToString()).GetAttachedData(key);
    }

    public static T GetAttachedData<T>(this Enum value, object key)
    {
        return (T)value.GetAttachedData(key);
    }
}

AttachDataAttribute是一个自定义属性,可以在各式成员上进行标记。它允许任何类型的Key和任何类型的Value,而开发人员就可以使用GetAttachedData方法,通过一个Key来获得其对应的Value。这个方法比较简单,但是大量用于老赵的项目中。以下便是一例:

public enum AgeRange
{ 
    [AttachData(AgeRangeAttachData.Text, "18岁及以下")]
    LessThan18,

    [AttachData(AgeRangeAttachData.Text, "19至29岁")]
    From19To29,

    [AttachData(AgeRangeAttachData.Text, "30岁及以上")]
    Above29
}

public enum AgeRangeAttachData
{ 
    Text
}

public static class AgeRangeExtensions
{
    public static string GetText(this AgeRange range)
    {
        return range.GetAttachedData<string>(AgeRangeAttachData.Text);
    }
}

枚举的每一项其实相当于一个只读的共有Field,也可以加上自定义属性。我们为AgeRange枚举的每一项加上一个以AgeRangeAttachData为Key的附加值作为该枚举的显示文字,并且针对AgeRange编写一个扩展方法GetText。于是代码中就可以使用以下方式来获取某个枚举值的显示文字了:

AgeRange.From19To29.GetText()

关于上面的使用方式,有几个需要提一下的地方:

  • 虽然可以使用任意类型作Key,但是一般建议使用枚举来作为一个强类型的Key。
  • 一般可以为扩展的内容再增加一个扩展方法来获取“同一组”附加数据,以供各个地方重复调用。
  • GetAttachedData使用反射并且遍历所有的AttachDataAttribute,如果您觉得需要提高性能,那么可以自行添加缓存。
  • 以上使用方法在普通情况下可以接受,但其实它并不是Best Practice。一般来说,文字内容都应该提取至外部资源文件中。

AttachDataExtensions的使用很广泛,以后老赵的文章中也会经常使用这个扩展。特此记录,已备引用。

Creative Commons License

本文基于署名 2.5 中国大陆许可协议发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名赵劼(包含链接),具体操作方式可参考此处。如您有任何疑问或者授权方面的协商,请给我留言

Add your comment

48 条回复

  1. 老赵
    admin
    链接

    老赵 2009-01-07 14:10:00

    简单了点

  2. 紫色永恒
    *.*.*.*
    链接

    紫色永恒 2009-01-07 14:24:00

    有的时候简单更美

  3. dali[未注册用户]
    *.*.*.*
    链接

    dali[未注册用户] 2009-01-07 14:41:00

    可以显示枚举值的本地化字符串时用

  4. 老赵
    admin
    链接

    老赵 2009-01-07 14:43:00

    @dali
    其实best practice还是用外部资源文件。

  5. GUO Xingwang
    *.*.*.*
    链接

    GUO Xingwang 2009-01-07 14:59:00

    看了半天终于看明白了,老赵还说简单呢!惭愧啊

  6. 幸运草
    *.*.*.*
    链接

    幸运草 2009-01-07 15:16:00

    没看明白是啥意思

  7. Kevin-moon
    *.*.*.*
    链接

    Kevin-moon 2009-01-07 15:18:00

    不错的方式 又可以少写些代码了

  8. Kevin-moon
    *.*.*.*
    链接

    Kevin-moon 2009-01-07 15:20:00

    AgeRangeAttachData这个有什么意义吗?
    感觉没有存在的必要呀!

  9. qqm[未注册用户]
    *.*.*.*
    链接

    qqm[未注册用户] 2009-01-07 15:31:00

    看不懂。。。

    另外,我复制了代码想研究下,

    public object Key { get; private set; }
    public object Value { get; private set; }

    这2句出错了:
    必须声明主体,因为它未标记为 abstract 或 extern

    为什么?

  10. 老赵
    admin
    链接

    老赵 2009-01-07 15:46:00

    @qqm
    需要C# 3.0

  11. TerryLee
    *.*.*.*
    链接

    TerryLee 2009-01-07 15:48:00

    --引用--------------------------------------------------
    Kevin-moon: AgeRangeAttachData这个有什么意义吗?
    感觉没有存在的必要呀!
    --------------------------------------------------------
    只是表示一个强类型的Key而已,如果不习惯,完全可以使用字符串或者其它的来表示,老赵不是说了嘛,建议使用枚举来作为一个强类型的Key。

  12. crosssky[未注册用户]
    *.*.*.*
    链接

    crosssky[未注册用户] 2009-01-07 15:54:00

    不错的方法啊。多谢楼主分享。

    PS:9楼的兄弟可能是你的。Net版本不对引起。 这是。Net3中新增 的定义方式。

  13. Kevin-moon
    *.*.*.*
    链接

    Kevin-moon 2009-01-07 15:59:00

    @TerryLee
    建议使用枚举来作为一个强类型的Key
    -------------------------------
    WHY?! 使用枚举比字符的优势在哪里呢?

  14. qqm[未注册用户]
    *.*.*.*
    链接

    qqm[未注册用户] 2009-01-07 16:04:00

    恩,我的是2.0的

  15. TerryLee
    *.*.*.*
    链接

    TerryLee 2009-01-07 16:04:00

    --引用--------------------------------------------------
    Kevin-moon: @TerryLee
    建议使用枚举来作为一个强类型的Key
    -------------------------------
    WHY?! 使用枚举比字符的优势在哪里呢?
    --------------------------------------------------------
    简单来说就是两个字:安全,不要把错误推迟到运行时……

  16. brightwang
    *.*.*.*
    链接

    brightwang 2009-01-07 16:40:00

    碰到类似问题的时候我都是用静态string字段,看来我很土。。。

  17. 紫色阴影
    *.*.*.*
    链接

    紫色阴影 2009-01-07 17:06:00

    不要key也是可以的吧,enum通过访问自己的attribute拿到text,比如
    [AttachData("18岁及以下")]
    LessThan18

    return attributes.First().Value;


  18. 老赵
    admin
    链接

    老赵 2009-01-07 17:20:00

    --引用--------------------------------------------------
    紫色阴影: 不要key也是可以的吧,enum通过访问自己的attribute拿到text,比如
    [AttachData("18岁及以下")]
    LessThan18

    return attributes.First().Value;
    --------------------------------------------------------
    无论怎么写都是根据你的需要来的,你真需要的话当然也可以这么做。

  19. nfa2dfa
    *.*.*.*
    链接

    nfa2dfa 2009-01-07 18:18:00

    --引用--------------------------------------------------
    Jeffrey Zhao: --引用--------------------------------------------------
    紫色阴影: 不要key也是可以的吧,enum通过访问自己的attribute拿到text,比如
    [AttachData(&quot;18岁及以下&quot;)]
    LessThan18

    return attributes.First().Value;
    --------------------------------------------------------
    无论怎么写都是根据你的需要来的,你真需要的话当然也可以这么做。
    --------------------------------------------------------

    不知老赵的需求是什么样的,当不止有提取文本的需求时,AgeRangeExtensions是否针对AgeRangeAttachData每一个枚举写一个拓展方法?我想老赵可否展示一下更为全面的代码。

  20. 老赵
    admin
    链接

    老赵 2009-01-07 18:23:00

    @nfa2dfa
    没错,为AgeRangeAttachData多加一些枚举项作为Key。

  21. nfa2dfa
    *.*.*.*
    链接

    nfa2dfa 2009-01-07 18:35:00

    --引用--------------------------------------------------
    Jeffrey Zhao: @nfa2dfa
    没错,为AgeRangeAttachData多加一些枚举项作为Key。
    --------------------------------------------------------
    我想问的是AgeRangeExtensions对应的处理
    不知是否是这个样子
    public static class AgeRangeExtensions
    {
    public static string GetAttachedData<T>(this AgeRange range, AgeRangeAttachData key)
    {
    return range.GetAttachedData<T>(key);
    }
    }

  22. nfa2dfa
    *.*.*.*
    链接

    nfa2dfa 2009-01-07 18:37:00

    不好意思我思维混乱了,多此一举多此一举

  23. nfa2dfa
    *.*.*.*
    链接

    nfa2dfa 2009-01-07 18:41:00

    确实是一个简单的好办法

  24. xiao_p(未登陆)[未注册用户]
    *.*.*.*
    链接

    xiao_p(未登陆)[未注册用户] 2009-01-07 20:54:00

    恩,不错,比直接弄个dictionary<key,value>更清晰。。。

    不简单了,扩展方法,自定义attribute能用的都用了,呵呵!

  25. 老赵
    admin
    链接

    老赵 2009-01-07 20:56:00

    @xiao_p(未登陆)
    没错,就是为了替代Dic的。

  26. 昊子
    *.*.*.*
    链接

    昊子 2009-01-12 18:25:00

    實現方式是不錯

    enum 的name我們通常用在編碼層,value用在數據層
    .net框架應該提供表現層屬性,不知道是不是設計師們還沒意識到

  27. wwwwwwwwwwwwwwww[未注册用户…
    *.*.*.*
    链接

    wwwwwwwwwwwwwwww[未注册用户] 2009-01-20 10:17:00

    public static object GetAttachedData(
    this ICustomAttributeProvider provider, object key)
    {
    var attributes = (AttachDataAttribute[])provider.GetCustomAttributes(
    typeof(AttachDataAttribute), false);
    return attributes.First(a => a.Key.Equals(key)).Value;
    }

    return attributes.First(a => a.Key.Equals(key)).Value;

    这里报错, 'System.Array' does not contain a definition for 'First'
    谁能帮忙看看

  28. 老赵
    admin
    链接

    老赵 2009-01-20 10:27:00

    @wwwwwwwwwwwwwwww
    .net 3.5 please.

  29. wwwwwwwwwwwwwwww[未注册用户…
    *.*.*.*
    链接

    wwwwwwwwwwwwwwww[未注册用户] 2009-01-20 11:27:00

    奇怪,是.NET 3.5的啊? 而且引用了System.Core;
    .net 3.5 还要有补丁? 还是VS2008SP1 , 这些都没打过...

  30. 老赵
    admin
    链接

    老赵 2009-01-20 11:52:00

    @wwwwwwwwwwwwwwww
    不用的。
    System.Linq命名空间又没有引入?

  31. wwwwwwwwwwwwwwww[未注册用户…
    *.*.*.*
    链接

    wwwwwwwwwwwwwwww[未注册用户] 2009-01-20 14:28:00

    好的,谢谢老赵, 就是缺少System.Linq; 没想到attributes.First跟System.Linq有联系.
    Net 3.5新特性基本没用过.需要扫盲了, 工作之余继续学习老赵的文章....

  32. 痘痘熊
    *.*.*.*
    链接

    痘痘熊 2009-02-13 14:15:00

    想点办法支持多国语言吧……

  33. 老赵
    admin
    链接

    老赵 2009-02-13 14:31:00

    @痘痘熊
    资源文件,最容易了,呵呵。

  34. 痘痘熊
    *.*.*.*
    链接

    痘痘熊 2009-02-13 17:47:00

    @Jeffrey Zhao
    我是说你把这个扩展搞成支持多国语言的,那样就彻底好用了~

  35. 老赵
    admin
    链接

    老赵 2009-02-13 22:04:00

    --引用--------------------------------------------------
    痘痘熊: @Jeffrey Zhao
    我是说你把这个扩展搞成支持多国语言的,那样就彻底好用了~
    --------------------------------------------------------
    怎么说呢,其实这个扩展和多国语言没有任何关系,只是我举的一个使用示例涉及到了这方面内容。所以要只是国际化,不应该从这个扩展下手吧。

  36. 朱才
    *.*.*.*
    链接

    朱才 2009-05-28 14:25:00

    郁闷了一把,果然很像,况且你写的早很多,难怪别人说我拷贝的你的,汗啊!不怪他。
    http://www.cnblogs.com/zhucai/archive/2009/05/27/enum_bind.html

  37. david.[未注册用户]
    *.*.*.*
    链接

    david.[未注册用户] 2009-10-16 21:58:00

    老赵,谢谢你。你已经把你的代码稍作改进了一下,可以用在项目中了。

    http://daiwei006.spaces.live.com/blog/cns!4EC78FD5128595F8!1373.entry

  38. silverage
    218.249.88.*
    链接

    silverage 2010-04-06 13:36:19

    老赵,为啥不用DescriptionAttribute呢?

  39. 老赵
    admin
    链接

    老赵 2010-04-06 13:41:15

    @silverage: 老赵,为啥不用DescriptionAttribute呢?

    没明白啥意思啊。

  40. silverage
    218.249.88.*
    链接

    silverage 2010-04-07 13:37:21

    @老赵: 没明白啥意思啊。

    为啥不用.net提供的DescriptionAttribute来描述枚举,而自己写一个Attribute呢?

  41. 老赵
    admin
    链接

    老赵 2010-04-07 17:55:10

    @silverage 为啥不用.net提供的DescriptionAttribute来描述枚举,而自己写一个Attribute呢?

    通用咯,作为示例我是用AttachData附加了Description,但又不是只能用来放Description,呵呵。

  42. straybird
    219.137.209.*
    链接

    straybird 2010-04-29 17:00:05

    赵老师,你好。

    我想问下对于类(class)的自定义属性(CustomAttributes)如何访问到呢?比如你这个例子中,如果我写了如下的类:

    [AttachData(AgeRangeAttachData.Text, "this is CustomAttributes text")]
    public class myclass 
    {
        public string str { get; set; }
    }
    

    应用你的方法,GetField()返回的值为空哦。

  43. 老赵
    admin
    链接

    老赵 2010-04-29 18:29:01

    @straybird

    太基础的问题就不应该问了,随便找本书就有。

  44. 木有ID
    59.56.202.*
    链接

    木有ID 2011-08-08 10:06:08

    我是来表示感谢的

  45. 链接

    一刀一个 2011-12-28 14:32:44

    我想问一下,

    public static object GetAttachedData(
        this ICustomAttributeProvider provider,
        object key)
    {
        var attributes = (AttachDataAttribute[])provider.GetCustomAttributes(
                typeof(AttachDataAttribute), false);
        return attributes.First(x => x.Key.Equals(key)).Value;
    }
    

    在没有数据的情况下会抛出异常.

    public static T GetAttachedData<T>(
        this ICustomAttributeProvider provider,
        object key)
    {
        return (T)provider.GetAttachedData(key);
    }
    

    方法也是这样,如果我调用这两个方法的时候. 一般是在什么地方处理这些异常呢?

    chrome下面回帖不能添加代码显示,唉.

  46. 老赵
    admin
    链接

    老赵 2011-12-28 17:30:07

    @一刀一个

    看帮助啊,还有预览做的那么详细了,怎么还能这么乱。什么地方处理异常就要视情况而定了,异常也是API设计的一部分。

  47. 链接

    一刀一个 2011-12-28 17:59:21

    @老赵

    汗...chrome上面点帮助就是脚本错误. chrome版本: 17.0.963.12 dev-m

    有没有异常设计的文章?

  48. 喔喔兒
    123.177.18.*
    链接

    喔喔兒 2012-07-18 10:30:43

    偶然看了一眼右边,老赵居然在勾搭妹子..


    我的推特

    @tinytinyface 求……
    

发表回复

登录 / 登录并记住我 ,登陆后便可删除或修改已发表的评论 (请注意保留评论内容)

昵称:(必填)

邮箱:(必填,仅用于Gavatar

主页:(可选)

评论内容(大于5个字符):

  1. Your Name yyyy-MM-dd HH:mm:ss

使用Live Messenger联系我