Condividi tramite


Creazione di interfacce generiche variant (C# e Visual Basic)

È possibile dichiarare parametri di tipo generico nelle interfacce come covariante o controvariante. La covarianza consente ai metodi di interfaccia di disporre di tipi restituiti più derivati di quelli definiti dai parametri di tipo generico. La controvarianza consente ai metodi di interfaccia di disporre di tipi di argomento meno derivati di quelli specificati dai parametri generici. Un'interfaccia generica che dispone di parametri di tipo generico covarianti o controvarianti è detta variant.

Nota

In .NET Framework 4 viene introdotto il supporto della varianza per diverse interfacce generiche esistenti. Per l'elenco delle interfacce variant in .NET Framework, vedere Varianza nelle interfacce generiche (C# e Visual Basic).

Dichiarazione di interfacce generiche variant

È possibile dichiarare le interfacce generiche variant utilizzando le parole chiave in e out per i parametri di tipo generico.

Nota importanteImportante

I parametri ByRef in Visual Basic e i parametri ref e out in C# non possono essere variant. I tipi di valore non supportano inoltre la varianza.

È possibile dichiarare una covariante del parametro di tipo generico utilizzando la parola chiave out. Il tipo di covariante deve soddisfare le condizioni seguenti:

  • Il tipo viene utilizzato solo come tipo restituito di metodi di interfaccia e non viene utilizzato come tipo di argomenti del metodo. Tale situazione è illustrata nell'esempio seguente, nel quale il tipo R viene dichiarato covariante.

    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);
    
    }
    

    Esiste un'eccezione a questa regola. Se si dispone di un delegato generico controvariante come parametro del metodo, è possibile utilizzare il tipo come parametro di tipo generico per il delegato. Tale situazione è illustrata dal tipo R nell'esempio seguente. Per ulteriori informazioni, vedere Varianza nei delegati (C# e Visual Basic) e Utilizzo della varianza per i delegati generici Func e Action (C# e 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);
    }
    
  • Il tipo non viene utilizzato come vincolo generico per i metodi di interfaccia. Tale situazione è illustrata nel codice seguente.

    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;
    }
    

È possibile dichiarare una controvariante del parametro di tipo generico utilizzando la parola chiave in. Il tipo di controvariante può essere utilizzato solo come tipo di argomenti del metodo e non come tipo restituito di metodi di interfaccia. Il tipo di controvariante può essere utilizzato anche per i vincoli generici. Nel codice seguente viene illustrato come dichiarare un'interfaccia controvariante e utilizzare un vincolo generico per uno dei metodi.

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();            
}

È inoltre possibile supportare sia la covarianza che la controvarianza nella stessa interfaccia, ma per parametri di tipo diversi, come illustrato nell'esempio di codice seguente.

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);
}

In Visual Basic, non è possibile dichiarare eventi nelle interfacce variant senza specificare il tipo di delegato. Inoltre, un'interfaccia variant non può disporre di strutture, enumerazioni o classi annidate, ma può disporre di interfacce annidate. Tale situazione è illustrata nel codice seguente.

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

Implementazione di interfacce generiche variant

Le interfacce generiche variant vengono implementate nelle classi utilizzando la stessa sintassi utilizzata per le interfacce invarianti. Nell'esempio di codice seguente viene illustrato come implementare un'interfaccia covariante in una classe generica.

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);
    }
}

Le classi che implementano le interfacce variant sono invarianti. Si consideri il codice di esempio seguente.

' 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;

Estensione di interfacce generiche variant

Quando si estende un'interfaccia generica variant, è necessario utilizzare le parole chiave in e out per specificare in modo esplicito se l'interfaccia derivata supporta la varianza. Il compilatore non deduce la varianza dall'interfaccia estesa. Si considerino ad esempio le seguenti interfacce.

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> { }

Nell'interfaccia IInvariant<T> (Invariant(Of T) in Visual Basic), il parametro di tipo generico T è invariante, mentre in IExtCovariant<out T> (IExtCovariant (Of Out T) in Visual Basic) il parametro di tipo è covariante, anche se entrambe le interfacce estendono la stessa interfaccia. La stessa regola viene applicata ai parametri di tipo generico controvarianti.

È possibile creare un'interfaccia che estende sia l'interfaccia in cui il parametro di tipo generico T è covariante che l'interfaccia in cui è controvariante se nell'interfaccia che estende il parametro di tipo generico T è invariante, come illustrato nell'esempio di codice riportato di seguito.

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> { }

Se, tuttavia, un parametro di tipo generico T è dichiarato covariante in una interfaccia, non è possibile dichiararlo controvariante nell'interfaccia che estende o viceversa, come illustrato nell'esempio di codice riportato di seguito.

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> { }

Evitare l'ambiguità

Quando si implementano le interfacce generiche variant, la varianza può talvolta generare ambiguità. Questa evenienza deve essere evitata.

Ad esempio, l'implementazione esplicita della stessa interfaccia generica variant con parametri di tipo generico diversi in una classe può creare ambiguità. Il compilatore non produce errori in questo caso, ma non viene specificato quale implementazione dell'interfaccia sarà scelta in fase di esecuzione. Potrebbero pertanto essere generati bug di difficile individuazione nel codice. Si consideri il codice di esempio seguente.

Nota

Con Option Strict Off, Visual Basic genera un avviso del compilatore quando esiste un'implementazione dell'interfaccia ambigua. Con Option Strict On, Visual Basic genera un errore del compilatore.

' 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();
    }
}

In questo esempio, non è specificato come il metodo pets.GetEnumerator scelga tra Cat e Dog. Potrebbero verificarsi problemi nel codice.

Vedere anche

Riferimenti

Utilizzo della varianza per i delegati generici Func e Action (C# e Visual Basic)

Concetti

Varianza nelle interfacce generiche (C# e Visual Basic)