Generic Enum to SelectList extension method

雨燕双飞 提交于 2020-01-13 10:08:41

问题


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
}

回答1:


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



回答2:


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



回答3:


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.




回答4:


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

}


来源:https://stackoverflow.com/questions/18145161/generic-enum-to-selectlist-extension-method

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!