Criando interfaces genéricas variantes (C#)
Você pode declarar parâmetros de tipo genéricos em interfaces como covariante ou contravariante. A covariância permite que os métodos de interface tenham mais tipos de retorno derivados do que os definidos pelos parâmetros de tipo genéricos. A contravariância permite que os métodos de interface tenham tipos de argumento menos derivados do que os especificados pelos parâmetros genéricos. Uma interface genérica que tem parâmetros de tipo genéricos covariantes ou contravariantes é chamada de variante.
Nota
O .NET Framework 4 introduziu o suporte à variância para várias interfaces genéricas existentes. Para obter a lista das interfaces variantes no .NET, consulte Variância em interfaces genéricas (C#).
Declarando interfaces genéricas de variantes
Você pode declarar interfaces genéricas variantes usando as in
palavras-chave e out
para parâmetros de tipo genérico.
Importante
ref
, in
e os parâmetros em C# não podem ser variantes out
. Os tipos de valor também não suportam variância.
Você pode declarar uma covariante de parâmetro de tipo genérico usando a out
palavra-chave. O tipo de covariante deve satisfazer as seguintes condições:
O tipo é usado apenas como um tipo de retorno de métodos de interface e não usado como um tipo de argumentos de método. Isso é ilustrado no exemplo a seguir, no qual o tipo
R
é declarado covariante.interface ICovariant<out R> { R GetSomething(); // The following statement generates a compiler error. // void SetSomething(R sampleArg); }
Há uma exceção a esta regra. Se você tiver um delegado genérico contravariante como um parâmetro de método, poderá usar o tipo como um parâmetro de tipo genérico para o delegado. Isso é ilustrado pelo tipo
R
no exemplo a seguir. Para obter mais informações, consulte Variância em delegados (C#) e Usando variância para func e delegados genéricos de ação (C#).interface ICovariant<out R> { void DoSomething(Action<R> callback); }
O tipo não é usado como uma restrição genérica para os métodos de interface. Isso é ilustrado no código a seguir.
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; }
Você pode declarar um parâmetro de tipo genérico contravariante usando a in
palavra-chave. O tipo contravariante pode ser usado apenas como um tipo de argumentos de método e não como um tipo de retorno de métodos de interface. O tipo contravariante também pode ser usado para restrições genéricas. O código a seguir mostra como declarar uma interface contravariante e usar uma restrição genérica para um de seus métodos.
interface IContravariant<in A>
{
void SetSomething(A sampleArg);
void DoSomething<T>() where T : A;
// The following statement generates a compiler error.
// A GetSomething();
}
Também é possível suportar covariância e contravariância na mesma interface, mas para parâmetros de tipo diferentes, como mostrado no exemplo de código a seguir.
interface IVariant<out R, in A>
{
R GetSomething();
void SetSomething(A sampleArg);
R GetSetSomethings(A sampleArg);
}
Implementando interfaces genéricas de variantes
Você implementa interfaces genéricas variantes em classes usando a mesma sintaxe usada para interfaces invariantes. O exemplo de código a seguir mostra como implementar uma interface covariante em uma classe genérica.
interface ICovariant<out R>
{
R GetSomething();
}
class SampleImplementation<R> : ICovariant<R>
{
public R GetSomething()
{
// Some code.
return default(R);
}
}
As classes que implementam interfaces variantes são invariantes. Por exemplo, considere o código a seguir.
// 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;
Estendendo interfaces genéricas de variantes
Quando você estende uma interface genérica variante, você tem que usar as in
palavras-chave e out
para especificar explicitamente se a interface derivada suporta variância. O compilador não infere a variância da interface que está sendo estendida. Por exemplo, considere as seguintes interfaces.
interface ICovariant<out T> { }
interface IInvariant<T> : ICovariant<T> { }
interface IExtCovariant<out T> : ICovariant<T> { }
Na interface, o IInvariant<T>
parâmetro T
de tipo genérico é invariante, enquanto no IExtCovariant<out T>
parâmetro de tipo é covariante, embora ambas as interfaces estendam a mesma interface. A mesma regra é aplicada aos parâmetros genéricos de tipo contravariantes.
Você pode criar uma interface que estenda tanto a interface onde o parâmetro T
de tipo genérico é covariante quanto a interface onde ele é contravariante se na interface de extensão o parâmetro T
de tipo genérico for invariante. Isso é ilustrado no exemplo de código a seguir.
interface ICovariant<out T> { }
interface IContravariant<in T> { }
interface IInvariant<T> : ICovariant<T>, IContravariant<T> { }
No entanto, se um parâmetro T
de tipo genérico for declarado covariante em uma interface, você não poderá declará-lo contravariante na interface de extensão ou vice-versa. Isso é ilustrado no exemplo de código a seguir.
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
Evitar a ambiguidade
Quando você implementa interfaces genéricas variantes, a variância às vezes pode levar à ambiguidade. Esta ambiguidade deve ser evitada.
Por exemplo, se você implementar explicitamente a mesma interface genérica variante com diferentes parâmetros de tipo genérico em uma classe, isso pode criar ambiguidade. O compilador não produz um erro neste caso, mas não é especificado qual implementação de interface será escolhida em tempo de execução. Essa ambiguidade pode levar a bugs sutis em seu código. Considere o seguinte exemplo de código.
// 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();
}
}
Neste exemplo, não é especificado como o pets.GetEnumerator
método escolhe entre Cat
e Dog
. Isso pode causar problemas no seu código.