C# : Interface générique covariante, contrevariante et invariante

Avec le langage de programmation C#, nous avons la possibilité d’écrire des classes et des interfaces génériques. Voici un exemple simple.

public interface IGreeter<T>
{
    void SayHello();
}
public class Greeter<T> : IGreeter<T>
{
    public void SayHello()
    {
        Console.WriteLine($"Hello! I'm a {typeof(T)}");
    }
}

Si j’utilise cette interface avec une classe Person, le résultat « Hello! I’m a Person » sera affiché dans la console.

public static void Main(string[] args)
{
    IGreeter<Person> greeterPerson = new Greeter<Person>();
    greeterPerson.SayHello();
}

record Person (string FirstName, string LastName);

Je peux également créer une class Developer, qui hérite de Person, et l’utiliser avec ma classe Greeter.

public static void Main(string[] args)
{
    IGreeter<Person> greeterPerson = new Greeter<Person>();
    IGreeter<Developer> greeterDeveloper = new Greeter<Developer>();
    
    greeterPerson.SayHello(); // Output Hello, I'm a Person
    greeterDeveloper.SayHello(); // Output Hello, I'm a Developer
}

record Person (string FirstName, string LastName);
record Developer (string Stack, string FirstName, string LastName) : Person (FirstName, LastName);

Invariance

Dans la ligne 3 l’exemple ci-dessus, la compilation fonctionne car le type passé par IGreeter (Person) est le même que celui passé dans la classe Greeter. Notre interface générique est donc invariante. En effet, si l’on modifie la ligne 3 en changeant le type utilisé par la classe Greeter en Developer, le code ne compilera plus, malgré le faite que Developer hérite de Person.

IGreeter<Person> greeterPerson = new Greeter<Developer>(); // Not working

Covariance

La covariance est le principe d’utiliser un type dérivé de votre type spécifié. Il est possible de préciser à notre interface d’autoriser les types covariants de T, à l’aide du mot-clé out.

public interface IGreeter<out T>
{
    void SayHello();
}

public static void Main(string[] args)
{
    IGreeter<Person> greeterPerson = new Greeter<Developer>();

    greeterPerson.SayHello(); // Output Hello, I'm a Developer
}

Contrevariance

IGreeter<Developer> greeterDeveloper = new Greeter<Person>();

La contrevariance est l’inverse de la covariance. Son principe est d’utiliser un type plus générique que le type spécifié. Pour préciser à notre interface d’autoriser les types contrevariants, il suffit de remplacer le mot-clé out par in.

public interface IGreeter<in T>
{
    void SayHello();
}

public static void Main(string[] args)
{
    IGreeter<Developer> greeterDeveloper = new Greeter<Person>();

    greeterDeveloper.SayHello(); // Output Hello, I'm a Person
}

Conclusion

L’utilisation des mots-clés in et out préfixé de votre type générique vous permet de rendre votre interface plus fexible en acceptant les types variants ou contrevariants de votre type utilisé.

You may also like...

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *