Fév 08

Il arrive régulièrement de devoir écrire une méthode générique qui accepte n’importe quel type Enum comme paramètre.
Voici un exemple de cas où cela peut s’avérer nécessaire.

Il s’agit rendre générique une méthode LogError, dont le second paramètre est un type Enum, qui permet de spécifier la catégorie :

public enum LogCategory
{
	Default,
	UserInterface,
	BusinessLogic,
	DataAccess,
	Security,
	Errors
}

Cette classe LogCategory est définie dans une librairie de classes (par exemple DataAccess). On a également une application web (par exemple Web1) qui appelle cette méthode LogError pour la loguer mais le problème c’est que cette application web a elle-même défini sa propre classe LogCategory.
Donc lorsque l’énumération LogCategory de Web1 est passée en paramètre de la méthode LogError, cela génère une erreur de compilation Cannot convert Web1.LogCategory into DataAccess.LogCategory car il s’agit bien entendu de deux classes différentes dans deux projets différents.

Il est donc nécessaire de créer une méthode LogError générique qui accepterait n’importe quel énumération comme paramètre.

Voici une première version de cette méthode :

public static void LogError<T>(string msg, T logCat)
{
	//Here T is generic type.
	EntLib.Logger.Write(msg, logCat.ToString(), 3, 1000, TraceEventType.Error);
	//EntLib = Microsoft.Practices.EnterpriseLibrary.Logging;
}

Mais cette méthode est devenue trop générique car T peut être de n’importe quel type. Il est donc nécessaire d’ajouter une contrainte sur le type de T afin de le limiter à des énumérations, grâce à l’utilisation de la clause where.

public static void Error<T>(string msg, T logCat) where T: Enum
{
	//Here T is generic type.
	EntLib.Logger.Write(msg, logCat.ToString(), 3, 1000, TraceEventType.Error);
	//EntLib = Microsoft.Practices.EnterpriseLibrary.Logging;
}

Mais cela ne fonctionne pas, car cela génère une erreur de compilation Type Expected. Apparemment il n’y a pas de manière simple de spécifier une contrainte sur une énumération. Étant donné que le type Enum implémente l’interface IConvertible, une solution de contournement pourrait être de créer une contrainte sur cette interface :

public static void Error<T>(string msg, T logCat) where T: struct, IConvertible
{
	//Here T is generic type.
	EntLib.Logger.Write(msg, logCat.ToString(), 3, 1000, TraceEventType.Error);
	//EntLib = Microsoft.Practices.EnterpriseLibrary.Logging;
}

Cela limite un peu le type de paramètre, l’instruction LogError<SomeXClass>("message", objOfSomeXClass) génère une erreur de compilation :

The type ‘SomeXClass’ must be a non-nullable value type in order to use it as parameter ‘T’ in the generic type or method ‘LogError<T>(string, T)’

C’est mieux , il est toujours possible de passer un objet d’un type implémentant l’interface IConvertible mais c’est plutôt rare.
Il est également possible de vérifier dans le code le type de T afin de valider qu’il est bien de type Enum et restreindre encore plus.

Version finale :

public static void Error<T>(string msg, T logCat)  where T : struct, IConvertible
{
	if (!typeof(T).IsEnum)
	{
		string errorMessage = string.Format("{0} must be an enumerated type", typeof(T).ToString());
		throw new ArgumentException(errorMessage, typeof(T).ToString());
	}
	EntLib.Logger.Write(msg, logCat.ToString(), 3, 1000, TraceEventType.Error);
	//EntLib = Microsoft.Practices.EnterpriseLibrary.Logging;
}

Source : Shailesh sur Broken Code.

Leave a Reply