공변성 및 반공변성은 원래 지정된 것보다 더 파생된 형식(더 구체적인) 또는 덜 파생된 형식(덜 구체적인)을 사용하는 기능을 참조하는 용어입니다. 제네릭 형식 매개 변수는 공변성 및 반공변성을 지원하여 제네릭 형식을 할당하고 사용하는 유연성을 높입니다.
형식 시스템을 참조할 때 공변성, 반공변성 및 분산에는 다음과 같은 정의가 있습니다. 이 예제에서는 명명된 기본 클래스와 이름이 Base
지정된 Derived
파생 클래스를 가정합니다.
Covariance
원래 지정된 것보다 더 많은 파생 형식을 사용할 수 있습니다.
형식
IEnumerable<Derived>
의IEnumerable<Base>
변수에 인스턴스를 할당할 수 있습니다.Contravariance
원래 지정된 것보다 더 제네릭(덜 파생된) 형식을 사용할 수 있습니다.
형식
Action<Base>
의Action<Derived>
변수에 인스턴스를 할당할 수 있습니다.Invariance
원래 지정된 형식만 사용할 수 있음을 의미합니다. 고정 제네릭 형식 매개 변수는 공변성 또는 반공변성이 아닙니다.
형식
List<Base>
의List<Derived>
변수에 인스턴스를 할당하거나 그 반대로 할당할 수 없습니다.
공변 형식 매개 변수를 사용하면 다음 코드와 같이 일반적인 다형성처럼 보이는 할당을 만들 수 있습니다.
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
List<T> 클래스는 IEnumerable<T> 인터페이스를 구현하므로, List<Derived>
는 List(Of Derived)
(Visual Basic에서) IEnumerable<Derived>
를 구현합니다. 공변 형식 매개 변수는 나머지를 수행합니다.
반면에 반공변성은 직관에 어긋나는 것처럼 보입니다. 다음 예제에서는 Visual Basic에서 형식 Action<Base>
Action(Of Base)
의 대리자를 만든 다음 해당 대리자를 형식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())
거꾸로 보일 수 있지만, 이는 컴파일되고 실행되는 타입 안전 코드입니다. 람다 식은 할당된 대리자와 일치하므로 하나의 형식 Base
매개 변수를 사용하고 반환 값이 없는 메서드를 정의합니다. 형식 매개 변수 Action<Derived>
가 반공변성이므로, T
대리자의 생성된 대리자를 형식 Action<T> 변수에 할당할 수 있습니다. 매개 변수 형식을 지정하기 때문에 T
코드는 형식이 안전합니다. 형식의 대리자가 형식 Action<Base>
Action<Derived>
의 대리자인 것처럼 호출되는 경우 해당 인수는 형식 Derived
이어야 합니다. 메서드의 매개변수가 Base
형식이기 때문에 이 인수는 항상 기본 메서드에 안전하게 전달할 수 있습니다.
일반적으로 공변 형식 매개 변수는 대리자의 반환 형식으로 사용할 수 있으며 반공변 형식 매개 변수를 매개 변수 형식으로 사용할 수 있습니다. 인터페이스의 경우 공변 형식 매개 변수를 인터페이스 메서드의 반환 형식으로 사용할 수 있으며 반공변 형식 매개 변수를 인터페이스 메서드의 매개 변수 형식으로 사용할 수 있습니다.
공변성 및 반공변성(contravariance)을 통칭하여 분산이라고 합니다. 공변성 또는 반공변으로 표시되지 않은 제네릭 형식 매개 변수를 고정이라고 합니다. 공용 언어 런타임의 분산에 대한 팩트의 간략한 요약:
변형 형식 매개 변수는 제네릭 인터페이스 및 제네릭 대리자 형식으로 제한됩니다.
제네릭 인터페이스 또는 제네릭 대리자 형식에는 공변 및 반공변 형식 매개 변수가 모두 있을 수 있습니다.
변형성은 참조 형식에만 적용됩니다. 변형 형식 매개 변수에 값 형식을 지정하면 해당 형식 매개 변수는 결과로 생성된 형식에서 불변이 됩니다.
대리자 조합에는 분산이 적용되지 않습니다. 즉,
Action<Derived>
및Action<Base>
형식의 대리자 두 개(Action(Of Derived)
및Action(Of Base)
는 Visual Basic에서 사용)는 결과가 형식적으로 안전하더라도 두 번째 대리자를 첫 번째 대리자와 결합할 수 없습니다. 분산을 사용하면 두 번째 대리자를 형식 변수에 할당할 수 있지만 대리자는 형식Action<Derived>
이 정확히 일치하는 경우에만 결합할 수 있습니다.C# 9부터 공변 반환 형식이 지원됩니다. 재정의 메서드는 재정의하는 메서드를 더 파생된 반환 형식으로 선언할 수 있으며 재정의되는 읽기 전용 속성은 더 파생된 형식을 선언할 수 있습니다.
공변 형식 매개 변수를 사용하는 제네릭 인터페이스
여러 제네릭 인터페이스에는 공변 형식 매개 변수(예: IEnumerable<T>, IEnumerator<T>, IQueryable<T>및 )가 있습니다 IGrouping<TKey,TElement>. 이러한 인터페이스의 모든 형식 매개 변수는 공변성이므로 형식 매개 변수는 멤버의 반환 형식에만 사용됩니다.
다음 예제에서는 공변 형식 매개 변수를 보여 줍니다. 이 예제는 두 가지 형식을 정의합니다: Base
에는 PrintBases
이라는 정적 메서드가 있으며, 이 메서드는 IEnumerable<Base>
(IEnumerable(Of Base)
은 Visual Basic) 매개변수를 받아 요소를 출력합니다.
Derived
에서 상속됩니다 Base
. 이 예제에서는 빈 List<Derived>
형식(List(Of Derived)
Visual Basic)을 만들고 이 형식을 캐스팅하지 않고 형식 PrintBases
변수에 IEnumerable<Base>
전달하고 할당할 수 있음을 보여 줍니다.
List<T>는 단일 공변 형식 매개 변수가 있는 IEnumerable<T>을 구현합니다. 공변 형식 매개 변수는 인스턴스 IEnumerable<Derived>
를 대신 사용할 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
반공변 형식 매개 변수를 사용하는 제네릭 인터페이스
여러 제네릭 인터페이스에는 반공변 형식 매개 변수가 있습니다. 예: IComparer<T>, IComparable<T>및 IEqualityComparer<T>. 이러한 인터페이스에는 반공변 형식 매개 변수만 있으므로 형식 매개 변수는 인터페이스의 멤버에서 매개 변수 형식으로만 사용됩니다.
다음 예제에서는 반공변 형식 매개 변수를 보여 줍니다. 이 예제에서는 MustInherit
Visual Basic에서 추상 Shape
클래스를 정의하고, Area
속성을 포함합니다. 이 예제에서는 ShapeAreaComparer
을(를) 구현하는 IComparer<Shape>
클래스도 Visual Basic(IComparer(Of Shape)
)에서 정의합니다. 메서드의 IComparer<T>.Compare 구현은 속성 값을 Area
기반으로 하므로 ShapeAreaComparer
영역별로 개체를 정렬 Shape
하는 데 사용할 수 있습니다.
Circle
클래스는 Shape
를 상속하고 Area
를 재정의합니다. 이 예제는 SortedSet<T>의 Circle
개체를 생성하기 위해, IComparer<Circle>
(Visual Basic에서는 IComparer(Of Circle)
)을 받는 생성자를 사용합니다. 그러나 이 예제에서는 IComparer<Circle>
을(를) 전달하는 대신 ShapeAreaComparer
을(를) 구현하는 IComparer<Shape>
개체를 전달합니다. 이 예제에서는 제네릭 인터페이스의 형식 매개 변수가Shape
반공변성이므로 코드가 더 파생된 형식()의 비교자를 호출할 때 덜 파생된 형식(Circle
)의 IComparer<T> 비교자를 전달할 수 있습니다.
새 Circle
개체가 SortedSet<Circle>
에 추가되면, 새 요소를 기존 요소와 비교할 때마다 IComparer<Shape>.Compare
개체의 IComparer(Of Shape).Compare
메서드(ShapeAreaComparer
메서드는 Visual Basic의 경우)가 호출됩니다. 메서드(Shape
)의 매개 변수 형식은 전달되는 형식(Circle
)보다 덜 파생되므로 호출은 형식이 안전합니다. 반공변성을 사용하면 ShapeAreaComparer
에서 파생된 단일 형식의 컬렉션뿐만 아니라 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
변형 형식 매개 변수가 있는 제네릭 대리자
Func
제네릭 대리자, 예를 들어 Func<T,TResult>, 는 공변 반환 형식과 반공변 매개 변수 형식을 가집니다.
Action
같은 제네릭 대리자는 Action<T1,T2>와 같은 반공변 매개 변수 형식을 가집니다. 즉, 파생 매개 변수 형식이 더 많고(제네릭 대리자의 Func
경우) 파생 반환 형식이 적은 변수에 대리자를 할당할 수 있습니다.
비고
제네릭 대리자의 Func
마지막 제네릭 형식 매개 변수는 대리자 서명에 있는 반환 값의 형식을 지정합니다. 다른 제네릭 형식 매개 변수는 반공변(out
키워드)인 반면 공변성(in
키워드)입니다.
다음 코드에서는 이를 보여 줍니다. 코드의 첫 번째 부분은 Base
라는 이름의 클래스, Derived
를 상속하는 Base
라는 클래스, 그리고 static
라는 메서드(Shared
는 Visual Basic에서의 명칭)를 포함하는 다른 클래스를 정의합니다. 메서드는 인스턴스 Base
를 가져와서 인스턴스를 Derived
반환합니다. 인수가 인스턴스 Derived
MyMethod
인 경우 해당 인수를 반환하고 인수가 인스턴스 Base
MyMethod
인 경우 .의 Derived
새 인스턴스를 반환합니다. 예제에서는 Main()
(Visual Basic에서) 나타내는 인스턴스 Func<Base, Derived>
를 만들고 변수Func(Of Base, Derived)
에 저장합니다MyMethod
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
두 번째 코드 조각에서는 반환 형식이 공변성이므로 대리자를 형식 Func<Base, Base>
변수(Func(Of Base, Base)
Visual Basic의 경우)에 할당할 수 있음을 보여줍니다.
// 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())
세 번째 코드 조각에서는 매개 변수 형식이 반공변이므로 대리자를 Visual Basic의 형식 Func<Derived, Derived>
Func(Of Derived, Derived)
변수에 할당할 수 있음을 보여줍니다.
// 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())
코드의 마지막 부분에서는 반공변성 매개 변수 형식과 공변성 반환 형식의 효과를 결합하여 대리자를 형식 Func<Derived, Base>
변수(Func(Of Derived, Base)
Visual Basic의 경우)에 할당할 수 있음을 보여 줍니다.
// 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())
제네릭이 아닌 대리자에서의 변동
앞의 코드에서 MyMethod
의 시그니처는 생성된 제네릭 대리자 Func<Base, Derived>
의 시그니처와 정확히 일치합니다 (Func(Of Base, Derived)
in Visual Basic). 이 예제에서는 모든 대리자 형식이 제네릭 대리자 형식에서 생성되는 한 파생된 매개 변수 형식이 더 많고 파생된 반환 형식이 적은 변수 또는 메서드 매개 변수에 이 제네릭 대리 Func<T,TResult>자를 저장할 수 있음을 보여 줍니다.
이것은 중요한 점입니다. 제네릭 대리자의 형식 매개 변수에서 공변성 및 반공변성의 효과는 일반 대리자 바인딩에서 공변성 및 반공변성의 영향과 유사합니다( 대리자의 차이(C#) 및 대리자의 차이 참조(Visual Basic)) 그러나 대리자 바인딩의 분산은 변형 형식 매개 변수가 있는 제네릭 대리자 형식뿐만 아니라 모든 대리자 형식에서 작동합니다. 또한 대리자 바인딩의 분산을 사용하면 더 제한적인 매개 변수 형식과 덜 제한적인 반환 형식이 있는 대리자에서 메서드를 바인딩할 수 있지만 제네릭 대리자의 할당은 두 대리자 형식이 동일한 제네릭 형식 정의에서 생성된 경우에만 작동합니다.
다음 예제에서는 대리자 바인딩에서 분산의 결합된 효과와 제네릭 형식 매개 변수의 분산을 보여 줍니다. 이 예제에서는 최소 파생()에서 가장Type1
많이 파생된(Type3
)에 이르는 세 가지 형식을 포함하는 형식 계층 구조를 정의합니다. 일반 대리자 바인딩의 분산은 매개 변수 형식과 반환 형식이 Type1
있는 메서드를 매개 변수 Type3
형식과 반환 Type2
형식이 Type2
있는 제네릭 대리자로 바인딩하는 데 사용됩니다. 그런 다음 제네릭 형식 매개 변수의 공변성과 반공변성을 사용하여 형식이 Type3
이고 반환 형식이 Type1
인 제네릭 대리자가 다른 변수에 할당됩니다. 두 번째 할당에서는 변수 형식과 대리자 형식을 모두 동일한 제네릭 형식 정의(이 경우 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
변형 제네릭 인터페이스 및 대리자 정의
Visual Basic 및 C#에는 인터페이스 및 대리자의 제네릭 형식 매개 변수를 공변 또는 반공변으로 표시할 수 있는 키워드가 있습니다.
공변 형식 매개 변수는 키워드(out
Visual Basic의 Out
키워드)로 표시됩니다. 공변 형식 매개 변수를 인터페이스에 속하는 메서드의 반환 값으로 사용하거나 대리자의 반환 형식으로 사용할 수 있습니다. 공변 형식 매개 변수를 인터페이스 메서드에 대한 제네릭 형식 제약 조건으로 사용할 수 없습니다.
비고
인터페이스의 메서드에 제네릭 대리자 형식인 매개 변수가 있는 경우 인터페이스 형식의 공변 형식 매개 변수를 사용하여 대리자 형식의 반공변 형식 매개 변수를 지정할 수 있습니다.
반공변 형식 매개 변수는 키워드(in
Visual Basic의 In
키워드)로 표시됩니다. 반공변 형식 매개 변수를 인터페이스에 속하는 메서드의 매개 변수 형식 또는 대리자의 매개 변수 형식으로 사용할 수 있습니다. 반공변성 형식 매개 변수를 인터페이스 메서드에 대한 제네릭 형식 제약 조건으로 사용할 수 있습니다.
인터페이스 형식 및 대리자 형식만 변형 형식 매개 변수를 가질 수 있습니다. 인터페이스 또는 대리자 형식에는 공변 및 반공변 형식 매개 변수가 모두 있을 수 있습니다.
Visual Basic 및 C#에서는 공변 및 반공변성 형식 매개 변수를 사용하는 규칙을 위반하거나 인터페이스 및 대리자가 아닌 형식의 형식 매개 변수에 공변성 및 반공변성 주석을 추가할 수 없습니다.
자세한 내용 및 예제 코드는 제네릭 인터페이스의 분산(C#) 및 제네릭 인터페이스의 분산(Visual Basic)을 참조하세요.
형식 목록
다음 인터페이스 및 대리자 형식에는 공변 및/또는 반공변 형식 매개 변수가 있습니다.
참고하십시오
.NET