Enum to list as an extension?

2020-07-05 07:28发布

I have various enums that I use as sources for dropdown lists, In order to provide for a user-friendly description, I added a Description attribute to each enum, and then do the following:

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

The above is repetitive because I have to use it in a lot of places. I tried to add an extension method:

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

However, I get an exception:

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

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

标签: c#
6条回答
小情绪 Triste *
2楼-- · 2020-07-05 07:34

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

There is no correct way to use it as an extension. Extension methods (similar to instance methods) are used when you have a value (instance) and for instance want to get some information related to that value. So the extension method would make sense if you want to get the description of a single enum value.

However, in your case the information you need (the list of enum value/description pairs) is not tied to a specific enum value, but to the enum type. Which means you just need a plain static generic method similar to Enum.TryParse<TEnum>. Ideally you would constrain the generic argument to allow only enum, but this type of constraint is not supported (yet), so we'll use (similar to the above system method) just where TEnum : struct and will add runtime check.

So here is a sample implementation:

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

and usage:

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

var list = EnumInfo.GetList<MyEnum>();
查看更多
SAY GOODBYE
3楼-- · 2020-07-05 07:35

Whenever I need an enumeration (a static list of known values) that need to have something more than just a mere integer value and a string counterpart, I end up using this Enumeration Utility class that essentially gives me java-like enumeration behavior.

So that would be my first option if I were on op's shoes as it would make it really trivial to achieve what he/she wants.

But, assuming this is not an option for op and she/he need to stick with C# enums, I would use a combination of both ehsan-sajjad and frank-j solutions:

  1. Have an extension method to return the description of a given enum item, which is pretty much what op had already;
  2. Have a static helper method to return a dictionary of items and their respective descriptions for a given enum type.

Here is how I would implement this:

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

And here is what the usage would look like:

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);
    }
}
查看更多
成全新的幸福
4楼-- · 2020-07-05 07:42

I have this extension method in my stack and use it for the same thing all the time.

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

Example usage:

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

Additionally, you can use this one to get a IDictionary for binding purposes.

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

Use it like so:

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

Update

Here is a working .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());
}

Then use it like this:

public enum Test
{
    [Description("A test enum value for 'Foo'")]
    Foo,
    [Description("A test enum value for 'Bar'")]
    Bar
}

typeof(Test).ToDictionary<Test>()
查看更多
The star\"
5楼-- · 2020-07-05 07:43

You can create a generic method which would take Enum and Attribute as generic argument.

For getting any attribute, you can create an extension method like:

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

}  

and here is the method for converting it to dictionary:

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



    }

You can call it this way:

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

I have used this Enum and Attribute as example:

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
}

Output:

enter image description here

查看更多
够拽才男人
6楼-- · 2020-07-05 07:50

Another take on this:

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;
    }
}
查看更多
ら.Afraid
7楼-- · 2020-07-05 07:52

Try replacing

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

with

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

In your example, the wrong overload of ToDictionary() is being called.

查看更多
登录 后发表回答