关于c#:Enum列出作为扩展名?


Enum to list as an extension?

我有各种枚举用作下拉列表的源,为了提供用户友好的描述,我向每个枚举添加了Description属性,然后执行以下操作:

1
2
3
4
var list = Enum.GetValues(typeof(MyEnum))
               .Cast<MyEnum>()
               .ToDictionary(k => k, v => v.GetAttributeOfType<DescriptionAttribute>().Description)
               .ToList();

上面是重复的,因为我必须在很多地方使用它。我尝试添加扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    public static T GetAttributeOfType<T>(this Enum enumVal) where T : System.Attribute
    {
        var type = enumVal.GetType();
        var memInfo = type.GetMember(enumVal.ToString());
        var attributes = memInfo[0].GetCustomAttributes(typeof(T), false);

        return (attributes.Length > 0) ? (T)attributes[0] : null;
    }

    public static KeyValuePair<T, string> ToList<T>(this Enum source)
    {
        return Enum.GetValues(typeof(T))
                   .Cast<T>()
                   .ToDictionary(k => k, v => v.GetAttributeOfType<DescriptionAttribute>().Description)
                   .ToList();
    }

但是,我得到一个例外:

Cannot convert lambda expression to type 'System.Collections.Generic.IEqualityComparer' because it is not a delegate type

使用它作为扩展的正确方法是什么(使用上述2种方法)?


What is the correct way to use it as an extension (using the above 2 methods)?

没有正确的方法将其用作扩展名。当您有一个值(实例)并且希望获得与该值相关的一些信息时,将使用扩展方法(类似于实例方法)。因此,如果您想获得单个Enum值的描述,那么扩展方法是有意义的。

但是,在您的情况下,您需要的信息(Enum值/描述对列表)与特定的Enum值无关,而是与Enum类型有关。这意味着您只需要一个类似于Enum.TryParse的普通静态泛型方法。理想情况下,您可以将泛型参数约束为只允许Enum,但这种类型的约束尚不受支持,因此我们将仅使用where TEnum : struct(类似于上述系统方法),并添加运行时检查。

下面是一个示例实现:

1
2
3
4
5
6
7
8
9
10
11
public static class EnumInfo
{
    public static List<KeyValuePair<TEnum, string>> GetList<TEnum>()
        where TEnum : struct
    {
        if (!typeof(TEnum).IsEnum) throw new InvalidOperationException();
        return ((TEnum[])Enum.GetValues(typeof(TEnum)))
           .ToDictionary(k => k, v => ((Enum)(object)v).GetAttributeOfType<DescriptionAttribute>().Description)
           .ToList();
    }
}

用途:

1
2
3
4
5
6
7
8
9
10
11
public enum MyEnum
{
    [Description("Foo")]
    A,
    [Description("Bar")]
    B,
    [Description("Baz")]
    C,
}

var list = EnumInfo.GetList<MyEnum>();

我的堆栈中有这个扩展方法,并且一直在为相同的事情使用它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static string Description(this Enum @enum)
{
    try
    {
        var @string = @enum.ToString();

        var attribute =
            @enum.GetType()
                 .GetField(@string)
                 .GetCustomAttribute<DescriptionAttribute>(false);

        return attribute != null ? attribute.Description : @string;
    }
    catch // Log nothing, just return an empty string
    {
        return string.Empty;
    }
}

示例用法:

1
MyEnum.Value.Description(); // The value from within the description attr.

此外,您可以使用此方法获取用于绑定目的的IDictionary。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static IDictionary<string, string> ToDictionary(this Type type)
{
    if (!type.IsEnum)
    {
        throw new InvalidCastException("'enumValue' is not an Enumeration!");
    }

    var names = Enum.GetNames(type);
    var values = Enum.GetValues(type);

    return Enumerable.Range(0, names.Length)
                     .Select(index => new
                     {
                         Key = names[index],
                         Value = ((Enum)values.GetValue(index)).Description()
                     })
                     .ToDictionary(k => k.Key, k => k.Value);
}

像这样使用它:

1
var dictionary = typeof(MyEnum).ToDictionary();

更新

这是一个正在工作的.NET小提琴。

1
2
3
4
5
6
7
public static Dictionary<TEnum, string> ToDictionary<TEnum>(this Type type)
    where TEnum : struct, IComparable, IFormattable, IConvertible
{
    return Enum.GetValues(type)
               .OfType<TEnum>()
               .ToDictionary(value => value, value => value.Description());
}

然后这样使用:

1
2
3
4
5
6
7
8
9
public enum Test
{
    [Description("A test enum value for 'Foo'")]
    Foo,
    [Description("A test enum value for 'Bar'")]
    Bar
}

typeof(Test).ToDictionary<Test>()


您可以创建一个将EnumAttribute作为泛型参数的泛型方法。

要获取任何属性,可以创建如下扩展方法:

1
2
3
4
5
6
7
8
9
public static string AttributeValue<TEnum,TAttribute>(this TEnum value,Func<TAttribute,string> func) where T : Attribute
{
   FieldInfo field = value.GetType().GetField(value.ToString());

   T attribute = Attribute.GetCustomAttribute(field, typeof(T)) as T;

   return attribute == null ? value.ToString() : func(attribute);

}

下面是将其转换为字典的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static Dictionary<TEnum,string> ToDictionary<TEnum,TAttribute>(this TEnum obj,Func<TAttribute,string> func)
  where TEnum : struct, IComparable, IFormattable, IConvertible
  where TAttribute : Attribute
    {

        return (Enum.GetValues(typeof(TEnum)).OfType<TEnum>()
            .Select(x =>
                new
                {
                    Value = x,
                    Description = x.AttributeValue<TEnum,TAttribute>(func)
                }).ToDictionary(x=>x.Value,x=>x.Description));



    }

你可以这样称呼它:

1
2
 var test =  eUserRole.SuperAdmin
                      .ToDictionary<eUserRole,EnumDisplayNameAttribute>(attr=>attr.DisplayName);

我使用这个枚举和属性作为示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class EnumDisplayNameAttribute : Attribute
{
    private string _displayName;
    public string DisplayName
    {
        get { return _displayName; }
        set { _displayName = value; }
    }
}  

public enum eUserRole : int
{
    [EnumDisplayName(DisplayName ="Super Admin")]
    SuperAdmin = 0,
    [EnumDisplayName(DisplayName ="Phoenix Admin")]
    PhoenixAdmin = 1,
    [EnumDisplayName(DisplayName ="Office Admin")]
    OfficeAdmin = 2,
    [EnumDisplayName(DisplayName ="Report User")]
    ReportUser = 3,
    [EnumDisplayName(DisplayName ="Billing User")]
    BillingUser = 4
}

输出:

enter image description here


尝试替换

1
.ToDictionary(k => k, v => v.GetAttributeOfType<DescriptionAttribute>().Description)

具有

16

在您的示例中,正在调用错误的toDictionary()重载。


每当我需要一个枚举(一个已知值的静态列表)时,它需要的不仅仅是一个整数值和一个字符串对应项,最后我会使用这个枚举实用工具类,它基本上给了我类似Java的枚举行为。

所以这是我的第一个选择,如果我是欧普的鞋子,因为这将使它真正的琐碎实现他/她想要的。

但是,假设这不是OP的选择,她/他需要坚持使用C Enums,我将同时使用ehsan sajjad和frank-j解决方案:

  • 有一个扩展方法返回给定枚举的描述项目,这几乎是欧普已经有了;
  • 使用静态helper方法返回给定枚举类型的项及其各自描述的字典。
  • 以下是我将如何实现这一点:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static class EnumUtils
    {
        public static string GetDescription(this Enum enumVal)
        {
            var type = enumVal.GetType();
            var memInfo = type.GetMember(enumVal.ToString());
            var attributes = memInfo[0].GetCustomAttributes(typeof (DescriptionAttribute), false);

            return (attributes.Length > 0) ? ((DescriptionAttribute) attributes[0]).Description : null;
        }

        public static Dictionary<TEnum, string> GetItemsWithDescrition<TEnum>()
        {
            var enumType = typeof(TEnum);
            if (!enumType.IsEnum)
            {
                throw new InvalidOperationException("TEnum must be an enum type");
            }

            return Enum
                    .GetValues(enumType)
                    .Cast<TEnum>()
                    .ToDictionary(enumValue => enumValue, enumValue => GetDescription(enumValue as Enum));
        }
    }

    下面是用法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    public class EnumUtilsTests
    {
        public enum MyEnum
        {
            [Description("Um")]
            One,
            [Description("Dois")]
            Two,
            [Description("Tres")]
            Three,
            NoDescription
        }

        public void Should_get_enum_description()
        {
            MyEnum.One.GetDescription().ShouldBe("Um");
            MyEnum.Two.GetDescription().ShouldBe("Dois");
            MyEnum.Three.GetDescription().ShouldBe("Tres");
            MyEnum.NoDescription.GetDescription().ShouldBe(null);
        }

        public void Should_get_all_enum_values_with_description()
        {
            var response = EnumUtils.GetItemsWithDescrition<MyEnum>();

            response.ShouldContain(x => x.Key == MyEnum.One && x.Value =="Um");
            response.ShouldContain(x => x.Key == MyEnum.Two && x.Value =="Dois");
            response.ShouldContain(x => x.Key == MyEnum.Three && x.Value =="Tres");
            response.ShouldContain(x => x.Key == MyEnum.NoDescription && x.Value == null);
        }
    }

    另一个观点是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    class Program
    {
        //Example enum
        public enum eFancyEnum
        {
            [Description("Obsolete")]
            Yahoo,
            [Description("I want food")]
            Meow,
            [Description("I want attention")]
            Woof,
        }
        static void Main(string[] args)
        {
            //This is how you use it
            Dictionary<eFancyEnum, string> myDictionary = typeof(eFancyEnum).ToDictionary<eFancyEnum>();
        }
    }

    public static class EnumExtension
    {
        //Helper method to get description
        public static string ToDescription<T>(this T en)
        {
             Type type = en.GetType();
             MemberInfo[] memInfo = type.GetMember(en.ToString());
             if (memInfo != null && memInfo.Length > 0)
             {
                object[] attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
                if (attrs != null && attrs.Length > 0)
                   return ((DescriptionAttribute)attrs[0]).Description;
             }
             return en.ToString();
        }

        //The actual extension method that builds your dictionary
        public static Dictionary<T, string> ToDictionary<T>(this Type source) where T : struct, IConvertible
        {
             if(!source.IsEnum || typeof(T) != source)
             {
                throw new InvalidEnumArgumentException("BOOM");
             }

             Dictionary<T, string> retVal = new Dictionary<T,string>();

             foreach (var item in Enum.GetValues(typeof(T)).Cast<T>())
              {
                retVal.Add(item, item.ToDescription());
              }

             return retVal;
        }
    }