web-dev-qa-db-fra.com

Obtenez des valeurs d'énumération par réflexion à partir d'une énumération imbriquée dans une classe générique

J'ai besoin d'imprimer les valeurs d'énumération et leurs valeurs de sous-style correspondantes à partir de certains types que j'acquiers par réflexion. Cela fonctionne très bien la plupart du temps. Cependant, si l'énumération est déclarée dans un type générique, Enum.GetValues lève l'exception suivante:

[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()

Code complet pour la 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
    }
}

Ou testez-le ici

Est-ce le comportement souhaité et comment puis-je travailler dans les environs?

Construire un type serait une solution de contournement mais inacceptable pour moi, car cela deviendrait trop compliqué.

34
CSharpie

La construction d'un type serait une solution de contournement mais inacceptable pour moi, car cela deviendrait trop compliqué.

C'est le seul moyen d'obtenir des valeurs qui se comporteront normalement.

Vous pouvez accéder aux champs d'un type ouvert, et la chose bizarre est que vous pouvez obtenir des valeurs de cette façon pour les énumérations. Vous devez essayer d'éviter d'utiliser ces valeurs, mais vous pouvez les convertir en leur type sous-jacent.

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

Cependant, une solution meilleure consiste à utiliser 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()}");
   }
}

De cette façon, si le CLR est jamais corrigé pour empêcher la production de telles valeurs étranges, votre code ne se cassera pas.

44
Jon Skeet

C'est le comportement attendu. Les types génériques ouverts ne peuvent pas exister au moment de l'exécution, de même que tout ce qui y vit. La seule façon de le faire est tout d'abord de fermer le type parent avec n'importe quel type, puis de l'utiliser pour refléter l'énumération:

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

Foo est ce qu'on appelle un type ouvert (un type qui n'est pas entièrement défini car il contient un générique) Et le tableau de type ouvert n'est pas autorisé, vous pouvez le simuler en faisant

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

Et puisque GetValues ​​of Enum dépend de la création d'un tableau, il échoue. Vous pourriez plutôt faire

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

("objet" étant un type factice afin que vous ne travailliez pas avec un type ouvert) Ou faites ce que Jon Skeet a suggéré.

2
areller