다음 본문은 도서 이펙티브 C# (빌 와그너)에서 나오는 주제를 다룹니다.
타입의 가변성(variance), 즉 공변(convariance)과 반공변(contravariance)은 특정 타입의 객체를 다른 타입 객체로 변환할 수 있는 성격을 말한다.
공변과 반공변
- 타입 매개변수로 주어지는 타입이 상호 호환 가능할 경우 이를 이용하는 제네릭 타입도 호환 가능함을 추론하는 기능.
- IEnumerable<Object> 를 매개변수로 취하는 메서드는 IEnumerable<MyType> 객체도 받아들일 수 있어야 한다.
- IEnumerable<MyType>객체를 반환하는 메서드는 이 반환 객체를 IEnumerable<Object> 객체에 할당할 수 있어야 한다.
공변 (out)
- X -> Y일 때, C<T>를 C<X> -> C<Y>인 경우 공변이다.
- out 데코레이터를 사용하여 정의할 수 있다.
반공변 (in)
- Y -> X일 때, C<T>를 C<X> -> C<Y>인 경우 반공변이다.
- in 데코레이터를 사용하여 정의할 수 있다.
out 키워드
아래와 같이 상속관계에 있는 클래스들이 있다고 해보자.
public abstract class Parent : IComparable<Parent>
{
public double Mass { get; set; }
public string Name { get; set; }
public int CompareTo(Parent other) => 0;
}
public class A : Parent{ }
public class B : Parent { }
ConvariantGeneric 메서드는 매개변수로 IEnumerable<Parent>를 취하지만 List<A>로도 호출이 가능하다.
public class MyTest
{
public void MyMethod()
{
List<A> temp = new List<A>();
CovariantGeneric(temp);
}
public static void CovariantGeneric(IEnumerable<Parent> baseItems)
{
foreach (var item in baseItems)
{
Console.WriteLine($"{item.Name} {item.Mass}");
}
}
}
이것이 가능한 이유는 IEnumerable<T> 인터페이스가 정의될 때 T를 out키워드로 선언했기 때문이다.
public interface IEnumerable<out T> : IEnumerable
{
IEnumerator<T> GetEnumerator();
}
이는 타입 매개변수 T를 출력 위치에서만 사용하겠다고 컴파일러에게 알려주는 것이다.
(반환 값, 속성의 get 접근자, 델리게이트 일부 위치)
내용을 조회는 하지만 수정을 하지 않음으로 공변을 지원한다.
IList 컬렉션을 매개변수로 받아서 수정하는 경우 이 인자는 공변이 아니라 불변(invariant)이 된다.
public static void InvariantGeneric(IList<Parent> baseItems)
{
baseItems[0] = new A { Name = "temp", Mass = 3 };
}
IList<T>를 선언할 때 T에 대해서는 in/out 데코레이터를 사용하지 않았기 때문에, 타입을 정확하게 일치시켜야 한다.
in 키워드
- IComparable은 in키워드를 사용하여 반공변으로 선언되어있다.
- 이는 IComparable을 사용하여 A와 B를 비교할 수 있음을 의미한다.
public interface IComparable<in T>
{
int CompareTo(T other);
}
- 하지만 IEquatable<T>는 불변이다. A와 B는 같음을 확인할 수 없음을 의미한다.
public interface IEquatable<T>
{
bool Equals(T other);
}
결론
- 제네릭 인터페이스와 제네릭 델리게이트를 정의할 때는 in이나 out 데코레이터를 사용하여 가변성 오류를 컴파일 시점에 확인하자.
'🌍 C# Study > 이펙티브 C#' 카테고리의 다른 글
[24] 베이스 클래스나 인터페이스에 대해서 제네릭을 특화하지 말라 (0) | 2021.04.27 |
---|---|
[23] 타입 매개변수에 대해 메서드 제약 조건을 설정하려면 델리게이트를 활용하라 (0) | 2021.04.27 |
[21] 타입 매개변수가 IDisposable을 구현한 경우를 대비하여 제네릭 클래스를 작성하라 (0) | 2021.04.27 |
[20] IComparable<T>와 IComparer<T>를 이용하여 객체의 선후 관계를 정의하라 (0) | 2021.04.26 |
[19] 런타임에 타입을 확인하여 최적의 알고리즘을 사용하라 (0) | 2021.04.26 |