简体   繁体   中英

Generic Enum to SelectList extension method

I need to create a SelectList from any Enum in my project.

I have the code below which I create a select list from a specific enum, but I'd like to make an extension method for ANY enum. This example retrieves the value of the DescriptionAttribute on each Enum value

var list = new SelectList(
            Enum.GetValues(typeof(eChargeType))
            .Cast<eChargeType>()
            .Select(n => new
                {
                    id = (int)n, 
                    label = n.ToString()
                }), "id", "label", charge.type_id);

Referencing this post , how do I proceed?

public static void ToSelectList(this Enum e)
{
    // code here
}

What I think you are struggling with, is the retrieval of the description. I'm sure once you have those that you can define your final method which gives your exact result.

First, if you define an extension method, it works on a value of the enum, not on the enum type itself. And I think, for easy of usage, you would like to call the method on the type (like a static method). Unfortunately, you cannot define those.

What you can do is the following. First define a method which retrieves the description of the enum value, if it has one:

public static string GetDescription(this Enum value) {
    string description = value.ToString();
    FieldInfo fieldInfo = value.GetType().GetField(description);
    DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

    if (attributes != null && attributes.Length > 0) {
        description = attributes[0].Description;
    }
    return description;
}

Next, define a method which takes all values of an enum, and use the previous method to look up the value which we want to show, and return that list. The generic argument can be inferred.

public static List<KeyValuePair<TEnum, string>> ToEnumDescriptionsList<TEnum>(this TEnum value) {
    return Enum
        .GetValues(typeof(TEnum))
        .Cast<TEnum>()
        .Select(x => new KeyValuePair<TEnum, string>(x, ((Enum)((object)x)).GetDescription()))
        .ToList();
}

And finally, for ease of usage, a method to call it directly without value. But then the generic argument is not optional.

public static List<KeyValuePair<TEnum, string>> ToEnumDescriptionsList<TEnum>() {
    return ToEnumDescriptionsList<TEnum>(default(TEnum));
}

Now we can use it like this:

enum TestEnum {
    [Description("My first value")]
    Value1,
    Value2,
    [Description("Last one")]
    Value99
}

var items = default(TestEnum).ToEnumDescriptionsList();
// or: TestEnum.Value1.ToEnumDescriptionsList();
// Alternative: EnumExtensions.ToEnumDescriptionsList<TestEnum>()
foreach (var item in items) {
    Console.WriteLine("{0} - {1}", item.Key, item.Value);
}
Console.ReadLine();

Which outputs:

Value1 - My first value
Value2 - Value2
Value99 - Last one

Late to the party, but since there is no accepted answer and it might help others:

As @Maarten mentioned, an extension method works on the value of an enum, not the enum type itelf, so as with Maarteen's soultion you can create a dummy or default value to call the extension method on, however, you may find, as I did, that it is simpler to just use a static helper method like so:

public static class EnumHelper
{
    public static SelectList GetSelectList<T>(string selectedValue, bool useNumeric = false)
    {
        Type enumType = GetBaseType(typeof(T));

        if (enumType.IsEnum)
        {
            var list = new List<SelectListItem>();

            // Add empty option
            list.Add(new SelectListItem { Value = string.Empty, Text = string.Empty });

            foreach (Enum e in Enum.GetValues(enumType))
            {
                list.Add(new SelectListItem { Value = useNumeric ? Convert.ToInt32(e).ToString() : e.ToString(), Text = e.Description() });
            }

            return new SelectList(list, "Value", "Text", selectedValue);
        }

        return null;
    }

    private static bool IsTypeNullable(Type type)
    {
        return (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>));
    }

    private static Type GetBaseType(Type type)
    {
        return IsTypeNullable(type) ? type.GetGenericArguments()[0] : type;
    } 

You would create the select list like this:

 viewModel.ProvinceSelect =  EnumHelper.GetSelectList<Province>(model.Province);

or using the optional numeric values instead of strings:

viewModel.MonthSelect =  EnumHelper.GetSelectList<Month>(model.Month,true);

The basic idea for this I got from here , though I changed it to suit my needs. One thing I added was the ability to optionally use ints for the value. I also added an enum extension to get the description attribute which is based on this blog post:

public static class EnumExtensions
{
    public static string Description(this Enum 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();
    }       
} 

Since enum can't have extensions pinned to the entire collection a convenient way to extend the Enum base class is with a static typed class. This will allow concise code such as:

Enum<MyCustomEnumType>.GetSelectItems();

Which can be achieved with the following code:

public static class Enum<T>
{
    public static SelectListItem[] GetSelectItems()
    {
        Type type = typeof(T);
        return
            Enum.GetValues(type)
                .Cast<object>()
                .Select(v => new SelectListItem() { Value = v.ToString(), Text = Enum.GetName(type, v) })
                .ToArray();
    }
}

Since enum do not have a shared interface Type misuse is possible, but the class name Enum should dispell any confusion.

Here is a corrected [type casted value to int] and simplified [uses tostring override instead of getname] version of Nathaniels answer that returns a List instead of an array:

public static class Enum<T>
{
    //usage: var lst =  Enum<myenum>.GetSelectList();
    public static List<SelectListItem> GetSelectList()
    {
        return  Enum.GetValues( typeof(T) )
                .Cast<object>()
                .Select(i => new SelectListItem()
                             { Value = ((int)i).ToString()
                              ,Text = i.ToString() })
                .ToList();
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM