枚举转为列表的扩展方法?

14

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

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

以上内容重复出现是因为我需要在很多地方使用它。我尝试添加了一个扩展方法:
    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();
    }

然而,我遇到了一个异常:

无法将 lambda 表达式转换为类型 'System.Collections.Generic.IEqualityComparer',因为它不是委托类型

如何正确地将其作为扩展方法使用(使用上述 2 种方法)?


3
可以的。 - crashmstr
应该是 Enum.GetValues(typeof(source)) 吧? - Kevin
@Kevin 如果我这样做,会出现异常:找不到命名空间名称“source”的类型。 - Ivan-Mark Debono
第二种方法(ToList)很奇怪。首先,它无法编译。其次,返回类型不清楚 - 你传递一个单一的 Enum 值,然后使用 ToDictionary(..).ToList() 来制作列表,而返回类型是单个 KeyValuePair。那么它真正应该是单个值还是列表呢?如果是列表,那么它将成为一个奇怪的扩展方法,所以你必须传递一个枚举值来获取列表,比如 MyEnum.A.ToList<MyEnum>()。简而言之,你是在寻找本文开头重复代码的等效方法吗? - Ivan Stoev
检查此答案可能也有用:https://dev59.com/kGfWa4cB1Zd3GeqPg26o#12022617 - QMaster
6个回答

10
没有正确的方法来将其用作 扩展。扩展方法(类似于实例方法)是在您拥有一个(实例)并且例如想要获取与该值相关的某些信息时使用的。因此,如果您想获取单个enum值的描述,则扩展方法是有意义的。
但是,在您的情况下,您所需要的信息(enum值/描述对的列表)与特定的enum值无关,而是与enum类型相关联。这意味着您只需要一个类似于Enum.TryParse<TEnum> 的普通 静态 泛型方法。理想情况下,您会限制泛型参数仅允许enum,但不支持这种类型的约束(尚),因此我们将使用(类似于上面的系统方法) just where TEnum : struct 并添加运行时检查。
以下是示例实现:
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();
    }
}

和用法:

public enum MyEnum
{
    [Description("Foo")]
    A,
    [Description("Bar")]
    B,
    [Description("Baz")]
    C,
}

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

2

我在我的代码库中有一个扩展方法,我一直在使用它来做同样的事情。

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;
    }
}

使用示例:

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

此外,您可以使用此方法获取IDictionary以进行绑定。
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);
}

使用方式如下:

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

更新

这里提供一个可用的.NET Fiddle

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());
}

然后像这样使用它:
public enum Test
{
    [Description("A test enum value for 'Foo'")]
    Foo,
    [Description("A test enum value for 'Bar'")]
    Bar
}

typeof(Test).ToDictionary<Test>()

1
这个方法难道不是返回一个枚举的字符串吗? - Ivan-Mark Debono
你需要调用ToDictionary()扩展方法。 - David Pine

1
你可以创建一个通用方法,该方法将采用枚举属性作为通用参数。
要获取任何属性,您可以创建一个扩展方法,如下所示:
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);

}  

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

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));



    }

你可以这样称呼它:

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

我已经使用这个枚举和属性作为例子:
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


你在字典中失去了枚举类型,并将其替换为 int - Frank J
@FrankJ,如果我们想要的话,我们可以很容易地更改它,我已经更新了它。 - Ehsan Sajjad
我认为现在这就是原帖作者想要的 -> +1 - Frank J

0
每当我需要一个枚举(已知值的静态列表),它需要比仅仅是整数值和字符串对应项更多的东西时,我最终使用这个Enumeration Utility class,它基本上给了我类似于Java的枚举行为。
所以如果我在op的位置上,这将是我的第一选择,因为这将使他/她想要实现的事情变得非常简单。
但是,假设这对于op不是一个选项,他/她需要坚持使用C#枚举,我会使用ehsan-sajjadfrank-j解决方案的组合:
  1. 拥有一个扩展方法来返回给定枚举项的描述,这几乎就是op已经拥有的;
  2. 拥有一个静态辅助方法,用于返回给定枚举类型的项目及其相应描述的字典。
以下是我如何实现这一点:
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));
    }
}

下面是使用方法示例:

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);
    }
}

0

尝试替换

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

使用

.Select(t => new { k = t, v = t.GetAttributeOfType<DescriptionAttribute>().Description)
.ToDictionary(s => s.k, s => s.v)

在你的例子中,错误的ToDictionary()重载被调用了。

0

另一种看法:

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;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接