Esporta (0) Stampa
Espandi tutto
Il presente articolo è stato tradotto manualmente. Passare il puntatore sulle frasi nell'articolo per visualizzare il testo originale. Ulteriori informazioni.
Traduzione
Originale

Covarianza e controvarianza nei generics

La covarianza e la controvarianza sono termini che fanno riferimento alla possibilità di utilizzare un tipo meno derivato o più derivato di quanto specificato in origine. I parametri di tipo generico supportano la covarianza e la controvarianza per offrire la massima flessibilità nell'assegnazione e nell'utilizzo dei tipi generici. Quando si fa riferimento a un sistema di tipi, la covarianza, la controvarianza e l'invarianza hanno le seguenti definizioni. Negli esempi si presuppone una classe di base denominata Base e una classe derivata denominata Derived.

  • Covariance

    Consente di utilizzare un tipo più specifico di quello originariamente specificato.

    È possibile assegnare un'istanza di IEnumerable<Derived> (IEnumerable(Of Derived) in Visual Basic) a una variabile di tipo IEnumerable<Base>.

  • Contravariance

    Consente di utilizzare un tipo più generico (meno derivato) di quello originariamente specificato.

    È possibile assegnare un'istanza di IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) a una variabile di tipo IEnumerable<Derived>.

  • Invariance

    Significa che è possibile utilizzare solo il tipo specificato originariamente; pertanto un parametro di tipo generico invariante non è covariante o controvariante.

    Non è possibile assegnare un'istanza di IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) a una variabile di tipo IEnumerable<Derived> o viceversa.

I parametri di tipo covariante consentono di effettuare assegnazioni simili al polimorfismo ordinario., come illustrato nel codice seguente.


IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;


La classe List<T> implementa l'interfaccia IEnumerable<T>, pertanto List<Derived> (List(Of Derived) in Visual Basic) implementa IEnumerable<Derived>. Il parametro di tipo covariante completa l'operazione.

Viceversa, la controvarianza è poco intuitiva. Nell'esempio seguente viene creato un delegato di tipo Action<Base> (Action(Of Base) in Visual Basic), che viene quindi assegnato a una variabile di tipo Action<Derived>.


Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());


Può sembrare un passo indietro, ma si tratta di codice indipendente dai tipi che viene compilato ed eseguito. L'espressione lambda corrisponde al delegato a cui è assegnato, pertanto definisce un metodo che accetta un parametro di tipo Base senza valore restituito. Il delegato risultante può essere assegnato a una variabile di tipo Action<Derived> poiché il parametro di tipo T del delegato Action<T> è controvariante. Il codice è indipendente dai tipi perché T specifica un tipo di parametro. Quando il delegato di tipo Action<Base> viene richiamato come se fosse un delegato di tipo Action<Derived>, il relativo argomento deve essere di tipo Derived. Questo argomento può sempre essere passato in modo sicuro al metodo sottostante, perché il parametro del metodo è di tipo Base.

In genere, un parametro di tipo covariante può essere utilizzato come tipo restituito di un delegato e i parametri di tipo controvariante possono essere utilizzati come tipi di parametri. Per un'interfaccia, i parametri di tipo covariante possono essere utilizzati come tipi restituiti dei metodi dell'interfaccia e i parametri di tipo controvariante possono essere utilizzati come tipi di parametri dei metodi dell'interfaccia.

La covarianza e la controvarianza sono definite collettivamente varianza. Un parametro di tipo generico non contrassegnato come covariante o controvariante viene definito invariante. Di seguito vengono riepilogati i concetti relativi alla varianza in Common Language Runtime:

  • In .NET Framework 4, i parametri di tipo variante sono limitati ai tipi di interfaccia generica e delegato generico.

  • Un tipo di interfaccia generica o delegato generico può presentare parametri di tipo sia covariante sia controvariante.

  • La varianza si applica solo ai tipi di riferimento. Se si specifica un tipo di valore per un parametro di tipo variante, tale parametro di tipo è invariante per il tipo costruito risultante.

  • La varianza non si applica alla combinazione di delegati. Ciò significa che nel caso di due delegati di tipo Action<Derived> e Action<Base> (Action(Of Derived) e Action(Of Base) in Visual Basic), non è possibile combinare il secondo delegato con il primo anche se il risultato sarebbe indipendente dai tipi. La varianza consente l'assegnazione del secondo delegato a una variabile di tipo Action<Derived>, ma i delegati possono essere combinati solo se il loro tipo corrisponde esattamente.

Nelle sottosezioni seguenti vengono descritti in dettaglio parametri di tipo covariante e controvariante:

A partire da .NET Framework 4, diverse interfacce generiche presentano parametri di tipo controvariante. Ad esempio: IEnumerable<T>IEnumerator<T>IQueryable<T> e IGrouping<TKey, TElement>. Tutti i parametri del tipo di queste interfacce sono covarianti, pertanto i parametri del tipo vengono utilizzati solo per i tipi restituiti dei membri.

Nell'esempio seguente vengono illustrati parametri di tipo covariante. Nell'esempio vengono definiti due tipi: Base presenta un metodo statico denominato PrintBases che accetta IEnumerable<Base> (IEnumerable(Of Base) in Visual Basic) e stampa gli elementi. Derived eredita da Base. Nell'esempio viene creato un tipo List<Derived> (List(Of Derived) in Visual Basic) vuoto e viene illustrato che è possibile passare tale tipo a PrintBases e assegnarlo a una variabile di tipo IEnumerable<Base> senza eseguire il cast. List<T> implementa IEnumerable<T>, che dispone di un solo parametro di tipo covariante. Il parametro di tipo covariante è il motivo per cui è possibile utilizzare un'istanza di IEnumerable<Derived> anziché di IEnumerable<Base>.


using System;
using System.Collections.Generic;

class Base
{
    public static void PrintBases(IEnumerable<Base> bases)
    {
        foreach(Base b in bases)
        {
            Console.WriteLine(b);
        }
    }
}

class Derived : Base
{
    public static void Main()
    {
        List<Derived> dlist = new List<Derived>();

        Derived.PrintBases(dlist);
        IEnumerable<Base> bIEnum = dlist;
    }
}


Torna all'inizio

A partire da .NET Framework 4, diverse interfacce generiche presentano parametri di tipo controvariante. Ad esempio: IComparer<T>, IComparable<T> e IEqualityComparer<T>. Queste interfacce dispongono solo di parametri di tipo controvariante, pertanto i parametri di tipo vengono utilizzati solo come tipi di parametri nei membri delle interfacce.

Nell'esempio seguente vengono illustrati parametri di tipo controvariante. Nell'esempio viene definita una classe Shape astratta (MustInherit in Visual Basic) con una proprietà Area. Nell'esempio viene definita anche una classe ShapeAreaComparer che implementa IComparer<Shape> (IComparer(Of Shape) in Visual Basic). L'implementazione del metodo IComparer<T>.Compare è basata sul valore della proprietà Area, pertanto ShapeAreaComparer può essere utilizzato per ordinare gli oggetti Shape in base all'area.

La classe Circle eredita dalla classe Shape ed esegue l'override di Area. Nell'esempio viene creato un oggetto SortedSet<T> di oggetti Circle, utilizzando un costruttore che accetta un oggetto IComparer<Circle> (IComparer(Of Circle) in Visual Basic). Tuttavia, anziché passare un oggetto IComparer<Circle>, nell'esempio viene passato un oggetto ShapeAreaComparer che implementa IComparer<Shape>. È possibile passare un operatore di confronto di un tipo meno derivato (Shape) quando il codice chiama un operatore di confronto di un tipo più derivato (Circle), poiché il parametro di tipo dell'interfaccia generica IComparer<T> è controvariante.

Quando un nuovo oggetto Circle viene aggiunto all'oggetto SortedSet<Circle>, il metodo IComparer<Shape>.Compare (metodo IComparer(Of Shape).Compare in Visual Basic) dell'oggetto ShapeAreaComparer viene chiamato ogni volta che il nuovo elemento viene confrontato con un elemento esistente. Il tipo di parametro del metodo (Shape) è meno derivato rispetto al tipo passato (Circle), pertanto la chiamata è indipendente dai tipi. La controvarianza consente a ShapeAreaComparer di ordinare una raccolta di qualsiasi singolo tipo nonché una raccolta mista di tipi che derivano da Shape.


using System;
using System.Collections.Generic;

abstract class Shape
{
    public virtual double Area { get { return 0; }}
}

class Circle : Shape
{
    private double r;
    public Circle(double radius) { r = radius; }
    public double Radius { get { return r; }}
    public override double Area { get { return Math.PI * r * r; }}
}

class ShapeAreaComparer : System.Collections.Generic.IComparer<Shape>
{
    int IComparer<Shape>.Compare(Shape a, Shape b) 
    { 
        if (a == null) return b == null ? 0 : -1;
        return b == null ? 1 : a.Area.CompareTo(b.Area);
    }
}

class Program
{
    static void Main()
    {
        // You can pass ShapeAreaComparer, which implements IComparer<Shape>,
        // even though the constructor for SortedSet<Circle> expects 
        // IComparer<Circle>, because type parameter T of IComparer<T> is
        // contravariant.
        SortedSet<Circle> circlesByArea = 
            new SortedSet<Circle>(new ShapeAreaComparer()) 
                { new Circle(7.2), new Circle(100), null, new Circle(.01) };

        foreach (Circle c in circlesByArea)
        {
            Console.WriteLine(c == null ? "null" : "Circle with area " + c.Area);
        }
    }
}

/* This code example produces the following output:

null
Circle with area 0.000314159265358979
Circle with area 162.860163162095
Circle with area 31415.9265358979
 */


Torna all'inizio

In .NET Framework 4 i delegati generici Func, ad esempio Func<T, TResult>, presentano tipi restituiti covarianti e tipi di parametro controvarianti. I delegati generici Action, ad esempio Action<T1, T2>, presentano tipi di parametro controvarianti. Ciò significa che i delegati possono essere assegnati a variabili che presentano tipi di parametro più derivati e (nel caso dei delegati generici Func) tipi restituiti meno derivati.

Nota Nota

L'ultimo parametro di tipo generico dei delegati generici Func specifica il tipo del valore restituito nella firma del delegato. È covariante (parola chiave out), mentre gli altri parametri di tipo generico sono controvarianti (parola chiave in).

Questa condizione è illustrata nel codice che segue. Nella prima parte di codice vengono definite una classe denominata Base, una classe denominata Derived che eredita da Base e un'altra classe con un metodo static (Shared in Visual Basic) denominata MyMethod. Il metodo accetta un'istanza di Base e restituisce un'istanza di Derived. Se l'argomento è un'istanza di Derived, MyMethod la restituisce. Se l'argomento è un'istanza di Base, MyMethod restituisce una nuova istanza di Derived. In Main(), nell'esempio viene creata un'istanza di Func<Base, Derived> (Func(Of Base, Derived) in Visual Basic) che rappresenta MyMethod, quindi l'istanza viene archiviata nella variabile f1.


public class Base {}
public class Derived : Base {}

public class Program
{
    public static Derived MyMethod(Base b)
    {
        return b as Derived ?? new Derived();
    }

    static void Main() 
    {
        Func<Base, Derived> f1 = MyMethod;


Nella seconda parte di codice viene illustrato che è possibile assegnare il delegato a una variabile di tipo Func<Base, Base> (Func(Of Base, Base) in Visual Basic), poiché il tipo restituito è covariante.


// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());


Nella terza parte di codice viene illustrato che è possibile assegnare il delegato a una variabile di tipo Func<Derived, Derived> (Func(Of Derived, Derived) in Visual Basic), poiché il tipo di parametro è controvariante.


// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());


Nella parte finale di codice viene illustrato che è possibile assegnare il delegato a una variabile di tipo Func<Derived, Base> (Func(Of Derived, Base) in Visual Basic), combinando gli effetti del tipo di parametro controvariante e del tipo restituito covariante.


// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());


Dd799517.collapse_all(it-it,VS.110).gifVarianza nei delegati generici e non generici

Nel codice precedente la firma di MyMethod corrisponde esattamente alla firma del delegato generico costruito: Func<Base, Derived> (Func(Of Base, Derived) in Visual Basic). Nell'esempio viene illustrato che è possibile archiviare questo delegato generico in variabili o parametri del metodo che presentano tipi di parametro più derivati e tipi restituiti meno derivati, purché tutti i tipi delegati vengano costruiti a partire dal tipo delegato generico Func<T, TResult>.

Questo è un punto importante. Gli effetti della covarianza e della controvarianza nei parametri di tipo dei delegati generici sono simili agli effetti della covarianza e della controvarianza nella normale associazione di delegati (vedere Varianza nei delegati (C# e Visual Basic)). Tuttavia, la varianza nell'associazione di delegati funziona con tutti i tipi delegati, non solo con quelli generici aventi parametri di tipo variante. Inoltre, la varianza nell'associazione di delegati consente l'associazione di un metodo a qualsiasi delegato con tipi di parametro più restrittivi e un tipo restituito meno restrittivo, mentre l'assegnazione di delegati generici funziona solo se entrambi i tipi di delegati sono costruiti a partire dalla stessa definizione di tipo generico.

Nell'esempio seguente sono mostrati gli effetti combinati della varianza nell'associazione di delegati e della varianza nei parametri di tipo generico. Nell'esempio viene definita una gerarchia di tre tipi, da quello meno derivato (Type1) a quello più derivato (Type3). La varianza nella normale associazione di delegati viene utilizzata per associare un metodo con un tipo di parametro Type1 e un tipo restituito Type3 a un delegato generico con un tipo di parametro Type2 e un tipo restituito Type2. Il delegato generico risultante viene quindi assegnato a un'altra variabile il cui tipo delegato generico presenta un parametro di tipo Type3 e un tipo restituito Type1, utilizzando la covarianza e la controvarianza di parametri di tipo generico. Per la seconda assegnazione è necessario che sia il tipo della variabile sia quello del delegato vengano costruiti a partire dalla stessa definizione di tipo generico, in questo caso Func<T, TResult>.


using System;

public class Type1 {}
public class Type2 : Type1 {}
public class Type3 : Type2 {}

public class Program
{
    public static Type3 MyMethod(Type1 t)
    {
        return t as Type3 ?? new Type3();
    }

    static void Main() 
    {
        Func<Type2, Type2> f1 = MyMethod;

        // Covariant return type and contravariant parameter type.
        Func<Type3, Type1> f2 = f1;
        Type1 t1 = f2(new Type3());
    }
}


Torna all'inizio

A partire da .NET Framework 4, Visual Basic e C# presentano parole chiave che consentono di contrassegnare i parametri di tipo generico di interfacce e delegati come covarianti o controvarianti.

Nota Nota

A partire dalla versione 2.0 di .NET Framework, Common Language Runtime supporta le annotazioni di variante nei parametri di tipo generico. Nelle versioni precedenti a .NET Framework 4, l'unico modo per definire una classe generica avente queste annotazioni è utilizzare Microsoft Intermediate Language (MSIL), compilando la classe con Ilasm.exe (Assembler IL) oppure generandola in un assembly dinamico.

Un parametro di tipo covariante è contrassegnato con la parola chiave out (parola chiave Out in Visual Basic, + per l'assembler MSIL). È possibile utilizzare un parametro di tipo covariante come valore restituito di un metodo che appartiene a un'interfaccia o come tipo restituito di un delegato. Non è possibile utilizzare un parametro di tipo covariante come vincolo di tipo generico per i metodi di interfaccia.

Nota Nota

Se un metodo di un'interfaccia presenta un parametro che è un tipo delegato generico, per specificare un parametro di tipo controvariante del tipo delegato è possibile utilizzare un parametro di tipo covariante del tipo di interfaccia.

Un parametro di tipo controvariante è contrassegnato con la parola chiave in (parola chiave In in Visual Basic, - per l'assembler MSIL). È possibile utilizzare un parametro di tipo controvariante come tipo di un parametro di un metodo che appartiene a un'interfaccia o come tipo di un parametro di un delegato. È possibile utilizzare un parametro di tipo controvariante come vincolo di tipo generico per un metodo di interfaccia.

Solo i tipi di interfaccia e i tipi delegati possono presentare parametri di tipo variante. Un tipo di interfaccia o delegato può presentare parametri di tipo sia covariante sia controvariante.

Visual Basic e C# non consentono di violare le regole per l'utilizzo di parametri di tipo covariante e controvariante o aggiungere annotazioni di covarianza e controvarianza ai parametri relativi a tipi diversi da interfacce e delegati. L'assembler MSIL non esegue tali controlli. Tuttavia, se si tenta di caricare un tipo che viola le regole, viene generato un oggetto TypeLoadException.

Per informazioni e per un esempio di codice, vedere Varianza nelle interfacce generiche (C# e Visual Basic).

Torna all'inizio

Aggiunte alla community

AGGIUNGI
Microsoft sta conducendo un sondaggio in linea per comprendere l'opinione degli utenti in merito al sito Web di MSDN. Se si sceglie di partecipare, quando si lascia il sito Web di MSDN verrà visualizzato il sondaggio in linea.

Si desidera partecipare?
Mostra:
© 2014 Microsoft