Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enum to list as an extension?

Tags:

c#

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

like image 653
Ivan-Mark Debono Avatar asked Mar 24 '16 19:03

Ivan-Mark Debono


3 Answers

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>();
like image 159
Ivan Stoev Avatar answered Oct 15 '22 05:10

Ivan Stoev


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>()
like image 28
David Pine Avatar answered Oct 15 '22 07:10

David Pine


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

like image 40
Ehsan Sajjad Avatar answered Oct 15 '22 07:10

Ehsan Sajjad