Wilfried Woivré & .Net

Unity : Enregistrer des types génériques et les résoudre

MARS15

Bon une fois n’est pas coutume, cela fait vraiment longtemps que je n’ai pas publié un billet n’ayant pas de rapport avec la plateforme Windows Azure. Même si j’avoue que je viens d’utiliser cette astuce dans la future version de mon blog sur Azure, mais vous en serez plus très bientôt j’espère !

 

Alors ma problématique est la suivante, je souhaitais faire un cache générique pour mon application, et bien entendu, j’utilise Unity dans tous mes projets, et donc celui ci ne fait pas exception à la règle. J’ai donc cherché à résoudre mes dépendances selon le type dont j’ai besoin.

 

On va donc commencer par créer une interface générique comme celle-ci  :

  1. public interface MyGenericInterface<T>
  2. {
  3.     void DisplayType(T instance);
  4. }

 

On va ensuite l’implémenter dans une classe :

  1. public class MyClass : MyGenericInterface<String>
  2. {
  3.     public void DisplayType(String instance)
  4.     {
  5.         Console.WriteLine(instance.GetType().FullName + Environment.NewLine);
  6.     }
  7. }

 

Voyons maintenant comment l’enregistrer dans un conteneur Unity :

  1. private static void Configure(IUnityContainer container)
  2. {
  3.     container.RegisterType(typeof (MyGenericInterface<>), typeof(MyClass));
  4. }

Comme on peut le voir, on enregistre les types de façon assez classique, il nous faut juste ajout les chevrons afin de définir un paramètre générique.

Si par hasard, vous avez plusieurs paramètres, par exemple, il faudra faire comme ci-dessous afin de définir qu’il y a bien 3 paramètres génériques

  1. private static void Configure(IUnityContainer container)
  2. {
  3.     container.RegisterType(typeof (MyGenericInterface<,,>), typeof(MyClass));
  4. }

 

Et maintenant, voyons comment résoudre nos types, si on prend l’exemple suivant :

  1. UnityRoot.EnsureInitialized();
  2. UnityRoot.Container.Resolve<MyGenericInterface<String>>().DisplayType("Hello");
  3.  
  4. try
  5. {
  6.     UnityRoot.Container.Resolve<MyGenericInterface<Int32>>().DisplayType(42);
  7. }
  8. catch (Exception ex)
  9. {
  10.     Console.ForegroundColor = ConsoleColor.Red;
  11.     Console.WriteLine(ex);
  12.     Console.ForegroundColor = ConsoleColor.White;
  13. }

 

On va donc essayer de résoudre une classe implémentant notre interface avec le type String, et ensuite avec le type Int32. Dans notre cas, nous n’avons ajouté qu’une classe avec une interface avec le type String.

Si nous lançons notre application, nous allons donc avoir ceci  :

image

 

Nous avons bien la résolution de notre premier élément, par contre la deuxième ne marche pas comme prévu, puisque nous n’avons pas enregistrer de classe avec notre interface implémentant le type Int32.

Voilà, je ne vous donne pas le code source de la solution, puisque tout est là, il ne manque que les références à Unity, et pour cela NuGet est votre meilleur ami !

Remonter

C# et les générics

MARS21

Au cours de mes différents développements, j'ai souvent eu besoin d'utiliser des méthodes ou des classes génériques afin de gagner en lisibilité du code, et surtout au niveau du nombre de ligne de code.

Et sur ce sujet on me pose souvent deux questions :

  • Comment donner la valeur null à un type générique, puisque si le type en question n'est pas nullable, on aura une erreur à l'exécution.
  • Comment caster un objet en type T.

Alors déjà pour caster un objet en type T, on ne peut bien entendu ne pas faire : liste.add((T)elt) ;

Ce qu'il faut faire c'est changer le type de l'objet via cette méthode :

liste.Add((T)Convert.ChangeType(elt, typeof(T)));

Maintenant comment donner la valeur null à un objet de type générique, techniquement, on ne peut pas faire ceci, à cause de l'erreur ci-dessous.

alt

En effet, imaginons que T soit de type int, et bien il est « non-nullable ». On peut donc lui assigner une valeur par défaut à la place de le mettre toujours null.

T t = default(T);

En effet dans le cas, ou T est « non-nullable » comme avec un type int, la variable t prendra la valeur 0. Si le type est String, alors t vaudra null.

De plus, ce qui est très utile pour les développeurs lors de la conception de leur programme, on peut rajouter sur les déclarations des méthodes, ou des classes une condition à la généricité.

internal Boolean CheckElement<T>(ref T output) where T : BaseElement

Par exemple dans ce cas, on ne pourra se servir de la fonction CheckElement, si, et seulement si, T dérive de BaseElement (ici une classe abstraite du projet), donc tout autre type d'objets passé à la méthode empêche la compilation, ce qui peut s'avérer utilise si l'on veut par la suite utiliser des méthodes ou des attributs de la classe BaseElement.

Remonter