Méthode générique avec contrainte T sur Enum : sous le capot C#

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Méthode générique avec contrainte T sur Enum : comment ça fonctionne sous le capot

\n\n

Imaginez : vous codez en C# et souhaitez créer une méthode universelle qui accepte n'importe quelle énumération (Enum). Simple, non ? Mais si vous avez déjà essayé d'écrire public void DoSomething<T>() where T : Enum, vous savez que le compilateur génère une erreur. Avant C# 7.3, c'était impossible. Après, c'est devenu possible, mais avec des nuances. Analysons ensemble comment cela fonctionne sous le capot et pourquoi c'est l'un des sujets les plus populaires sur Stack Overflow.

\n\n

Cet article n'est pas une simple redite de la documentation. Nous creuserons plus profondément : du code IL aux recommandations de ReSharper. C'est parti !

\n\n

Pourquoi ne pouvait-on pas simplement écrire where T : Enum ?

\n\n

Pendant longtemps, C# interdisait d'utiliser Enum ou Delegate comme contrainte pour les paramètres génériques. Pourquoi ? Parce que le compilateur devait garantir que le type T est un type valeur (struct) ou un type référence (class). Or, Enum est un type structurel spécial héritant de System.Enum. Mais dans le système de types C#, il ne s'inscrivait pas dans les règles simples :

\n\n
    \n
  • Enum est un struct, mais pas n'importe quel struct.
  • \n
  • Enum hérite de ValueType, mais le compilateur ne permettait pas d'utiliser where T : System.Enum.
  • \n
  • Essayer d'écrire where T : struct, Enum ne fonctionnait pas non plus.
  • \n
\n\n

Les développeurs utilisaient des solutions de contournement : vérification à l'exécution via typeof(T).IsEnum ou utilisation de la réflexion. Mais cela nuisait aux performances et à la sécurité des types.

\n\n

C# 7.3 : La solution tant attendue

\n\n

À partir de C# 7.3 et .NET Core 2.0 / .NET Framework 4.8, il est devenu possible d'écrire :

\n\n
public static string GetEnumName<T>(T value) where T : Enum\n{\n    return Enum.GetName(typeof(T), value);\n}\n
\n\n

Désormais, le compilateur comprend : T est une énumération. Mais comment fait-il exactement ? Regardons sous le capot.

\n\n

Que se passe-t-il dans le code IL ?

\n\n

Lorsque vous écrivez where T : Enum, le compilateur génère un appel contraint spécial pour les méthodes comme Enum.HasFlag, Enum.GetName, etc. Au lieu d'utiliser le boxing ou la réflexion, le compilateur JIT peut substituer un appel direct à la méthode statique, connaissant le type spécifique T.

\n\n

Exemple de code IL pour la méthode ci-dessus :

\n\n
.method public hidebysig static \n    string GetEnumName<([mscorlib]System.Enum) T>(!!0 value) cil managed\n{\n    // appel à Enum.GetName avec préfixe contraint\n    call string [mscorlib]System.Enum::GetName(class [mscorlib]System.Type, object)\n}\n
\n\n

Remarquez l'attribut ([mscorlib]System.Enum) T. C'est la nouvelle contrainte. Le JIT le voit et sait que T est une enum, ce qui permet d'éviter le boxing lors du passage de value à GetName.

\n\n

Comment utiliser une méthode générique avec Enum en pratique ?

\n\n

Créons un exemple utile : une méthode pour analyser une chaîne en n'importe quelle énumération avec prise en charge de l'ignorance de la casse.

\n\n
public static T ParseEnum<T>(string value, bool ignoreCase = true) where T : Enum\n{\n    if (string.IsNullOrWhiteSpace(value))\n        throw new ArgumentException("La valeur ne peut pas être nulle ou vide.", nameof(value));\n\n    if (!Enum.TryParse(typeof(T), value, ignoreCase, out object? result))\n        throw new ArgumentException($"'{value}' n'est pas une valeur valide pour l'enum {typeof(T).Name}.");\n\n    return (T)result;\n}\n
\n\n

Important : Même si T est une Enum, le cast (T)result nécessite toujours un cast explicite. Cela est dû au fait qu'à l'exécution, le type object ne connaît pas le paramètre générique. Mais le compilateur garantit que T est une enum, donc le cast est sûr.

\n\n

Variante plus élégante avec Enum.TryParse<T>

\n\n

À partir de .NET 5, une surcharge est apparue

Blogs

Book Recommendations