Share via


Création d'interfaces génériques de type variant (C# et Visual Basic)

Vous pouvez déclarer des paramètres de type générique dans les interfaces comme covariant ou contravariant. La covariance permet aux méthodes d'interface d'avoir des types de retour plus dérivés que ceux définis par les paramètres de type générique. La contravariance permet aux méthodes d'interface d'avoir des types d'argument moins dérivés que ceux spécifiés par les paramètres génériques. Une interface générique ayant des paramètres de type générique covariant ou contravariant est appelée variante.

Notes

.NET Framework 4 apporte une prise en charge de la variance pour plusieurs interfaces génériques existantes.Pour obtenir la liste des variantes dans le .NET Framework, consultez Variance dans les interfaces génériques (C# et Visual Basic).

Déclaration d'interfaces génériques variantes

Vous pouvez déclarer des interfaces génériques variantes à l'aide des mots clés in et out pour les paramètres de type générique.

Important

Les paramètres ByRef en Visual Basic et les paramètres ref et out en C# ne peuvent pas être de type variant.Les types valeur ne prennent pas non plus en charge la variance.

Vous pouvez déclarer un covariant de paramètre de type générique à l'aide du mot clé out. Le type covariant doit satisfaire les conditions suivantes :

  • Le type est utilisé uniquement comme un type de retour de méthodes d'interface et non comme un type d'arguments de méthode. L'exemple suivant fournit une illustration dans laquelle le type R est déclaré covariant.

    Interface ICovariant(Of Out R)
        Function GetSomething() As R
        ' The following statement generates a compiler error. 
        ' Sub SetSomething(ByVal sampleArg As R) 
    End Interface
    
    interface ICovariant<out R>
    {
        R GetSomething();
        // The following statement generates a compiler error. 
        // void SetSometing(R sampleArg);
    
    }
    

    Il existe une exception à cette règle. Si vous avez un délégué générique contravariant comme paramètre de méthode, vous pouvez utiliser le type comme paramètre de type générique pour le délégué. L'exemple suivant du type R illustre ce principe. Pour plus d'informations, consultez Variance dans les délégués (C# et Visual Basic) et Utilisation de la variance pour les délégués génériques Func et Action (C# et Visual Basic).

    Interface ICovariant(Of Out R)
        Sub DoSomething(ByVal callback As Action(Of R))
    End Interface
    
    interface ICovariant<out R>
    {
        void DoSomething(Action<R> callback);
    }
    
  • Le type n'est pas utilisé comme contrainte générique pour les méthodes d'interface. C'est ce que montre le code suivant.

    Interface ICovariant(Of Out R)
        ' The following statement generates a compiler error 
        ' because you can use only contravariant or invariant types 
        ' in generic contstraints. 
        ' Sub DoSomething(Of T As R)() 
    End Interface
    
    interface ICovariant<out R>
    {
        // The following statement generates a compiler error 
        // because you can use only contravariant or invariant types 
        // in generic contstraints. 
        // void DoSomething<T>() where T : R;
    }
    

Vous pouvez déclarer un contravariant de paramètre de type générique à l'aide du mot clé in. Le type contravariant peut être utilisé uniquement comme type d'arguments de méthode et non comme type de retour de méthodes d'interface. Le type contravariant peut également être utilisé pour les contraintes génériques. Le code suivant indique comment déclarer une interface contravariante et utiliser une contrainte générique pour l'une de ses méthodes.

Interface IContravariant(Of In A)
    Sub SetSomething(ByVal sampleArg As A)
    Sub DoSomething(Of T As A)()
    ' The following statement generates a compiler error. 
    ' Function GetSomething() As A 
End Interface
interface IContravariant<in A>
{
    void SetSomething(A sampleArg);
    void DoSomething<T>() where T : A;
    // The following statement generates a compiler error. 
    // A GetSomething();            
}

Il est également possible de prendre en charge à la fois la covariance et la contravariance dans la même interface, mais pour des paramètres de type différent comme indiqué dans l'exemple de code suivant.

Interface IVariant(Of Out R, In A)
    Function GetSomething() As R
    Sub SetSomething(ByVal sampleArg As A)
    Function GetSetSomething(ByVal sampleArg As A) As R
End Interface
interface IVariant<out R, in A>
{
    R GetSomething();
    void SetSomething(A sampleArg);
    R GetSetSometings(A sampleArg);
}

En Visual Basic, vous ne pouvez pas déclarer d'événements dans des interfaces variantes sans spécifier le type délégué. De même, une interface variante ne peut pas avoir de classes, d'enums ou de structures imbriqués, mais elle peut inclure des interfaces imbriquées. C'est ce que montre le code suivant.

Interface ICovariant(Of Out R)
    ' The following statement generates a compiler error. 
    ' Event SampleEvent() 
    ' The following statement specifies the delegate type and  
    ' does not generate an error. 
    Event AnotherEvent As EventHandler

    ' The following statements generate compiler errors, 
    ' because a variant interface cannot have 
    ' nested enums, classes, or structures. 

    'Enum SampleEnum : test : End Enum 
    'Class SampleClass : End Class 
    'Structure SampleStructure : Dim value As Integer : End Structure 

    ' Variant interfaces can have nested interfaces. 
    Interface INested : End Interface 
End Interface

Implémentation d'interfaces génériques variantes

Vous implémentez des interfaces génériques variantes dans les classes à l'aide de la même syntaxe utilisée pour les interfaces invariantes. L'exemple de code suivant indique comment implémenter une interface covariante dans une classe générique.

Interface ICovariant(Of Out R)
    Function GetSomething() As R
End Interface 

Class SampleImplementation(Of R)
    Implements ICovariant(Of R)
    Public Function GetSomething() As R _
    Implements ICovariant(Of R).GetSomething
        ' Some code. 
    End Function 
End Class
interface ICovariant<out R>
{
    R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
    public R GetSomething()
    {
        // Some code. 
        return default(R);
    }
}

Les classes qui implémentent des interfaces variantes sont invariantes. Par exemple, prenons le code suivant.

' The interface is covariant. 
Dim ibutton As ICovariant(Of Button) =
    New SampleImplementation(Of Button)
Dim iobj As ICovariant(Of Object) = ibutton

' The class is invariant. 
Dim button As SampleImplementation(Of Button) =
    New SampleImplementation(Of Button)
' The following statement generates a compiler error 
' because classes are invariant. 
' Dim obj As SampleImplementation(Of Object) = button
// The interface is covariant.
ICovariant<Button> ibutton = new SampleImplementation<Button>();
ICovariant<Object> iobj = ibutton;

// The class is invariant.
SampleImplementation<Button> button = new SampleImplementation<Button>();
// The following statement generates a compiler error 
// because classes are invariant. 
// SampleImplementation<Object> obj = button;

Extension d'interfaces génériques variantes

Lorsque vous étendez une interface générique variante, vous devez utiliser les mots clés in et out pour spécifier explicitement si l'interface dérivée prend en charge la variance. Le compilateur ne déduit pas la variance de l'interface étendue. Observez par exemple les interfaces suivantes.

Interface ICovariant(Of Out T)
End Interface 

Interface IInvariant(Of T)
    Inherits ICovariant(Of T)
End Interface 

Interface IExtCovariant(Of Out T)
    Inherits ICovariant(Of T)
End Interface
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }

Dans l'interface IInvariant<T> (Invariant(Of T) en Visual Basic), le paramètre de type générique T est invariant, alors que dans IExtCovariant<out T> (IExtCovariant (Of Out T) en Visual Basic), le paramètre de type est covariant, bien que les deux interfaces étendent la même interface. La même règle est appliquée aux paramètres de type générique contravariant.

Vous pouvez créer une interface qui étend à la fois l'interface où le paramètre de type générique T est covariant et l'interface où le type est contravariant si dans l'interface étendue, le paramètre de type générique T est invariant. L'exemple de code suivant l'illustre.

Interface ICovariant(Of Out T)
End Interface 

Interface IContravariant(Of In T)
End Interface 

Interface IInvariant(Of T)
    Inherits ICovariant(Of T), IContravariant(Of T)
End Interface
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }

Toutefois, si un paramètre de type générique T est déclaré covariant dans une interface, vous ne pouvez pas le déclarer contravariant dans l'interface étendue, ou vice versa. L'exemple de code suivant l'illustre.

Interface ICovariant(Of Out T)
End Interface 

' The following statements generate a compiler error. 
' Interface ICoContraVariant(Of In T) 
'     Inherits ICovariant(Of T) 
' End Interface
interface ICovariant<out T> { }
// The following statement generates a compiler error. 
// interface ICoContraVariant<in T> : ICovariant<T> { }

Éviter l'ambiguïté

Lorsque vous implémentez des interfaces génériques variantes, la variance peut quelquefois mener à une certaine ambiguïté. Cela doit être évité.

Par exemple, si vous implémentez explicitement la même interface générique variante avec des paramètres de type générique différents dans une classe, cela peut créer une ambiguïté. Le compilateur ne génère pas d'erreur dans ce cas, mais il n'est pas spécifié quelle implémentation d'interface sera choisie à l'exécution. Cela pourrait engendrer des bogues subtils dans votre code. Prenons l'exemple de code suivant.

Notes

Avec Option Strict Off, Visual Basic génère un avertissement du compilateur lorsque l'implémentation d'interface est ambiguë.Avec Option Strict On, Visual Basic génère une erreur de compilateur.

' Simple class hierarchy. 
Class Animal
End Class 

Class Cat
    Inherits Animal
End Class 

Class Dog
    Inherits Animal
End Class 

' This class introduces ambiguity 
' because IEnumerable(Of Out T) is covariant. 
Class Pets
    Implements IEnumerable(Of Cat), IEnumerable(Of Dog)

    Public Function GetEnumerator() As IEnumerator(Of Cat) _
        Implements IEnumerable(Of Cat).GetEnumerator
        Console.WriteLine("Cat")
        ' Some code. 
    End Function 

    Public Function GetEnumerator1() As IEnumerator(Of Dog) _
        Implements IEnumerable(Of Dog).GetEnumerator
        Console.WriteLine("Dog")
        ' Some code. 
    End Function 

    Public Function GetEnumerator2() As IEnumerator _
        Implements IEnumerable.GetEnumerator
        ' Some code. 
    End Function 
End Class 

Sub Main()
    Dim pets As IEnumerable(Of Animal) = New Pets()
    pets.GetEnumerator()
End Sub
// Simple class hierarchy. 
class Animal { }
class Cat : Animal { }
class Dog : Animal { }

// This class introduces ambiguity 
// because IEnumerable<out T> is covariant. 
class Pets : IEnumerable<Cat>, IEnumerable<Dog>
{
    IEnumerator<Cat> IEnumerable<Cat>.GetEnumerator()
    {
        Console.WriteLine("Cat");
        // Some code. 
        return null;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        // Some code. 
        return null;
    }

    IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
    {
        Console.WriteLine("Dog");
        // Some code. 
        return null;
    }
}
class Program
{
    public static void Test()
    {
        IEnumerable<Animal> pets = new Pets();
        pets.GetEnumerator();
    }
}

Dans cet exemple, il n'est pas spécifié de quelle façon la méthode pets.GetEnumerator choisit entre Cat et Dog. Cela pourrait provoquer des problèmes dans votre code.

Voir aussi

Référence

Utilisation de la variance pour les délégués génériques Func et Action (C# et Visual Basic)

Concepts

Variance dans les interfaces génériques (C# et Visual Basic)