1

I was trying to make my code cleaner by moving something that I call a lot into a static method. The original code is:

List<ListItem> listItems = Enum
  .GetValues(typeof(TimelineinfoEnums.StateEnum))
  .Cast<TimelineinfoEnums.StateEnum>()
  .Select(x => new ListItem { 
     Text  = x.ToString(), 
     Value = (int)x })
  .ToList();

TimelineinfoEnums.StateEnum is just a place holder, the point of my method is to be able to call it, pass in a value, and make TimelineinfoEnums.StateEnum some other enum that I'm working with. After reading elsewhere on SO, that you have to use a string input for things like this because the "entity type is literally not known, or knowable, at compile time.", I tried this code:

 public static List<ListItem> GetDropdownList(string @enum)
 {     
     List<ListItem> listItems = Enum
       .GetValues(@enum.GetType())
       .Cast<@enum.GetType()>()
       .Select(
          x => new ListItem { Text = x.ToString(), Value = (int)x }).ToList();            
          return listItems;
        }

However, when I attempted to do so, on the second @enum.GetType()I got an error of:

Operator '<' cannot be applied to operands of type 'method group' and 'Type'

I've tried removing the .GetType() bit but then I get an error of

enum is a variable but is used like a type

I'm not too familar with Linq, so I'm likely missing something straightforward, thanks for any help!

Edit: ListItem Looks like this:

 public class ListItem
        {
            public int Value { get; set; }
            public string Text { get; set; }
        }
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
Bubinga
  • 613
  • 12
  • 29
  • casting is some **compiletime**-operation. So there´s no way to do it, when you provide the actual type at **runtime**. – MakePeaceGreatAgain Aug 24 '20 at 11:08
  • `.Cast<@enum.GetType()>()` this does not do what you think it does. Generics are a compile-time (meaning: JIT-compile-time) thing, not a runtime thing. The closest think you'll get to that is by using `Activator.CreateInstance`. – Dai Aug 24 '20 at 11:14
  • Ok, just to be clear I am providing the value at compile-time. – Bubinga Aug 24 '20 at 11:15
  • @Bubinga No you aren't, you're passing a `string` at runtime. – Dai Aug 24 '20 at 11:15
  • Then I guess I don't really know what runtime is. How do you know that I am? – Bubinga Aug 24 '20 at 11:16

2 Answers2

2

You can still use generics, but use the struct, Enum constraint added in C# 7.3:

Change your code to this:

public static List<ListItem> GetDropdownList<TEnum>()
    where TEnum : struct, Enum
{     
     return Enum.GetValues( typeof(TEnum) )
        .Cast<TEnum>()
        .Select( e => new ListItem() { Text = e.ToString(), Value = Convert.ToInt32( e ) } );
        .ToList();

    public static IReadOnlyList<ListItem> Items => _items;
}

Note that the above code's performance can be improved by moving it to a static generic class and storing the result in a static field, as this will cache the result (as generic types exist once for each type, so there's no need to perform this computation more than once):

public static class EnumListItems<TEnum>
    where TEnum : struct, Enum
{
    private static readonly IReadOnlyList<ListItem> _items = Enum.GetValues( typeof(TEnum) )
        .Cast<TEnum>()
        .Select( e => new ListItem() { Text = e.ToString(), Value = Convert.ToInt32( e ) } );
        .ToList();

    public static IReadOnlyList<ListItem> Items => _items;
}

Used like so:

IReadOnlyList<ListItem> items = EnumListItems<MyEnum>.Items;

If you need a mutable list, use .ToList():

List<ListItem> items = EnumListItems<MyEnum>.Items.ToList();
Dai
  • 141,631
  • 28
  • 261
  • 374
  • Thanks for such a thorough answer, ngl I don't fully understand everything though, but that is just because I'm rather new to programming. +1 – Bubinga Aug 24 '20 at 11:24
  • @Bubinga I accidentally pressed "Cut" on my code instead of "Copy" when I was expanding my answer. I've restored my original code now. Sorry for the confusion. – Dai Aug 24 '20 at 11:34
  • How would you change this code to get the display name of the enum values? The ```[Display(Name = "Example")]``` thingo? – Bubinga Aug 29 '20 at 06:24
  • @Bubinga Getting the value of a `DisplayAttribute` for enum members requires a lot of code, see here: https://stackoverflow.com/questions/13099834/how-to-get-the-display-name-attribute-of-an-enum-member-via-mvc-razor-code – Dai Aug 29 '20 at 06:31
1

entity type is literally not known, or knowable, at compile time

You appear to have to use Cast<T>() to do anything meaningful with Linq, because Enum.GetValues()'s return type was never updated to support generic collections for Linq. Also, @enum.GetType() will always return System.String.

The only thing you want to extract, are the enum members' integer and textual value, and you can extract those from any enum. So:

private static List<ListItem> GetListItems<TEnum>()
{
    var result = new List<ListItem>();
    
    foreach (object e in Enum.GetValues(typeof(TEnum)))
    {
        result.Add(new ListItem 
        { 
            Text = e.ToString(),
            Value = (int)e
        });
    }
               
    return result;
}

When using C# 7.3 and up, you should constrain TEnum to System.Enum. And if you want Linq instead of a loop, you can use Enum.GetValues(typeof(TEnum)).Cast<System.Enum>().

Now if you don't know TEnum at compile time, look at How to call generic method with a given Type object? and Type.GetType(enumName).

CodeCaster
  • 147,647
  • 23
  • 218
  • 272