2021
04.27

다음 본문은 도서 이펙티브 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 데코레이터를 사용하여 가변성 오류를 컴파일 시점에 확인하자.

 

 

COMMENT