공변성과 반공변성 (컴퓨터 과학)

프로그래밍 언어의 **공변성**(Covariance)과 **반공변성**(Contravariance)은 프로그래밍 언어가 타입 생성자(type constructor)에 있어 서브타입을 처리하는 방법을 나타내는 것으로, 더 복잡한 타입간의 서브타입 관계가 타입 사이의 서브타입 관계에 따라 정의되거나, 이를 배반해 정의됨을 가리킨다. 예를 들어, 목록과 함수에 대해 생각해 보자. 고양이는 동물의 서브타입이다. 그렇다면 고양이 목록은 동물 목록과 어떻게 연관되어야 할까? 또는 고양이를 인자로 받는 함수가 동물을 인자로 받는 함수와 어떻게 연관되어야 할까? 이는 변성(Variance)으로 설명될 수 있다. OCaml 언어에서 고양이 목록은 동물 목록의 서브타입이다. 목록 타입 생성자가 공변하기 때문이다. 그러나 동물 -> 문자열 함수는 고양이 -> 문자열 함수의 서브타입이다. 함수 타입 생성자는 인자 타입에 있어 반공변하기 때문이다.
# 정의
프로그래밍 언어의 자료형 체계에서 I를 타입 매개변수 하나를 받는 타입 생성자라고 할 때, * **공변**(Covariance)할 때, S가 T의 서브타입이라면(S }는 I{}의 서브타입이다. (I{} }) * **반공변**(Contravariance)할 때, S가 T의 서브타입이라면(S }는 I{}의 서브타입이다. (I{} }) * **이변**(Bivariance)할 때, 공변하면서도 반공변한다. * **무공변**(Invariant)하다면 공변하지도 반공변하지도 않는다.
# 제네릭에서
‘공변성(Covariance)’과 ‘반공변성(Contravariance)’은 원래 지정된 것보다 더 많이 파생되거나(더 구체적인) 더 적게 파생된 형식(덜 구체적인)을 사용할 수 있는 능력을 지칭하는 용어입니다. 제네릭 형식 매개 변수는 더욱 유연하게 제네릭 형식을 할당하고 사용할 수 있도록 공변성과 반공변성을 지원합니다. 형식 시스템을 참조할 때 공변성(Covariance), 반공변성(Contravariance), 불변성의 정의는 다음과 같습니다. 이 예제에서는 `Base` 라는 기본 클래스와 `Derived`라는 파생 클래스가 있는 것으로 가정합니다. * `Covariance` 원래 지정된 것보다 더 많이 파생된 형식을 사용할 수 있습니다. `IEnumerable`의 인스턴스를 `IEnumerable` 형식의 변수에 할당할 수 있습니다. * `Contravariance` 원래 지정된 것보다 더 제네릭한(덜 파생적인) 형식을 사용할 수 있습니다. `Action`의 인스턴스를 `Action` 형식의 변수에 할당할 수 있습니다. * `Invariance` 원래 지정된 형식만 사용할 수 있음을 의미합니다. 고정 제네릭 형식 매개 변수는 공변(covariant) 및 반공변(contravariant)이 아닙니다. `List`의 인스턴스를 `List` 형식의 변수에 할당할 수 없고, 그 반대로도 할당할 수 없습니다. 다음 코드에 표시된 것처럼, 공변 형식 매개 변수를 사용하여 일반적인 다형성과 매우 비슷한 할당을 수행할 수 있습니다. C# IEnumerable d = new List(); IEnumerable b = d; List 클래스는 IEnumerable 인터페이스를 구현하므로 `List` (Visual Basic의 경우`List(Of Derived)` )는 `IEnumerable`를 구현합니다. 나머지 작업은 공변 형식 매개 변수가 처리합니다. 그러나 반공변성은 직관적으로 이해하기가 어려울 수 있습니다. 다음 예제에서는 `Action`(Visual Basic의 경우 `Action(Of Base)`) 형식의 대리자를 만든 다음 이 대리자를 `Action` 형식의 변수에 할당합니다. C# `Action b = (target) => { Console.WriteLine(target.GetType().Name); }; Action d = b; d(new Derived());` 이는 순서가 뒤바뀐 것처럼 보이지만 실제로는 형식이 안전한 코드로서 문제 없이 컴파일 및 실행됩니다. 람다 식이 할당된 대리자와 일치하므로 `Base` 형식의 매개 변수 하나를 사용하고 반환 값이 없는 메서드를 정의할 수 있습니다. 결과로 얻은 대리자를 `Action` 형식의 변수에 할당할 수 있습니다. `T` 대리자의 형식 매개 변수 Action 가 반공변성을 지니기 때문입니다. `T` 에서 매개 변수 형식을 지정하므로 이는 형식이 안전한 코드입니다. `Action` 형식의 대리자를 `Action`형식의 대리자인 것처럼 호출하는 경우 해당 인수가 `Derived`형식이어야 합니다. 메서드의 매개 변수가 `Base`형식이므로 이 인수를 내부 메서드에 항상 안전하게 전달할 수 있습니다. 일반적으로 공변 형식 매개 변수는 대리자의 반환 형식으로 사용할 수 있으며, 반공변 형식 매개 변수는 매개 변수 형식으로 사용할 수 있습니다. 인터페이스의 경우 공변 형식 매개 변수는 인터페이스 메서드의 반환 형식으로 사용할 수 있으며, 반공변 형식 매개 변수는 인터페이스 메서드의 매개 변수 형식으로 사용할 수 있습니다. 공변성(Covariance) 및 반공변성(Contravariance)을 전체적으로 *차이*라고 합니다. 공변 또는 반공변 여부가 표시되지 않은 제네릭 형식 매개 변수를 *고정(invariant)*매개 변수라고 합니다. 다음은 공용 언어 런타임의 가변성에 대한 간략한 설명입니다. * Variant 형식 매개 변수가 제네릭 인터페이스와 제네릭 대리자 형식에만 사용됩니다. * 제네릭 인터페이스 또는 제네릭 대리자 형식은 공변 및 반공변 형식 매개 변수를 둘 다 가질 수 있습니다. * 가변성은 참조 형식에만 적용되므로 variant 형식 매개 변수에 대한 값 형식을 지정하면 이 형식 매개 변수는 결과로 생성되는 형식에 대해 invariant가 됩니다. * 대리자 결합에는 가변성이 적용되지 않습니다. 즉, 두 대리자의 형식이 `Action` 및 `Action` (Visual Basic의 경우`Action(Of Derived)` 및 `Action(Of Base)` )라고 할 때 결과의 형식이 안전하더라도 둘째 대리자를 첫째 대리자와 결합할 수 없습니다. 가변성을 통해 둘째 대리자를 `Action`형식의 변수에 할당할 수 있지만, 대리자를 결합하기 위해서는 해당 형식이 정확하게 일치해야 합니다. * C# 9부터 공변 반환 형식이 지원됩니다. 재정의 메서드는 재정의하는 메서드의 더 파생된 반환 형식으로 선언할 수 있으며, 재정의 읽기 전용 속성은 더 파생된 형식을 선언할 수 있습니다.
# 공변 형식 매개 변수가 있는 제네릭 인터페이스
여러 제네릭 인터페이스에 공변 형식 매개 변수가 있습니다(예: IEnumerable, IEnumerator, IQueryable, IGrouping). 이러한 인터페이스의 모든 형식 매개 변수는 공변성이므로, 형식 매개 변수는 멤버의 반환 형식에만 사용됩니다. 다음 예제에서는 공변 형식 매개 변수를 사용하는 방법을 보여 줍니다. 이 예제에서는 두 가지 형식을 정의합니다. 그중 하나인 `Base` 에는 `PrintBases` (Visual Basic의 경우 `IEnumerable` )를 취하고 요소를 출력하는`IEnumerable(Of Base)` 라는 정적 메서드가 있습니다. `Derived`는 `Base`에서 상속됩니다. 이 예제에서는 빈 `List`(Visual Basic의 경우 `List(Of Derived)`)를 만든 다음 이 형식을 캐스팅하지 않은 채 `PrintBases`에 전달하고 `IEnumerable` 형식의 변수에 할당하는 방법을 보여 줍니다. List 은 단일 공변 형식 매개 변수가 있는 IEnumerable을 구현합니다. `IEnumerable`의 인스턴스를 `IEnumerable` 대신 사용할 수 있는 것은 공변 형식 매개 변수 때문입니다. C# `using System; using System.Collections.Generic; class Base { public static void PrintBases(IEnumerable bases) { foreach(Base b in bases) { Console.WriteLine(b); } } } class Derived : Base { public static void Main() { List dlist = new List(); Derived.PrintBases(dlist); IEnumerable bIEnum = dlist; } }`
# 반공변 형식 매개 변수가 있는 제네릭 인터페이스
IComparer, IComparable, IEqualityComparer 같은 여러 제네릭 인터페이스에서 반공변 형식 매개 변수를 사용합니다. 이러한 인터페이스에는 반공변 형식 매개 변수만 있으므로 형식 매개 변수가 인터페이스 멤버의 매개 변수 형식으로만 사용됩니다. 다음 예제에서는 반공변 형식 매개 변수를 사용하는 방법을 보여 줍니다. 이 예제에서는`MustInherit` 속성을 사용하여 추상(Visual Basic의 경우 `Shape` ) `Area` 클래스를 정의합니다. 이 예제에서는 또한 `ShapeAreaComparer` (Visual Basic의 경우 `IComparer` )를 구현하는`IComparer(Of Shape)` 클래스를 정의합니다. IComparer.Compare 메서드 구현은 `Area` 속성의 값을 기반으로 하므로 `ShapeAreaComparer` 를 사용하여 `Shape` 개체를 영역별로 정렬할 수 있습니다. `Circle` 클래스는 `Shape` 를 상속하고 `Area`를 재정의합니다. 이 예제에서는 SortedSet (Visual Basic의 경우 `Circle` )을 받아 들이는 생성자를 사용하여 `IComparer` 개체의`IComparer(Of Circle)` 을 만듭니다. 하지만 이 예제에서는 `IComparer`을 전달하는 대신 `ShapeAreaComparer` 를 구현하는 `IComparer`개체를 전달합니다. 이 예제에서는`Shape`제네릭 인터페이스의 형식 매개 변수가 반공변 형식이기 때문에 코드에서 더 많이 파생된 형식(`Circle`)의 비교자를 호출하더라도 더 적게 파생된 형식( IComparer )의 비교자를 전달할 수 있습니다. 새 `Circle` 개체를 `SortedSet`에 추가하면 기존 요소에 대해 새 요소를 비교할 때마다 `IComparer.Compare` 개체의`IComparer(Of Shape).Compare` 메서드(Visual Basic의 경우 `ShapeAreaComparer` 메서드)가 호출됩니다. 메서드의 매개 변수 형식(`Shape`)은 전달되는 형식(`Circle`)보다 더 적게 파생되었으므로 호출에서 형식 안전성을 유지합니다. 반공변성은 `ShapeAreaComparer`가 `Shape`에서 파생되는 혼합된 형식 컬렉션뿐만 아니라 모든 단일 형식의 컬렉션을 정렬할 수 있게 해줍니다. C# `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 { int IComparer.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, // even though the constructor for SortedSet expects // IComparer, because type parameter T of IComparer is // contravariant. SortedSet circlesByArea = new SortedSet(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 */`
Variant 형식 매개 변수가 있는 제네릭 대리자 == Func 같은 `Func` 제네릭 대리자가 공변 반환 형식과 반공변 매개 변수 형식을 사용합니다. `Action` 같은 Action제네릭 대리자는 반공변 매개 변수 형식을 사용합니다. 즉, 더 많이 파생된 매개 변수 형식과 더 적게 파생된 반환 형식(`Func` 제네릭 대리자의 경우)이 있는 변수에 대리자를 할당할 수 있습니다. 참고 `Func` 제네릭 대리자의 마지막 제네릭 형식 매개 변수는 대리자 시그니처에 반환 값의 형식을 지정합니다. 이 제네릭 형식 매개 변수는 공변(`out` 키워드)인 반면 다른 한 제네릭 형식 매개 변수는 반공변(`in` 키워드)입니다. 다음 코드가 이를 보여줍니다. 코드의 첫 부분에서는 `Base`라는 클래스, `Derived` 를 상속하는 `Base`라는 클래스 및 `static` 라는`Shared` 메서드(Visual Basic의 경우 `MyMethod`)가 있는 또 다른 클래스를 정의합니다. 이 메서드는 `Base`의 인스턴스를 사용하고 `Derived`의 인스턴스를 반환합니다. 인수가 `Derived`의 인스턴스이면 `MyMethod`에서 이 인스턴스를 반환하고, 인수가 `Base`의 인스턴스이면 `MyMethod`에서 `Derived`의 새 인스턴스를 반환합니다. 이 예제에서는 `Main()` 내에서 `Func`를 나타내고 이를 `Func(Of Base, Derived)` 변수에 저장하는 `MyMethod`(Visual Basic의 경우 `f1`)의 인스턴스를 만듭니다. C# `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 f1 = MyMethod;` 코드의 둘째 부분에서는 반환 형식이 공변적이기 때문에 `Func` (Visual Basic의 경우`Func(Of Base, Base)` ) 형식의 변수에 대리자를 할당할 수 있음을 보여 줍니다. C# `// Covariant return type. Func f2 = f1; Base b2 = f2(new Base());` 코드의 셋째 부분에서는 매개 변수 형식이 반공변적이기 때문에 `Func`(Visual Basic의 경우 `Func(Of Derived, Derived)`) 형식의 변수에 대리자를 할당할 수 있음을 보여 줍니다. C# `// Contravariant parameter type. Func f3 = f1; Derived d3 = f3(new Derived());` 코드의 마지막 부분에서는 반공변 매개 변수 형식과 공변 반환 형식의 효과를 결합하여 `Func` (Visual Basic의 경우`Func(Of Derived, Base)` ) 형식의 변수에 대리자를 할당할 수 있음을 보여 줍니다. C# `// Covariant return type and contravariant parameter type. Func f4 = f1; Base b4 = f4(new Derived());`
## 제네릭이 아닌 대리자의 가변성
위의 코드에서 `MyMethod`의 시그니처는 생성된 제네릭 대리자 `Func`(Visual Basic의 경우 `Func(Of Base, Derived)`)의 시그니처와 정확하게 일치합니다. 이 예제에서는 모든 대리자 형식이 제네릭 대리자 형식 Func에서 생성되는 한 더 많이 파생된 매개 변수 형식과 더 적게 파생된 반환 형식이 있는 메서드 매개 변수 또는 변수에 이 제네릭 대리자를 저장할 수 있음을 보여 줍니다. 다음은 중요한 내용입니다. 제네릭 대리자의 형식 매개 변수가 지니는 공변성 및 반공변성의 효과는 일반적인 대리자 바인딩이 지니는 공변성 및 반의 효과와 비슷합니다. 자세한 내용은 대리자의 가변성(C#) 및 대리자의 가변성(Visual Basic)을 참조하세요. 그러나 대리자 바인딩의 가변성은 variant 형식 매개 변수가 있는 제네릭 대리자 형식뿐 아니라 모든 대리자 형식에서 작동합니다. 또한 대리자 바인딩이 가변성을 지니면 더 제한적인 매개 변수 형식과 덜 제한적인 반환 형식이 있는 임의의 대리자에 메서드를 바인딩할 수 있는 반면, 제네릭 대리자의 할당은 두 대리자 형식이 모두 동일한 제네릭 형식 정의에서 생성되는 경우에만 작동합니다. 다음 예제에서는 대리자 바인딩의 가변성과 제네릭 형식 매개 변수의 가변성이 결합된 효과를 보여 줍니다. 이 예제에서는 가장 적게 파생된 형식(`Type1`)부터 가장 많이 파생된 형식(`Type3`)까지 세 개의 형식을 포함하는 형식 계층을 정의합니다. 일반적인 대리자 바인딩의 가변성은 `Type1` 의 매개 변수 형식과 `Type3` 의 반환 형식이 있는 제네릭 대리자에 `Type2` 의 매개 변수 형식과 `Type2`의 반환 형식이 있는 메서드를 바인딩하는 데 사용됩니다. 결과로 생성된 제네릭 대리자는 제네릭 형식 매개 변수의 공 분산과 반공 분산을 사용하여 제네릭 대리자의 매개 변수 형식과 반환 형식이 각각 `Type3`과 `Type1`인 다른 변수에 할당됩니다. 이 두 번째 할당을 수행하려면 변수 형식과 대리자 형식이 둘 다 같은 제네릭 형식 정의(이 예제의 경우 Func)에서 생성된 것이어야 합니다. C# `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 f1 = MyMethod; // Covariant return type and contravariant parameter type. Func f2 = f1; Type1 t1 = f2(new Type3()); } }`
# Variant 제네릭 인터페이스 및 대리자 정의
Visual Basic과 C#에서 인터페이스와 대리자의 제네릭 형식 매개 변수를 공변 또는 반공변으로 표시하는 데 사용할 수 있는 키워드를 제공합니다. 공변 형식 매개 변수는 `out` 키워드(Visual Basic의 `Out` 키워드)로 표시됩니다. 공변 형식 매개 변수는 인터페이스에 속하는 메서드의 반환 값이나 대리자의 반환 형식으로 사용할 수 있지만 인터페이스 메서드에 대한 제네릭 형식 제약 조건으로는 사용할 수 없습니다. 참고 인터페이스의 메서드에 제네릭 대리자 형식인 매개 변수가 있으면 인터페이스 형식의 공변 형식 매개 변수를 사용하여 대리자 형식의 반공변 형식 매개 변수를 지정할 수 있습니다. 반공변 형식 매개 변수는 `in` 키워드(Visual Basic의 `In` 키워드)로 표시됩니다. 반공변 형식 매개 변수는 인터페이스에 속하는 메서드의 매개 변수 형식이나 대리자의 매개 변수 형식으로 사용할 수 있으며 인터페이스 메서드의 제네릭 형식 제약 조건으로도 사용할 수 있습니다. variant 형식 매개 변수를 사용할 수 있는 것은 인터페이스 형식과 대리자 형식뿐입니다. 인터페이스 형식이나 대리자 형식은 공변 및 반공변 형식 매개 변수를 둘 다 가질 수 있습니다. Visual Basic과 C#에서는 공변 및 반공변 형식 매개 변수의 사용 규칙을 위반하거나 인터페이스와 대리자가 아닌 다른 형식의 형식 매개 변수에 공 분산 및 반공 분산 주석을 추가할 수 없습니다. 자세한 내용과 예제 코드는 제네릭 인터페이스의 가변성(C#) 및 제네릭 인터페이스의 가변성(Visual Basic)을 참조하세요.
# 형식 목록
다음 인터페이스 및 대리자 형식에서 공변 및 반공변 형식 매개 변수를 사용합니다. {| class="wikitable" !Type !공변 형식 매개 변수 !반공변 형식 매개 변수 |- |Action \- Action | |예 |- |Comparison | |네 |- |Converter |네 |네 |- |Func |예 | |- |Func \- Func |예 |네 |- |IComparable | |네 |- |Predicate | |네 |- |IComparer | |네 |- |IEnumerable |네 | |- |IEnumerator |네 | |- |IEqualityComparer | |네 |- |IGrouping |네 | |- |IOrderedEnumerable |네 | |- |IOrderedQueryable |네 | |- |IQueryable |예 | |}
# 참고 항목
* 공변성(Covariance) 및 반공변성(Contravariance)(C#) * 공변성(Covariance) 및 반공변성(Contravariance)(Visual Basic) * 대리자의 가변성(C#) * 대리자의 가변성(Visual Basic)
# 함수 타입에서
빈 문단
# 객체 지향 언어의 상속에서
서브클래스가 슈퍼클래스의 메소드를 오버라이드 할 경우, 컴파일러는 메소드의 타입이 올바른지 확인해 타입 안전(type safe)을 보장해야만 한다. 무공변만을 허용하는 일부 언어에서는 인자 타입과 반환 타입 모두 슈퍼클래스의 인자 타입 그리고 반환 타입과 정확히 같아야 한다. 그러나 특별한 규칙을 따른다면, 오버라이드된 메소드가 더 유연한 타입을 갖도록 허용하면서도 타입 안전을 보장할 수 있다. 함수 유형에 대한 일반적인 서브타이핑 규칙에 따르면, 오버라이드된 메소드는 더욱 구체적인 타입을 반환해야 하고(반환 타입 공변), 더욱 일반적인 타입을 허용해야만 한다.(인자 타입 반공변) ClassB가 ClassA를 상속하는 서브클래스라고 할 때, 가능한 조합들을 UML로 나타내면 다음과 같다. 파일:Vererbung 인자/반환 타입과 서브타이핑 오버라이딩된 메소드의 시그니처는 변하지 않았다. -- 타입 안전하다. 반환 타입*. 서브타이핑 관계는 CalssA와 ClassB 사이의 관계와 같은 방향이다. -- 타입 안전하다. 인자 타입*. 서브타이핑 관계는 ClassA 그리고 ClassB 사이의 관계와 반대 방향이다. -- 타입 안전하다. 인자 타입''. -- 이 경우에는 타입 안전하지 않다. 객체지향 프로그래밍 언어들의 메소드 오버라이딩 규칙을 요약하면 다음과 같다. {| class="wikitable" border="1" |- ! 프로그래밍 언어 !! 인자 타입 !! 반환 타입 |- | C++ (1998년부터), 자바 (J2SE 5.0부터), D || 무공변 || 공변 |- | C# || 무공변 || 공변 (C# 9부터 - 이전까지는 무공변) |- | Scala || 반공변 || 공변 |- | Eiffel || 공변 || 공변 |}
# 참조문헌
* issn=0164-0925 위키데이터 속성 추적 분류:다형성 (컴퓨터 과학) 분류:유형 이론 분류:객체 지향 프로그래밍 분류:함수형 프로그래밍 분류:제네릭 프로그래밍