4

I'm trying to convert an enum to a List as mentioned in this example e.g.

Enum.GetValues(typeof(MyEnum)).Cast<MyEnum>().Select(v => new SelectListItem {
    Text = v.ToString(),
    Value = ((int)v).ToString()
}).ToList();

This work but I want to amend it to work with a generic enum

public static List<SelectListItem> GetEnumList<TEnum>(TEnum value)
{

        return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Select(v => new SelectListItem
        {
            Text = v.ToString(),
            Value = ((int)v).ToString()
        }).ToList();
 }

However the above code doesn't compile and gives

Cannot convert type 'TEnum' to 'int'

for the line

  Value = ((int)v).ToString()
  1. How do I fix this above code.

  2. Why is it giving a compile error with generic enum and not with a normal enum


Edit: I have tried the suggestions in the thread but I get a further error:

Here is my full code:

public static IHtmlContent EnumDropDownListFor<TModel, TResult,TEnum>(
    this IHtmlHelper<TModel> htmlHelper,
    Expression<Func<TModel, TResult>> expression,
    TEnum enumValue,
    string optionLabel) 
{
    return htmlHelper.DropDownListFor(expression, (IEnumerable<SelectListItem>)GetEnumList(enumValue), optionLabel);
}

public static List<SelectListItem> GetEnumList<TEnum>(TEnum value) 
{
    return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Select(v => new SelectListItem
    {
        Text = v.ToString(),
        Value = Convert.ToInt32(v).ToString()
    }).ToList();

}

but I get a runtime error

ArgumentException: Type provided must be an Enum.

Parameter name: enumType

on the line

return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Select(v => new SelectListItem
    {
        Text = v.ToString(),
        Value = Convert.ToInt32(v).ToString()
    }).ToList();

What do I need to fix in the code to not get the runtime error.

Community
  • 1
  • 1
zoaz
  • 1,981
  • 4
  • 17
  • 28

5 Answers5

6

You've told the compiler nothing about TEnum. As far as its concerned, it could be a string, a DateTime, a BankAccount, a Bullet or anything.

To get this to work, you can use Enum.Parse and Convert.ToInt32

UPD: Let me just format the code from comment and fix compilation errors for SO-copy-pasters :D

public static int GetEnumIntValue<T>(T value)
    where T : struct
{
    Type genericType = typeof(T);
    Debug.Assert(genericType.IsEnum);
    Enum enumValue = Enum.Parse(genericType, value.ToString()) as Enum;
    return Convert.ToInt32(enumValue);
}
moudrick
  • 2,148
  • 1
  • 22
  • 34
Colin Grealy
  • 615
  • 4
  • 12
  • Can you provide an example how to use the Enum.Parse in the code example – zoaz Nov 17 '15 at 12:30
  • 1
    `public static int GetEnumIntValue(T value) where T : struct { Type genericType = typeof(T); Debug.Assert(genericType.IsEnum); Enum enumValue = Enum.Parse(typeof(T), value.ToString()) as Enum; return Convert.ToInt32(test); }` A bit of google fu on your part should easily have found that. – Colin Grealy Nov 17 '15 at 21:23
  • Thanks @ColinGrealy! Let me just format your code and fix coimpilation errors for SO-copy-pasters :D (see edited answer) – moudrick Sep 19 '17 at 10:00
4

You don't need a value, just the enum type. You can use something like this

Generic

public static List<SelectListItem> GetEnumList<TEnum>()
    where TEnum : struct, IConvertible, IFormattable
{
    return ((TEnum[])Enum.GetValues(typeof(TEnum))).Select(v => new SelectListItem
    {
        Text = v.ToString(),
        Value = v.ToString("d", null)
    }).ToList();
}

Non generic

public static List<SelectListItem> GetEnumList(Type enumType)
{
    return Enum.GetValues(enumType).Cast<IFormattable>().Select(v => new SelectListItem
    {
        Text = v.ToString(),
        Value = v.ToString("d", null)
    }).ToList();
}

Both methods are not compile type safe. Generic, because of the lack of an enum connstraint. Non generic - well, it's similar to the Enum static methods which are not compile type safe anyway. Both methods will throw runtime exception if called with non enum type.

Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • non-generic method is not compile-type safe, because *any* type can be passed to method, including non enum – ASh Nov 17 '15 at 13:13
  • @Ash Correct, but same for the generic one, due to the lack of an enum constraint. Both can throw runtime exception if not called with an enum type. Which is similar to `Enum` methods. – Ivan Stoev Nov 17 '15 at 13:14
  • If I use the generic way As soon as I add that where clause of "where TEnum : struct, IConvertible, IFormattable " my code won't compile Error CS0453 The type 'Type' must be a non-nullable value type in order to use it as parameter 'TEnum' in the generic type or method 'MyHelperClass.GetEnumList(TEnum)' – zoaz Nov 17 '15 at 13:52
  • @ozdev You need to add the same constraint to the calling method - where `TEnum : struct, IConvertible, IFormattable` - this is the closest to the currently missing `enum` constraint. If it was there, both calling method and the generic method would have been constrained with `where TEnum : enum`. – Ivan Stoev Nov 17 '15 at 14:05
  • I get that error message when I add the where clause it to both the calling method and the method being called – zoaz Nov 17 '15 at 14:48
  • @ozdev So probably you have another generic method calling yours. The type constraints must be the same in all generic methods involved. That's why I provided the non generic version - you can use it from your `EnumDropDownListFor` (w/o putting constraints) like `GetEnumList(typeof(TEnum))`. – Ivan Stoev Nov 17 '15 at 15:06
1
public static List<SelectListItem> GetEnumList<TEnum>(TEnum value) where TEnum : IConvertible
{
    return Enum.GetValues(typeof(TEnum)).Cast<TEnum>().Select(v => new SelectListItem
    {
        Text = v.ToString(),
        Value = v.ToInt32(null).ToString()
    }).ToList();
}
  1. Complier know that you use Enum type after .Cast<MyEnum>().
  2. But after .Cast<TEnum>() compiler doesn't know about type which can be.
Denis
  • 5,894
  • 3
  • 17
  • 23
1

compiler complains because type parameter in generic method is not constrained

also method doesn't have to be generic

public static List<SelectListItem> GetEnumList(Enum value)
{
    return Enum.GetValues(value.GetType())
            .Cast<Enum>()
            .Select(v => new SelectListItem
                        {
                            Text = v.ToString(),
                            Value = Convert.ToInt32(v).ToString()
                        })
            .ToList();
}

here still can be a problem, if underlying type of enum was not int:

public enum LongEnum: ulong
{
    A = 0,
    B = 3000000000
}

// Run-time exception: Value was either too large or too small for an Int32.
ASh
  • 34,632
  • 9
  • 60
  • 82
  • That's true, but then you have to call it with a value, not just the type. I.e: `GetEnumList(Blah.Test)` vs `GetEnumList()`. It works, but it might be confusing in that `Test` is not used at all. – Rob Nov 17 '15 at 11:39
  • @Rob, method in question takes 1 parameter. i didn't change this part. yes, indeed, a bit inconvenient, but there is no constraint for enums only – ASh Nov 17 '15 at 11:41
  • Of course - I wasn't attacking the answer, merely leaving a comment for future readers if they stumble upon it. There's really no clean way to do what OP whats, so each approach has its down sides. – Rob Nov 17 '15 at 11:44
  • 2
    For `ulong`, you can solve the problem with this: `Value = Convert.ChangeType(v, Enum.GetUnderlyingType(value.GetType())).ToString()` :) – Rob Nov 17 '15 at 11:51
  • @Rob, thank you for valuable comments. i tested your suggestion and it works as expected – ASh Nov 17 '15 at 11:54
  • I tried the code but I get a further error of "Type provided must be an Enum" – zoaz Nov 17 '15 at 12:31
  • @ozdev, i checked your edit and it seems that you are trying another suggestion than mine. i suggest to use non-generic method – ASh Nov 17 '15 at 12:34
  • If I changed both methods from TEnum to Enum it won't work cause I can't call @Html.EnumDropDownListFor(m => m.MyValue, MyEnum,"") as it will say "MyEnum is a type which isn't valid in the current context" – zoaz Nov 17 '15 at 13:09
  • Yes but its on the create MVC view screen so if I change it from MyValue I get runtime error NullReferenceException: Object reference not set to an instance of an object. – zoaz Nov 17 '15 at 13:20
  • Isn't it weird to ask passing a value in order to get a list? Like `GetEnumList(MyEnum.A)`. Which will be same as `GetEnumList(MyEnum.B)' etc. – Ivan Stoev Nov 17 '15 at 13:28
  • @ozdev, dropdown appeared when i have tried `@Html.EnumDropDownListFor(m=>m.MyValue, Model.MyValue, "")` – ASh Nov 17 '15 at 13:45
0

This is what works for me, Extention method :

public static List<IdNameDto> ToIdNameList<TEnum>(this TEnum @enum) where TEnum : Enum
{
    return Enum.GetValues(typeof(TEnum)).Cast<TEnum>()
        .Select(x => new IdNameDto
        {
            Id = Convert.ToInt32(x),
            Name = x.ToString(),
        }).ToList();
}

dto :

public class IdNameDto
{
    public int Id { get; set; }
    public string Name { get; set; }
}
Uttam Ughareja
  • 842
  • 2
  • 12
  • 21