Covariância e/contravariância no genéricos
Os parâmetros de tipo genérico covariant e contravariant fornecem maior flexibilidade na atribuição e o uso de tipos genéricos. Por exemplo, parâmetros de tipo covariant permitem fazer atribuições que parecem muito comum de polimorfismo. Suponha que você tenha uma classe base e uma classe derivada, chamado Base e Derived. O polimorfismo permite que você atribua uma instância de Derived a uma variável do tipo Base. Da mesma forma, porque o parâmetro de tipo a IEnumerable<T> interface é covariant, você pode atribuir uma instância de IEnumerable<Derived> (IEnumerable(Of Derived) em Visual Basic) a uma variável do tipo IEnumerable<Base>, como mostra o código a seguir.
Dim d As IEnumerable(Of Derived) = New List(Of Derived)
Dim b As IEnumerable(Of Base) = d
IEnumerable<Derived> d = new List<Derived>();
IEnumerable<Base> b = d;
O List<T> classe implementa o IEnumerable<T> interface isso List<Derived> (List(Of Derived) em Visual Basic) implementa IEnumerable<Derived>. O parâmetro de tipo covariant faz o resto.
Covariância parece muito natural, porque ele se parece com o polimorfismo. Por outro lado, / contravariância, parece confuso. O exemplo a seguir cria um delegado do tipo Action<Base> (Action(Of Base) em Visual Basic) e em seguida, atribui esse delegado a uma variável do tipo Action<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())
Action<Base> b = (target) => { Console.WriteLine(target.GetType().Name); };
Action<Derived> d = b;
d(new Derived());
Isso parece com versões anteriores, mas é um código de tipo seguro que compila e executa. A expressão lambda coincide com o delegado está atribuído, portanto, ele define um método que usa um parâmetro de tipo Base e que tem nenhum valor de retorno. O delegado resultante pode ser atribuído a uma variável do tipo Action<Derived> porque o parâmetro de tipo T da Action<T> representante é contravariant. O código é de tipo seguro porque T Especifica um tipo de parâmetro. Quando o delegado do tipo Action<Base> é chamado como se fosse um delegado do tipo Action<Derived>, o argumento deve ser do tipo Derived. Esse argumento pode sempre ser passado com segurança para o método base, porque o parâmetro do método é do tipo Base.
Em geral, um parâmetro de tipo covariant pode ser usado como o tipo de retorno de um delegado e parâmetros de tipo contravariant podem ser usados como tipos de parâmetro. Para uma interface, os parâmetros de tipo covariant podem ser usados como os tipos de retorno de métodos da interface e os parâmetros de tipo contravariant podem ser usados como os tipos de parâmetro de métodos da interface.
Covariância e/contravariância são coletivamente chamados de variação. Um parâmetro de tipo genérico que não está marcado covariant ou contravariant é conhecido como invariável. Um breve resumo dos fatos sobre a variação no common language runtime:
No .NET Framework versão 4, os parâmetros de tipo variant são restritos a interface genérica e tipos de representante genérico.
Uma interface genérica ou um tipo delegado genérico pode ter ambos covariant e contravariant digite parâmetros.
Variação só se aplica a tipos de referência; Se você especificar um tipo de valor para um parâmetro de tipo variant, o parâmetro de tipo é invariável para o tipo construído resultante.
Não se aplicam a variação para delegar a combinação. Ou seja, recebe dois delegados, tipos de Action<Derived> e Action<Base> (Action(Of Derived) e Action(Of Base) em Visual Basic), não é possível combinar o segundo delegado com o primeiro lugar, embora o resultado seria tipo seguro. Variação permite que o segundo delegado a ser atribuído a uma variável do tipo Action<Derived>, mas os representantes podem combinar somente se os seus tipos de correspondam exata.
As subseções descrevem covariant e contravariant digite parâmetros em detalhes a seguir:
Interfaces genéricas com parâmetros de tipo Covariant
Interfaces genéricas com parâmetros de tipo Contravariant genérico
Representantes genéricos com variante digite parâmetros
Definindo a delegados e Interfaces genéricas da variante
Lista de Interface genérica de variante e tipos Delegate
Interfaces genéricas com parâmetros de tipo Covariant
Começando com o .NET Framework 4, várias interfaces genéricas têm parâmetros de tipo covariant; Por exemplo: IEnumerable<T>, IEnumerator<T>, IQueryable<T>, and IGrouping<TKey, TElement>. Todos os parâmetros de tipo dessas interfaces são covariant, portanto, os parâmetros de tipo são usados apenas para os tipos de retorno de membros.
O exemplo a seguir ilustra os parâmetros de tipo covariant. O exemplo define dois tipos: Basetem um método estático denominado PrintBases que leva um IEnumerable<Base> (IEnumerable(Of Base) em Visual Basic) e imprime elementos. Derivedherda do Base. O exemplo cria um vazio List<Derived> (List(Of Derived) em Visual Basic) e demonstra de que esse tipo pode ser passado para PrintBases e atribuído a uma variável do tipo IEnumerable<Base> sem projeção. List<T>implementa IEnumerable<T>, que tem um parâmetro de tipo covariant único. O parâmetro de tipo covariant é o motivo por que uma instância de IEnumerable<Derived> pode ser usado em vez de IEnumerable<Base>.
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
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;
}
}
Voltar ao topo
Interfaces genéricas com parâmetros de tipo Contravariant genérico
Começando com o .NET Framework 4, várias interfaces genéricas têm parâmetros de tipo contravariant; Por exemplo: IComparer<T>, IComparable<T>, and IEqualityComparer<T>. Essas interfaces têm apenas contravariant digite os parâmetros, portanto, os parâmetros de tipo são usados apenas como tipos de parâmetro nos membros das interfaces.
O exemplo a seguir ilustra os parâmetros de tipo contravariant. O exemplo define um resumo (MustInherit em Visual Basic) Shape de classe com um Area propriedade. O exemplo também define um ShapeAreaComparer classe que implementa IComparer<Shape> (IComparer(Of Shape) em Visual Basic). A implementação da IComparer<T>.Compare método baseia-se no valor da Area propriedade, portanto, ShapeAreaComparer pode ser usado para classificar Shape objetos por área.
O Circle herda da classe Shape e substitui Area. O exemplo cria um SortedSet<T> de Circle objetos, usando um construtor que leva um IComparer<Circle> (IComparer(Of Circle) em Visual Basic). No entanto, em vez de passar um IComparer<Circle>, o exemplo passa um ShapeAreaComparer o objeto, que implementa IComparer<Shape>. O exemplo pode passar um comparador de um tipo derivado de menos (Shape) quando o código chama um comparador de um tipo mais derivado (Circle), porque o parâmetro de tipo a IComparer<T> interface genérica é contravariant.
Quando um novo Circle objeto é adicionado ao SortedSet<Circle>, o IComparer<Shape>.Compare método (IComparer(Of Shape).Compare método em Visual Basic) da ShapeAreaComparer o objeto é chamado sempre que o novo elemento é comparado com um elemento existente. O tipo de parâmetro do método (Shape) é derivada de menor que o tipo que está sendo passado (Circle), portanto, a chamada é tipo seguro. / Contravariância permite ShapeAreaComparer para classificar uma coleção de qualquer tipo único, bem como um conjunto misto de tipos, que derivam de Shape.
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
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
*/
Voltar ao topo
Representantes genéricos com variante digite parâmetros
No .NET Framework 4, o Func delega genérico, como Func<T, TResult>, que têm tipos de retorno covariant e tipos de parâmetro contravariant. O Action delega genérico, como Action<T1, T2>, têm tipos de parâmetro contravariant. Isso significa que os delegados podem ser atribuídos a variáveis tem mais derivado de tipos de parâmetro e (no caso do Func representantes genéricos) menos derivado tipos de retorno.
Observação
O último parâmetro de tipo genérico da Func representantes genéricos Especifica o tipo de valor de retorno na assinatura do delegado.É covariant (out palavra-chave), enquanto os outros parâmetros de tipo genérico são contravariant (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 Basee outra classe com um static método (Shared em Visual Basic) chamado MyMethod. O método obtém uma instância de Base e retorna uma instância de Derived. (Se o argumento for uma instância de Derived, MyMethod retorna; Se o argumento for uma instância de Base, MyMethod retorna uma nova instância de Derived.) Em Main(), o exemplo cria uma instância de Func<Base, Derived> (Func(Of Base, Derived) em Visual Basic) que representa o MyMethode o armazena na variável f1.
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
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;
A segunda parte do código mostra que o delegado pode ser atribuído a uma variável do tipo Func<Base, Base> (Func(Of Base, Base) em Visual Basic), porque o tipo de retorno é covariant.
' Covariant return type.
Dim f2 As Func(Of Base, Base) = f1
Dim b2 As Base = f2(New Base())
// Covariant return type.
Func<Base, Base> f2 = f1;
Base b2 = f2(new Base());
A terceira parte do código mostra que o delegado pode ser atribuído a uma variável do tipo Func<Derived, Derived> (Func(Of Derived, Derived) em Visual Basic), porque o tipo de parâmetro é contravariant.
' Contravariant parameter type.
Dim f3 As Func(Of Derived, Derived) = f1
Dim d3 As Derived = f3(New Derived())
// Contravariant parameter type.
Func<Derived, Derived> f3 = f1;
Derived d3 = f3(new Derived());
A peça final do código mostra que o delegado pode ser atribuído a uma variável do tipo Func<Derived, Base> (Func(Of Derived, Base) em Visual Basic), combinando os efeitos do contravariant parâmetro de tipo e retorna o covariant tipo.
' Covariant return type and contravariant parameter type.
Dim f4 As Func(Of Derived, Base) = f1
Dim b4 As Base = f4(New Derived())
// Covariant return type and contravariant parameter type.
Func<Derived, Base> f4 = f1;
Base b4 = f4(new Derived());
Variação em genérico e não genéricas delegados
No código anterior, a assinatura de MyMethod corresponde exatamente a assinatura do delegado genérico construído: Func<Base, Derived>(Func(Of Base, Derived) em Visual Basic). O exemplo mostra que esse delegado genérico pode ser armazenado em variáveis ou parâmetros de método têm tipos de parâmetro derivados de mais e menos derivado tipos de retorno, desde que todos os tipos de delegados são construídos a partir do tipo de delegado genérico Func<T, TResult>.
Este é um ponto importante. Os efeitos da covariância e/contravariância nos parâmetros de tipo de representantes genéricos são semelhantes aos efeitos de covariância e/contravariância no delegado comum de vinculação (consulte Variação de delegados (C# e Visual Basic)). Entretanto, a variação na ligação do delegado funciona com todos os tipos de representante, não apenas com os tipos de representante genérico com parâmetros de tipo variant. Além disso, a variação de delegar a vinculação permite um método para ser vinculado a qualquer delegado que tem mais restritivos tipos de parâmetro e um tipo de retorno de menos restritivo, enquanto que a atribuição do works representantes genéricos, somente se ambos delegar tipos são construídos com a mesma definição de tipo genérico.
O exemplo a seguir mostra os efeitos de variação delegado de ligação e a variação nos parâmetros de tipo genérico. O exemplo define uma hierarquia de tipo inclui três tipos, de menos derivado (Type1) para mais derivado (Type3). Variação do delegado comum de vinculação é usada para ligar um método com um tipo de parâmetro de Type1 e um tipo de retorno de Type3 a um delegado genérico com um tipo de parâmetro de Type2 e um tipo de retorno de Type2. O delegado genérico resultante é atribuído a outra variável cujo tipo de delegado genérico tem um parâmetro do tipo Type3 e um tipo de retorno de Type1, usando a covariância e/contravariância de parâmetros de tipo genérico. A atribuição a segunda requer o tipo de variável e o tipo de delegado ser construído a partir da mesma definição de tipo genérico, nesse caso, Func<T, TResult>.
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
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());
}
}
Voltar ao topo
Definindo a delegados e Interfaces genéricas da variante
Começando com o .NET Framework 4, Visual Basic e C# têm palavras-chave que permitem marcar os parâmetros de tipo genérico de interfaces e delega como covariant ou contravariant.
Observação
Começando com o.NET Framework versão 2.0, o common language runtime oferece suporte a anotações de variação em parâmetros de tipo genérico.Antes de .NET Framework 4, a única maneira de definir uma classe genérica que tenha essas anotações é usar o Microsoft intermediate language (MSIL) por compilar a classe com Ilasm. exe (Assembler MSIL) ou emitindo-lo em um assembly dinâmico.
Um parâmetro de tipo covariant é marcado com o out palavra-chave (Out palavra-chave em Visual Basic, + para o Assembler msil). Como o valor de retorno de um método que pertence a uma interface, ou o tipo de retorno de um delegado, você pode usar um parâmetro de tipo covariant. É possível usar um parâmetro de tipo covariant como uma restrição de tipo genérico para os métodos de interface.
Observação
Se um método de uma interface tem um parâmetro que é um tipo delegado genérico, um parâmetro de tipo covariant do tipo de interface pode ser usado para especificar um parâmetro de tipo contravariant do tipo delegado.
Um parâmetro de tipo contravariant é marcado com o in palavra-chave (In palavra-chave em Visual Basic, - para o Assembler msil). Você pode usar um parâmetro de tipo contravariant como o tipo de um parâmetro de um método que pertence a uma interface, ou o tipo de um parâmetro de um delegado. Você pode usar um parâmetro de tipo contravariant como uma restrição de tipo genérico para um método de interface.
Somente os tipos de interface e tipos de representante podem ter parâmetros de tipo variant. Um tipo de interface ou representante pode ter ambos covariant e contravariant digite parâmetros.
Visual Basic e C# não permitem a violar as regras para usar covariant e parâmetros de tipo contravariant, ou para adicionar a covariância e anotações / contravariância aos parâmetros de tipo de tipos diferentes de delegados e interfaces. O Assembler msil não executa essas verificações, mas um TypeLoadException é lançada se você tentar carregar um tipo que viola as regras.
Para obter informações e código de exemplo, consulte Variação em Interfaces genéricas (C# e Visual Basic).
Voltar ao topo
Lista de Interface genérica de variante e tipos Delegate
No .NET Framework 4, a seguinte interface e o delegado tipos têm covariant e/ou parâmetros de tipo contravariant.
Tipo |
Parâmetros de tipo covariant |
Parâmetros de tipo contravariant |
---|---|---|
Action<T>paraAction<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16> |
Sim |
|
Sim |
||
Sim |
Sim |
|
Sim |
||
Func<T, TResult>paraFunc<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, TResult> |
Sim |
Sim |
Sim |
||
Sim |
||
Sim |
||
Sim |
||
Sim |
||
Sim |
||
Sim |
||
Sim |
||
Sim |
||
Sim |
Voltar ao topo
Consulte também
Conceitos
Variação de delegados (C# e Visual Basic)