Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
È possibile dichiarare parametri di tipo generico nelle interfacce come covariante o controvarianti. La Covarianza consente ai metodi di interfaccia di avere tipi restituiti più derivati rispetto a quelli definiti dai parametri di tipo generico. La controvarianza consente ai metodi di interfaccia di avere tipi di argomento meno derivati rispetto a quelli specificati dai parametri generici. Un'interfaccia generica con parametri di tipo generico covariante o controvarianti è denominata variant.
Annotazioni
.NET Framework 4 ha introdotto il supporto della varianza per diverse interfacce generiche esistenti. Per l'elenco delle interfacce varianti in .NET, vedere Varianza nelle interfacce generiche (C#).
Dichiarazione di interfacce generiche varianti
È possibile dichiarare interfacce generiche varianti usando le in parole chiave e out per i parametri di tipo generico.
Importante
refI parametri , ine out in C# non possono essere varianti. Anche i tipi valore non supportano la varianza.
È possibile dichiarare un parametro di tipo generico covariante usando la out parola chiave . Il tipo covariante deve soddisfare le condizioni seguenti:
Il tipo viene utilizzato solo come tipo restituito di metodi di interfaccia e non utilizzato come tipo di argomenti del metodo. Questo è illustrato nell'esempio seguente, in cui il tipo
Rè dichiarato covariante.interface ICovariant<out R> { R GetSomething(); // The following statement generates a compiler error. // void SetSomething(R sampleArg); }Esiste un'eccezione a questa regola. Se si dispone di un delegato generico controvariante come parametro del metodo, è possibile usare il tipo come parametro di tipo generico per il delegato. Questo è illustrato dal tipo
Rnell'esempio seguente. Per ulteriori informazioni, vedere Varianza nei delegati (C#) e Uso della varianza per i delegati generici Func e Action (C#).interface ICovariant<out R> { void DoSomething(Action<R> callback); }Il tipo non viene usato come vincolo generico per i metodi di interfaccia. Questo è illustrato nel codice seguente.
interface ICovariant<out R> { // The following statement generates a compiler error // because you can use only contravariant or invariant types // in generic constraints. // void DoSomething<T>() where T : R; }
È possibile dichiarare un parametro di tipo generico controvariante usando la in parola chiave . Il tipo controvariante può essere utilizzato solo come tipo di argomenti del metodo e non come tipo restituito di metodi di interfaccia. Il tipo controvariante può essere usato anche per i vincoli generici. Il codice seguente illustra come dichiarare un'interfaccia controvariante e usare un vincolo generico per uno dei relativi metodi.
interface IContravariant<in A>
{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}
È anche 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<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}
Implementazione di interfacce generiche varianti
Le interfacce generiche varianti vengono implementate nelle classi usando la stessa sintassi usata per le interfacce invarianti. Nell'esempio di codice seguente viene illustrato come implementare un'interfaccia covariante in una classe generica.
interface ICovariant<out R>
{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}
Le classi che implementano interfacce varianti sono invarianti. Si consideri ad esempio il codice seguente.
// 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 delle interfacce generiche varianti
Quando si estende un'interfaccia generica variante, è necessario usare 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 che viene estesa. Si considerino ad esempio le interfacce seguenti.
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }
Nell'interfaccia IInvariant<T> il parametro T di tipo generico è invariante, mentre nel IExtCovariant<out T> parametro di tipo è covariante, anche se entrambe le interfacce estendono la stessa interfaccia. La stessa regola viene applicata ai parametri di tipo generico controvariante.
È possibile creare un'interfaccia che estende sia l'interfaccia in cui il parametro T di tipo generico è covariante che l'interfaccia in cui è controvariante se nell'interfaccia di estensione il parametro di tipo generico T è invariante. Questo è illustrato nell'esempio di codice seguente.
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }
Tuttavia, se un parametro T di tipo generico è dichiarato covariante in un'interfaccia, non è possibile dichiararlo controvariante nell'interfaccia di estensione o viceversa. Questo è illustrato nell'esempio di codice seguente.
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
Evitare ambiguità
Quando si implementano interfacce generiche varianti, la varianza può talvolta causare ambiguità. Tale ambiguità deve essere evitata.
Ad esempio, se si implementa in modo esplicito la stessa interfaccia generica variant con parametri di tipo generici diversi in una classe, può creare ambiguità. Il compilatore non genera un errore in questo caso, ma non è specificato quale implementazione dell'interfaccia verrà scelta in fase di esecuzione. Questa ambiguità potrebbe causare bug sottili nel codice. Si consideri l'esempio di codice seguente.
// 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 viene specificato come il pets.GetEnumerator metodo sceglie tra Cat e Dog. Ciò potrebbe causare problemi nel codice.