Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get enum values via reflection from nested enum in generic class

I need to print out enum values and their corresponding underyling values from certain types i accquire through reflection. This works fine most of the time. However if the enum is declared within a generic type, Enum.GetValues throws the following exception:

[System.NotSupportedException: Cannot create arrays of open type. ]  
at System.Array.InternalCreate(Void* elementType, Int32 rank, Int32* pLengths, Int32* pLowerBounds)    
at System.Array.CreateInstance(Type elementType, Int32 length)
at System.Array.UnsafeCreateInstance(Type elementType, Int32 length)   
at System.RuntimeType.GetEnumValues()

Full code for reproduction :

using System;

public class Program
{
    public static void Main()
    {
       var enumType= typeof(Foo<>.Bar);
       var underlyingType = Enum.GetUnderlyingType(enumType);
       Console.WriteLine(enumType.IsEnum);

       foreach(var value in Enum.GetValues(enumType))
       {
           Console.WriteLine("{0} = {1}", value, Convert.ChangeType(value, underlyingType));
       }
    }

}

public class Foo<T>
{
    public enum Bar
    {
        A = 1,
        B = 2
    }
}

Or test it here

Is this desired behaviour and how do I work arround?

Constructing a type would be a workarround but inacceptable for me, since it would get too complicated.

like image 334
CSharpie Avatar asked Apr 26 '17 10:04

CSharpie


People also ask

Does enum Getvalues use reflection?

Roughly, yes. There are two kind of reflection code, the general kind that goes through RuntimeType and the specific kind that uses dedicated CLR helper functions.

Can you use spaces in the name of items in enumerations?

Sometimes you might need to loop on the elements of a particular enum and print its element names, however as you know by the language constraints, the enum element names must follow the naming convention, you cannot include spaces or other special characters.

Do enums have to be sequential?

It is not necessary to assign sequential values to Enum members. They can have any values. In the above example, we declared an enum PrintMedia .


3 Answers

Construction a type would be a workaround but inacceptable for me, since it would get too complicated.

That's the only way of getting values that will behave normally.

You can get at the fields of an open type, and the bizarre thing is that you can get values that way for enums. You should try to avoid using those values, but you can convert them to their underlying type.

public static void Main()
{
   var enumType = typeof(Foo<>.Bar);
   var underlyingType = Enum.GetUnderlyingType(enumType);

   foreach(var field in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
   {
       var value = field.GetValue(null);
       var underlyingValue = Convert.ChangeType(value, underlyingType);
       Console.WriteLine($"{field.Name} = {underlyingValue}");
   }
}

However, a better solution is to use field.GetRawConstantValue():

public static void Main()
{
   var enumType = typeof(Foo<>.Bar);

   foreach(var field in enumType.GetFields(BindingFlags.Public | BindingFlags.Static))
   {
       Console.WriteLine($"{field.Name} = {field.GetRawConstantValue()}");
   }
}

That way if the CLR is ever fixed to prevent such weird values from ever being produced, your code won't break.

like image 128
Jon Skeet Avatar answered Nov 10 '22 10:11

Jon Skeet


This is the expected behavior. Open generic types cannot exist at runtime, so neither can anything that lives inside them. The only way you can do this is first closing the parent type with any type, and then using that to reflect the enum:

 var enumType = typeof(Foo<object>.Bar);
like image 3
Akos Nagy Avatar answered Nov 10 '22 10:11

Akos Nagy


Foo is what's called an open type (A type which is not fully defined because it has a generic in it) And array of open type is not allowed, you could simulate it by doing

Array.CreateInstance(typeof(Foo<>), 2)

And since GetValues of Enum is dependent upon creating an array, it fails. You could instead do

var enumType = typeof(Foo<object>.Bar);

("object" being a dummy type so that you won't work with an open type) Or do what Jon Skeet suggested.

like image 2
areller Avatar answered Nov 10 '22 10:11

areller