Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Covariância e contravariância são termos que se referem à capacidade de usar um tipo mais derivado (mais específico) ou um tipo menos derivado (menos específico) do que o especificado originalmente. Os parâmetros de tipo genérico dão suporte à covariância e à contravariância para proporcionar maior flexibilidade na atribuição e no uso de tipos genéricos.
Quando você estiver se referindo a um sistema de tipos, covariância, contravariância e invariância têm as definições a seguir. Os exemplos pressupõem uma classe base nomeada Base
e uma classe derivada chamada Derived
.
Covariance
Permite que você use um tipo mais derivado do que o especificado originalmente.
Você pode atribuir uma instância de
IEnumerable<Derived>
uma variável do tipoIEnumerable<Base>
.Contravariance
Permite que você use um tipo mais genérico (menos derivado) do que o especificado originalmente.
Você pode atribuir uma instância de
Action<Base>
uma variável do tipoAction<Derived>
.Invariance
Significa que você pode usar apenas o tipo especificado originalmente. Um parâmetro de tipo genérico invariável não é covariante nem contravariante.
Você não pode atribuir uma instância de
List<Base>
uma variável do tipoList<Derived>
ou vice-versa.
Os parâmetros de tipo covariante permitem que você faça atribuições que se parecem muito com o Polimorfismo comum, conforme mostrado no código a seguir.
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d
A List<T> classe implementa a IEnumerable<T> interface, portanto List<Derived>
(List(Of Derived)
no Visual Basic) implementa IEnumerable<Derived>
. O parâmetro de tipo covariante faz o restante.
Por outro lado, a contravariância parece contraintuitiva. O exemplo a seguir cria um delegado de tipo Action<Base>
(Action(Of Base)
no Visual Basic) e atribui esse delegado a uma variável do tipo Action<Derived>
.
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
Dim b As Action(Of Base) = Sub(target As Base)
Console.WriteLine(target.GetType().Name)
End Sub
Dim d As Action(Of Derived) = b
d(New Derived())
Isso parece ser um retrocesso, mas é esse código de tipo seguro que compila e executa. A expressão lambda corresponde ao delegado ao qual está atribuída, portanto, define um método que usa um parâmetro de tipo Base
e que não tem nenhum valor retornado. O delegado resultante pode ser atribuído a uma variável do tipo Action<Derived>
porque o tipo de parâmetro T
do delegado Action<T> é contravariante. O código é type-safe porque T
especifica um tipo de parâmetro. Quando o delegado do tipo Action<Base>
é invocado como se fosse um delegado do tipo Action<Derived>
, seu argumento deve ser do tipo Derived
. Esse argumento sempre pode ser passado com segurança para o método subjacente, pois o parâmetro do método é do tipo Base
.
Em geral, um parâmetro de tipo covariante pode ser usado como o tipo de retorno de um delegado e parâmetros de tipo contravariante podem ser usados como tipos de parâmetro. Para uma interface, parâmetros de tipo covariante podem ser usados como os tipos de retorno dos métodos da interface e parâmetros de tipo contravariantes podem ser usados como os tipos de parâmetro dos métodos da interface.
Covariância e contravariância são coletivamente conhecidas como variação. Um parâmetro de tipo genérico que não está marcado como covariante ou contravariante é conhecido como invariável. Um breve resumo de fatos sobre variância em Common Language Runtime:
Os parâmetros de tipo variant são restritos a interfaces genéricas e tipos de delegado genéricos.
Uma interface genérica ou um tipo de delegado genérico pode ter parâmetros de tipo covariantes e contravariantes.
A variação se aplica somente aos tipos de referência; se você especificar um tipo de valor para um parâmetro de tipo variante, esse parâmetro de tipo será invariável para o tipo construído resultante.
A variância não se aplica à combinação de delegado. Ou seja, considerando dois delegados de tipos
Action<Derived>
eAction<Base>
(Action(Of Derived)
eAction(Of Base)
no Visual Basic), você não pode combinar o segundo delegado com o primeiro, embora o resultado seja tipo seguro. A variação permite que o segundo delegado seja atribuído a uma variável de tipoAction<Derived>
, mas os delegados só poderão combinar se seus tipos corresponderem exatamente.A partir do C# 9, há suporte para tipos de retorno covariantes. Um método de substituição pode declarar um tipo de retorno mais derivado do método que ele substitui e uma propriedade somente leitura de substituição pode declarar um tipo mais derivado.
Interfaces genéricas com parâmetros de tipo covariante
Várias interfaces genéricas têm parâmetros de tipo covariante, por exemplo, IEnumerable<T>, e IEnumerator<T>IQueryable<T>IGrouping<TKey,TElement>. Todos os parâmetros de tipo dessas interfaces são covariantes, portanto, os parâmetros de tipo são usados apenas para os tipos de retorno dos membros.
O exemplo a seguir ilustra parâmetros de tipo covariante. O exemplo define dois tipos: Base
tem um método estático chamado PrintBases
que usa um IEnumerable<Base>
(IEnumerable(Of Base)
no Visual Basic) e imprime os elementos.
Derived
herda de Base
. O exemplo cria um List<Derived>
vazio (List(Of Derived)
no Visual Basic) e demonstra que esse tipo pode ser passado a PrintBases
e atribuído a uma variável do tipo IEnumerable<Base>
sem converter.
List<T> implementa IEnumerable<T>, que tem um único parâmetro de tipo covariante. O parâmetro de tipo covariante é o motivo pelo qual uma instância IEnumerable<Derived>
pode ser usada em vez de 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;
}
}
Imports System.Collections.Generic
Class Base
Public Shared Sub PrintBases(ByVal bases As IEnumerable(Of Base))
For Each b As Base In bases
Console.WriteLine(b)
Next
End Sub
End Class
Class Derived
Inherits Base
Shared Sub Main()
Dim dlist As New List(Of Derived)()
Derived.PrintBases(dlist)
Dim bIEnum As IEnumerable(Of Base) = dlist
End Sub
End Class
Interfaces genéricas com parâmetros de tipo contravariante
Várias interfaces genéricas têm parâmetros de tipo contravariantes; por exemplo: IComparer<T>, IComparable<T>e IEqualityComparer<T>. Essas interfaces possuem apenas parâmetros de tipo contravariantes. Assim, os parâmetros de tipo são usados apenas como parâmetros de tipo nos membros das interfaces.
O exemplo a seguir ilustra parâmetros de tipo contravariante. O exemplo define uma classe abstrata (MustInherit
no Visual Basic) Shape
com uma Area
propriedade. O exemplo também define uma ShapeAreaComparer
classe que implementa IComparer<Shape>
(IComparer(Of Shape)
no Visual Basic). A implementação do IComparer<T>.Compare método baseia-se no valor da Area
propriedade, portanto ShapeAreaComparer
, pode ser usada para classificar Shape
objetos por área.
A Circle
classe herda Shape
e substitui Area
. O exemplo cria um SortedSet<T> de objetos Circle
usando um construtor que usa IComparer<Circle>
(IComparer(Of Circle)
no Visual Basic). No entanto, em vez de passar um IComparer<Circle>
, o exemplo passa um ShapeAreaComparer
objeto, que implementa IComparer<Shape>
. O exemplo pode passar um comparador de um tipo derivado (Shape
) quando um código chama um comparador de um tipo mais derivado (Circle
) porque o parâmetro de tipo da interface genérica IComparer<T> é contravariante.
Quando um novo Circle
objeto é adicionado ao SortedSet<Circle>
, o método IComparer<Shape>.Compare
(IComparer(Of Shape).Compare
método no Visual Basic) do objeto ShapeAreaComparer
é chamado sempre que o novo elemento é comparado a um elemento existente. O tipo de parâmetro do método (Shape
) é menos derivado do que o tipo que está sendo passado (Circle
), portanto, a chamada é tipo seguro. A contravariância permite que ShapeAreaComparer
classifique uma coleção de um único tipo, bem como uma coleção mista de tipos, que deriva de 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
*/
Imports System.Collections.Generic
MustInherit Class Shape
Public MustOverride ReadOnly Property Area As Double
End Class
Class Circle
Inherits Shape
Private r As Double
Public Sub New(ByVal radius As Double)
r = radius
End Sub
Public ReadOnly Property Radius As Double
Get
Return r
End Get
End Property
Public Overrides ReadOnly Property Area As Double
Get
Return Math.Pi * r * r
End Get
End Property
End Class
Class ShapeAreaComparer
Implements System.Collections.Generic.IComparer(Of Shape)
Private Function AreaComparer(ByVal a As Shape, ByVal b As Shape) As Integer _
Implements System.Collections.Generic.IComparer(Of Shape).Compare
If a Is Nothing Then Return If(b Is Nothing, 0, -1)
Return If(b Is Nothing, 1, a.Area.CompareTo(b.Area))
End Function
End Class
Class Program
Shared Sub Main()
' You can pass ShapeAreaComparer, which implements IComparer(Of Shape),
' even though the constructor for SortedSet(Of Circle) expects
' IComparer(Of Circle), because type parameter T of IComparer(Of T)
' is contravariant.
Dim circlesByArea As New SortedSet(Of Circle)(New ShapeAreaComparer()) _
From {New Circle(7.2), New Circle(100), Nothing, New Circle(.01)}
For Each c As Circle In circlesByArea
Console.WriteLine(If(c Is Nothing, "Nothing", "Circle with area " & c.Area))
Next
End Sub
End Class
' This code example produces the following output:
'
'Nothing
'Circle with area 0.000314159265358979
'Circle with area 162.860163162095
'Circle with area 31415.9265358979
Delegados genéricos com parâmetros de tipo variante
Os Func
delegados genéricos, como Func<T,TResult>, têm tipos de retorno covariantes e tipos de parâmetro contravariantes. Os Action
delegados genéricos, como Action<T1,T2>, têm tipos de parâmetro contravariantes. Isso significa que os delegados podem ser atribuídos a variáveis que têm tipos de parâmetro mais derivados e (no caso dos delegados Func
genéricos) tipos de retorno menos derivados.
Observação
O último parâmetro de tipo genérico dos Func
delegados genéricos especifica o tipo do valor retornado na assinatura delegada. É covariante (out
palavra-chave), enquanto os outros parâmetros de tipo genérico são contravariantes (in
palavra-chave).
O código a seguir ilustra isso. A primeira parte do código define uma classe chamada Base
, uma classe chamada Derived
que herda Base
e outra classe com um static
método (Shared
no Visual Basic) chamado MyMethod
. O método recebe uma instância de Base
e retorna uma instância de Derived
. (Se o argumento for uma instância de Derived
, MyMethod
retorna- o; se o argumento for uma instância de Base
, MyMethod
retornará uma nova instância de Derived
.) Em Main()
, o exemplo cria uma instância de Func<Base, Derived>
(Func(Of Base, Derived)
no Visual Basic) que representa MyMethod
e armazena-a na variável 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;
Public Class Base
End Class
Public Class Derived
Inherits Base
End Class
Public Class Program
Public Shared Function MyMethod(ByVal b As Base) As Derived
Return If(TypeOf b Is Derived, b, New Derived())
End Function
Shared Sub Main()
Dim f1 As Func(Of Base, Derived) = AddressOf MyMethod
A segunda parte do código mostra que o delegado pode ser atribuído a uma variável de tipo Func<Base, Base>
(Func(Of Base, Base)
no Visual Basic), porque o tipo de retorno é covariante.
// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());
' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())
A terceira parte do código mostra que o delegado pode ser atribuído a uma variável de tipo Func<Derived, Derived>
(Func(Of Derived, Derived)
no Visual Basic), porque o tipo de parâmetro é contravariante.
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());
' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())
A parte final do código mostra que o delegado pode ser atribuído a uma variável de tipo Func<Derived, Base>
(Func(Of Derived, Base)
no Visual Basic), combinando os efeitos do tipo de parâmetro contravariante e o tipo de retorno covariante.
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());
' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
Variação em delegados não genéricos
No código anterior, a assinatura de MyMethod
corresponde exatamente à assinatura do delegado genérico construído: Func<Base, Derived>
(Func(Of Base, Derived)
no Visual Basic). O exemplo mostra que esse delegado genérico pode ser armazenado em variáveis ou parâmetros de método que têm tipos de parâmetro mais derivados e tipos de retorno menos derivados, desde que todos os tipos delegados sejam construídos a partir do tipo Func<T,TResult>delegado genérico.
Esse é um ponto importante. Os efeitos da covariância e da contravariância nos parâmetros de tipo de delegados genéricos são semelhantes aos efeitos da covariância e da contravariância na associação comum de delegação (confira Variância em delegados (C#) e Variância em delegados (Visual Basic)). No entanto, a variância na associação de delegados funciona com todos os tipos de delegados, e não apenas com tipos de delegados genéricos com parâmetros de tipo variantes. Além disso, a variação na associação de delegados permite que um método seja associado a qualquer delegado que tenha tipos de parâmetro mais restritivos e um tipo de retorno menos restritivo, enquanto a atribuição de delegados genéricos só funcionará se ambos os tipos delegados forem construídos a partir da mesma definição de tipo genérico.
O exemplo a seguir mostra os efeitos combinados da variação na associação delegada e na variação em parâmetros de tipo genérico. O exemplo define uma hierarquia de tipo que inclui três tipos, do menos derivado (Type1
) ao mais derivado (Type3
). A variação na associação de delegados comuns é usada para associar um método com um tipo de parâmetro Type1
e um tipo de retorno Type3
a um delegado genérico com um tipo de parâmetro Type2
e um tipo de retorno Type2
. O delegado genérico resultante é então atribuído a outra variável cujo tipo de delegado genérico tem um parâmetro de tipo Type3
e um tipo de retorno Type1
, usando a covariância e a contravariância dos parâmetros de tipo genérico. A segunda atribuição requer que o tipo de variável e o tipo delegado sejam construídos a partir da mesma definição de tipo genérico, nesse 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());
}
}
Public Class Type1
End Class
Public Class Type2
Inherits Type1
End Class
Public Class Type3
Inherits Type2
End Class
Public Class Program
Public Shared Function MyMethod(ByVal t As Type1) As Type3
Return If(TypeOf t Is Type3, t, New Type3())
End Function
Shared Sub Main()
Dim f1 As Func(Of Type2, Type2) = AddressOf MyMethod
' Covariant return type and contravariant parameter type.
Dim f2 As Func(Of Type3, Type1) = f1
Dim t1 As Type1 = f2(New Type3())
End Sub
End Class
Definir delegados e interfaces genéricas variantes
O Visual Basic e o C# têm palavras-chave que permitem marcar os parâmetros de tipo genérico de interfaces e delegados como covariantes ou contravariantes.
Um parâmetro de tipo covariante é marcado com a out
palavra-chave (Out
palavra-chave no Visual Basic). Você pode usar um parâmetro de tipo covariante como o valor retornado de um método que pertence a uma interface ou como o tipo de retorno de um delegado. Você não pode usar um parâmetro de tipo covariante como uma restrição de tipo genérico para métodos de interface.
Observação
Se um método de uma interface tiver um parâmetro que seja um tipo delegado genérico, um parâmetro de tipo covariante do tipo de interface poderá ser usado para especificar um parâmetro de tipo contravariante do tipo delegado.
Um parâmetro de tipo contravariante é marcado com a in
palavra-chave (In
palavra-chave no Visual Basic). Você pode usar um parâmetro de tipo contravariante como o tipo de um parâmetro de um método que pertence a uma interface ou como o tipo de um parâmetro de um delegado. Você pode usar um parâmetro de tipo contravariante como uma restrição de tipo genérico para um método de interface.
Somente tipos de interface e tipos de delegado podem ter parâmetros de tipo variante. Um tipo de interface ou delegado pode ter parâmetros de tipo covariantes e contravariantes.
O Visual Basic e o C# não permitem que você viole as regras para usar parâmetros de tipo covariantes e contravariantes ou adicione anotações de covariância e contravariância aos parâmetros de tipo de tipos que não sejam interfaces e delegados.
Para obter informações e código de exemplo, consulte Variação em Interfaces Genéricas (C#) e Variação em Interfaces Genéricas (Visual Basic).
Lista de tipos
Os tipos de interface e delegado a seguir têm parâmetros de tipo covariantes e/ou contravariantes.
Tipo | Parâmetros de tipo covariante | Parâmetros de tipo contravariantes |
---|---|---|
Action<T> a Action<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16> | Sim | |
Comparison<T> | Sim | |
Converter<TInput,TOutput> | Sim | Sim |
Func<TResult> | Sim | |
Func<T,TResult> a Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult> | Sim | Sim |
IComparable<T> | Sim | |
Predicate<T> | Sim | |
IComparer<T> | Sim | |
IEnumerable<T> | Sim | |
IEnumerator<T> | Sim | |
IEqualityComparer<T> | Sim | |
IGrouping<TKey,TElement> | Sim | |
IOrderedEnumerable<TElement> | Sim | |
IOrderedQueryable<T> | Sim | |
IQueryable<T> | Sim |