Astuces DotNet (Sébastien Courtois)

10/05/2010

[Nouveautés C# .NET 4] Code Contracts : Mettre ses contrats sur des interfaces

Filed under: .NET, C# 4, DevLabs, Hors Catégorie, Intermediaire — Étiquettes : , , , , , — sebastiencourtois @ 15:30

Ce post fait suite à une introduction sur les Code Contracts publié précedement.

Les codes contracts permettent de spécifier et contrôler des règles au sein de votre code. Dans l’optique de la création d’API, il est interessant d’utiliser les Code Contract au sein de classe mais il est encore plus judicieux de les utiliser sur des interfaces (notamment pour les tests unitaires / IoC …). Nous allons donc voir comment réaliser des “code contracts” sur des interfaces.

  • Pour cela mettons nous en situation …

public interfaceIPerson
{
    void SetAge(int age);
}

public interfaceICustomer : IPerson
{
    void SetId(int Id);
}

public classPerson: IPerson
{
    protected int Age { get; set; }

    public void SetAge(int age)
    {
        this.Age = age;
    }
}

public classCustomer: Person,ICustomer
{
    protected int Id { get; set; }
    public void SetId(int id)
    {
        this.Id = id;
    }
}

Nous créons deux interfaces IPerson pouvant modifier l’age d’une personne et ICustomer dérivant de IPerson (donc on pourra modifier son age aussi) où l’on pourra modifier son Identifiant client. Ces deux interfaces ont été implémenté dans deux classes Person et Customer. Si on prend le scénario de la création d’une API, le créateur de l’API aura défini et manipulera les interfaces et le développeur utilisant l’API, aura implémenté les deux classes.

  • Comment le créateur de l’API peut définir des spécifications sur les paramètres de ses interfaces ?

Le but pour le créateur de l’API est de fournir un modèle de développement. Il peut ainsi indiquer les méthodes et les types à utiliser mais ne peut pas avoir plus de précision quand au contenu des paramètres. On a vu, qu’avec Code contract, il peut définir ces informations au sein des méthodes. Cela reviendrait à créer une classe abstrait (pas toujours pertinent surtout dans un langage où il n’y a pas d’héritage multiple comme le C#).

Pour définir un code contract sur une interface, quatre étapes sont nécessaires :

  1. Créer une classe contrat héritant el ‘interface sur laquelle on souhaite faire le contrat.
  2. Implémenter les méthodes de l’interface dans la classe de contrat en fournissant les code contracts.
  3. Décorer l’interface avec l’attribut ContractClass en fournissant le type de la classe de contract
  4. Décorer la classe avec l’attribut ContractClassFor en fournissant le nom des interface qui sont régit par le contrat
[ContractClass(typeof(PersonContracts))]
public interface IPerson
{
    void SetAge(int age);
}

[ContractClassFor(typeof(IPerson))]
public class PersonContracts : IPerson
{
    public void SetAge(int age)
    {
        Contract.Requires(age > 0 && age < 120, "L'age doit être compris entre 1 et 120 ans.");
    }
}

Le tour est joué. Ainsi pour le code exemple devient :

[ContractClass(typeof(PersonContracts))]
public interface IPerson
{
    void SetAge(int age);
}

[ContractClassFor(typeof(IPerson))]
public class PersonContracts : IPerson
{
    public void SetAge(int age)
    {
        Contract.Requires(age > 0 && age < 120, "L'age doit être compris entre 1 et 120 ans.");
    }
}

[ContractClass(typeof(CustomerContracts))]
public interface ICustomer : IPerson
{
    void SetId(int Id);
}

[ContractClassFor(typeof(ICustomer))]
public class CustomerContracts : PersonContracts,ICustomer
{
    public void SetId(int Id)
    {
        Contract.Requires(Id > 0, "L'identifiant doit être supérieur à 0.");
    }
}

Le code ci dessus est fait par le créateur de l’API.

public class Person : IPerson
   {
       protected int Age { get; set; }

       public void SetAge(int age)
       {
           this.Age = age;
       }
   }

   public class Customer : Person,ICustomer
   {
       protected int Id { get; set; }
       public void SetId(int id)
       {
           this.Id = id;
       }
   }

Le code ci dessus est fait par l’utilisateur de l’API. Il n’a pas à se préoccuper des contrats définis dans l’API et peut définir ses propres contracts.

14/04/2010

[Nouveautés C# .NET 4] Introduction à la Covariance / Contravariance

Filed under: .NET, Débutant, Intermediaire — Étiquettes : , , , , , — sebastiencourtois @ 14:44

Pour la sortie de .NET 4, je vais faire quelques articles sur les nouveautés du langages (en attendant de pouvoir jouer avec SL4). Aujourd’hui, nous allons voir la notion de Covariance / Contravariance.

EDIT (08/05/2010) : La Covariance/Contravariance n’est pas disponible pour Silverlight 4

  • Définitions

La covariance et la contravariance dixit Wikipedia :

Within the type system of a programming language, a typing rule or a type conversion operator is:

  • covariant if it preserves the ordering, ≤, of types, which orders types from more specific to more generic;
  • contravariant if it reverses this ordering, which orders types from more generic to more specific;
  • invariant if neither of these apply.

Bon, je suis d’accord qu’avec cette définition, on est pas plus avancé. On voit que cela parle de conversion de type et de généricité mais ça reste très abstrait.

Nous allons donc voir des exemples puis revenir sur ces définitions.

  • Exemples sur la covariance 

Nous prendrons une architecture simple pour ces exemples :

public abstract class Person { }
public class Employe : Person { }
public class Customer : Person { }
public class Manager : Employe { }

Nous allons maintenant jouer avec des tableaux de ces objets :

Person[] MaSociete = new Manager[10];  // .NET 3.5 / 4 : OK 
Employe[] MaSociete2 = new Manager[10]; // .NET 3.5 / 4 : OK 

Rien de transcendant là dessus. On a un tableau de Manager. Un manager étant une personne (ça dépend dans quel société …:)), il n’y a pas de problème à mettre les données d’un tableau de manager dans un tableau de personnes. De même pour mettre nos données manager dans le tableau d’employés (Manager étant un employé).

Faison la même chose avec des listes

Manager[] MaSociete3 = new Manager[10];
IEnumerable<Manager> im = MaSociete3.Where(p => p.ToString() == "Manager"); // .NET 3.5 / 4 : OK 
IEnumerable<Manager> im2 = new List<Manager>(); // .NET 3.5 / 4 : OK 

Le requête LINQ renvoie un IEnumerable<Manager> que je peux donc mettre dans im. De même pour une liste de manager car List<T> implémente IEnumerable<T>.

IEnumerable<Person> ip = im;  // .NET 3.5 : Erreur de compilation  / .NET 4 : OK

Sur cette ligne, on obtient une erreur de compilation en .NET 3.5 (Cannot Convert IEnumerable<Manager> to IEnumerable<Person>). Pourtant cela devrait marcher comme un tableau (un manager est une personne donc un tableau de personne peut contenir des données venant d’un tableau de manager). Cela n’est pas possible car, en .NET 3.5, IEnumerable<T> n’est pas covariant (à l’inverse de Array qui lui est covariant). C’est pour corriger ce manque que Microsoft a autorisé la covariance des interfaces et des délégués en .NET 4.

En .NET 4, je peux ainsi écrire :

IEnumerable<Person> ipl = new List<Manager>();  // .NET 3.5 : Erreur de compilation  / .NET 4 : OK
Func<string> funcString = () => { return "Bonjour le monde"; };
Func<object> funcObject = funcString;         // .NET 3.5 : Erreur de compilation  / .NET 4 : OK

Pour résumer, on peut dire que la covariance permet de caster un type générique A<T> dans un type générique A<K> si T hérite directement ou indirectement de K et si le type T est un paramètre de sortie (nous reviendrons sur cette notion plus loin).

Remarque :  La covariance (ainsi que la contravariance) fonctionne uniquement avec des interfaces et des délégués. Les arguments génériques doivent être des types références. Il n’est pas possible de faire les lignes suivantes :

List<Person> lo = new List<Manager>();         // .NET 3.5 / 4 : Erreur de compilation 
IEnumerable<ValueType> lo = new List<int>();    // .NET 3.5 / 4 : Erreur de compilation 

  • Exemples sur la contravariance 

Dans certains cas, on aimerait que l’inverse soit possible.

  Action<object> actObject = (object o) => { };
  Action<string> actString = actObject;          // .NET 3.5 : Erreur de compilation  / .NET 4 : OK

Dans cet exemple, je souhaite pouvoir caster mon délégués avec une valeur de retour de type string ou un de ses parents. En effet,lorsque je récupère une donnée, je peux la caster dans une variable d’un type dont elle hérite. C’est le principe de la contravariance introduite dans .NET 4.

La contravariance est la possiblité de caster un type générique A<T> dans un type générique A<K> si T est parent direct ou indirect de K et si T est un paramètre d’entrée (nous reviendrons sur cette notion plus loin).

  • Les interfaces / Délégués déja covariant et contravariant

Voici la liste des interfaces/délégués ayant été mis à jour au sein du framework .net 4 pour tenir compte de cette nouvelle possibilité.

 

  • Exemple pratique : La création d’une interface covariant et contravariant

Prenons l’exemple d’une classe réalisant de la transformations de données.

public interface IDataTransformer<T, K>
{
    K Transform(T data);
}

public class DataTransformer<T, K> : IDataTransformer<T, K>
{
    public K Transform(T data)
    {
        return default(K);
    }
}

Nous avons donc une interface IDataTransformer comprenant deux paramètres génériques T et K et une méthode qui va transformer une variable de type T en type K. Nous allons créer un type implémentant cette interface puis l’utiliser.

IDataTransformer<Manager, string> dataTransformer = new DataTransformer<Manager, string>(); // .NET 3.5 / 4 : OK
IDataTransformer<Person, string> dataTransformer2 = new DataTransformer<Manager, string>(); // .NET 3.5 / 4 : Erreur de compilation 
IDataTransformer<Manager, object> dataTransformer3 = new DataTransformer<Manager, string>(); // .NET 3.5 / 4 : Erreur de compilation 

Si le premier cas fonctionne logiquement, les deux cas suivants sont  moins logique. Dans le cas de dataTransfomer2, si on crée un DataTransformer prenant un Manager en entrée, on devrait aussi pouvoir prendre une personne (rappelons le, les managers sont des personnes).Pourtant le compilateur n’est pas de cette avis car il n’arrive pas à convertir DataTransformer<Manager,string> en IDataTransformer<Person,string>. De même pour dataTransformer3. Si on récupère en sortie une chaine de caractère, on pourrait très bien récupérer un object car string dérive de object.

Voila les limites de .NET 3.5 et voyons comment faire pour corriger cela en  .NET 4. Pour cela nous allons créer une nouvelle interface et une nouvelle classe Variant.

public interface IDataTransformerVariant<in T,out K>
{
    K Transform(T data);
}

  
public class DataTransformerVariant<T, K> : IDataTransformerVariant<T, K>
{
    public K Transform(T data)
    {
        return default(K);
    }
}

La différence se situe uniquement dans la déclaration de l’interface IDataTransformerVariant. On a rajouté un mot clé in devant le type d’entrée (T) et un out devant le type de sortie (K). En procédant ainsi, on active la covariance et la contravariance et on oblige le paramètre générique T a toujours être en paramètres de méthodes et le paramètre générique K a toujours être en valeur retour d’une méthode. Ne pas suivre cette règle entraine l’erreur de compilation suivante :

CoVarImg1

  • Conclusion

Pour conclure, je reprendrais les phrases importantes de cet article :

la covariance permet de caster un type générique A<T> dans un type générique A<K> si T hérite directement ou indirectement de K et si le type T est un paramètre de sortie.

La contravariance est la possiblité de caster un type générique A<T> dans un type générique A<K> si T est parent direct ou indirect de K et si T est un paramètre d’entrée.

Le fait que un paramètre générique soit d’entrée ou de sortie dépend de son placement dans les méthodes interfaces ou des délégués.

A titre d’information, ce concept est disponible depuis .NET 2.0 en IL (comme l’à montré Simon Ferquel dans son blog) mais aucun mot clé n’étant fourni en .NET, on ne pouvait pas l’utliser en C#.

Merci à Alexis Pera (MSP) pour la relecture et correction de cet article.

Propulsé par WordPress.com.