본문 바로가기

Develop/.NET 가이드

[C#] Covariance 공변성 및 Contravariance 반공변성

반응형

Cvariance 공변성 및 Contravariance 반공변성
Covariance & Contravariance

공변성 및 반공변성

마이크로소프트의 공식문서에서 공변성과 반공변성에 대한 개념을 소개하고는 있지만, 저는 이해하기가 너무 어려웠습니다. 저에게는 글 구성이 너무 난해하고 배경 설명이 너무 부족했습니다. 그래서 저와 같은 어려움을 겪는 개발자를 위해 제 나름대로 이해한 내용을 정리하여 기록해봅니다. 모두에게 유익한 글이 되기를...

상속 관계에 있는 클래스는 서로 형변환이 가능합니다. 다만 형변환을 하게 되면 정의된 범위와 구현된 범위가 달라지게 됩니다. 그래서 객체 간에 형변환을 하게 되면 예상치 못한 예외가 발생하곤 합니다. 특히 공변성과 반공변성이라는 개념은 이러한 형변환으로 파생되는 예외와 관련된 개념입니다.

C# 언어가 발전하면서 .NET 내부적으로 제네릭 타입과 대리자 기능을 활발하게 사용되었습니다. 이는 곧 형변환이 자주 일어나게 되는 결과를 가져오게 됩니다. 문제는 형변환으로 발생 가능한 모든 예외를 컴파일할 때 검증할 수 없었습니다. 그리고 복잡하게 사용되는 제네릭 타입과 대리자를 따라가며 예외 사항을 모두 확인하기란 너무 어렵습니다. 그래서 형변환으로 발생 가능한 모든 예외를 컴파일할 때 검증하기 위해 형변환하는 형태를 공변성반공변성으로 분류하게 됩니다. 형변환 형태를 구분하여 처리함으로 컴파일로 더 많은 예외를 검증할 수 있게 하여 코드 생산성을 증진시키는 겁니다. C# 에서는 inout 이라는 키워드를 통해 허용되는 형변환 형태를 구분합니다.

결국 공변성과 반공변성은 형변환의 형태를 분류하기 위한 개념이고, 이 개념을 언어로 구현하기 위해 C# 에서는 inout 키워드를 활용합니다.

공변성과 반공변성은 쉽게 설명하면,

  • 공변성 자신 또는 자식 객체에 해당하는 타입을 부모 변수에 해당하는 타입으로 형변환. out 키워드로 지정.
  • 반공변성 자신 또는 부모 객체에 해당하는 타입을 자식 변수에 해당하는 타입으로 형변환. in 키워드로 지정.
  • 불변성 자신으로만 형변환

일반적인 개발자라면 공변성과 반공변성이 필요한 이유를 납득하기 매우 어려우실 수 있습니다. 왜냐하면 지금 설명하는 내용이 미래에 어떤 오류를 발생시킬지 머리 속으로 상상하기가 매우 어렵기 때문이죠. 또한 이미 언어 수준에서 예외가 발생하지 않도록 세심하게 배려해주고 있기에 지금에 와서 형변환과 관련된 복잡한 예외를 실제로 마주칠 일도 거의 없어졌습니다. 그러므로 '공변성과 반공변성이라는 어려운 단어가 개발에서는 이러한 뜻이구나'라고 이해하고 코드에 등장하면 당황하지 않으실 정도로만 이해해도 충분하다 생각합니다.

하지만 지식에 목마른 개발자를 위해 더 자세히 설명하자면,

공변성 형변환

IEnumerable<string> strings = new List<string>();  
// object를 상속하는 자식 객체 string 이 object 부모 변수에 할당됩니다.
IEnumerable<object> objects = strings;  
static string GetString() { return ""; }  
static void Test()  
{  
    // 반환타입이 object 로 string 의 부모인 대리자에 반환타입이 string 으로 obejct의 자식인 함수를 할당합니다.  
    Func<object> del = GetString;  
}  

자식 객체가 부모 변수로 할당되는 형변환
C# 키워드는 out

공변 제네릭 인터페이스

// Covariant interface.
interface ICovariant<out R> { }

// Extending covariant interface.
interface IExtCovariant<out R> : ICovariant<R> { }

// Implementing covariant interface.
class Sample<R> : ICovariant<R> { }

class Program
{
    static void Test()
    {
        ICovariant<Object> iobj = new Sample<Object>();
        ICovariant<String> istr = new Sample<String>();

        // You can assign istr to iobj because
        // the ICovariant interface is covariant.
        iobj = istr;
    }
}

공변 제네릭 대리자

// Covariant delegate.
public delegate R DCovariant<out R>();

// Methods that match the delegate signature.
public static Control SampleControl()
{ return new Control(); }

public static Button SampleButton()
{ return new Button(); }

public void Test()
{            
    // Instantiate the delegates with the methods.
    DCovariant<Control> dControl = SampleControl;
    DCovariant<Button> dButton = SampleButton;

    // You can assign dButton to dControl
    // because the DCovariant delegate is covariant.
    dControl = dButton;

    // Invoke the delegate.
    dControl(); 
}

반공변성 형변환

// 클래스 내에 아래와 같은 함수가 정의되어 있다고 가정해봅시다.
// static void SetObject(object o) { }
Action<object> actObject = SetObject;  
// 자식 변수 string 을 받는 대리자에게 부모 변수 object 를 받는 대리자를 할당합니다.
Action<string> actString = actObject; 

부모 객체가 자식 변수로 할당되는 형변환
C# 키워드는in

반공변 제네릭 인터페이스

// Contravariant interface.
interface IContravariant<in A> { }

// Extending contravariant interface.
interface IExtContravariant<in A> : IContravariant<A> { }

// Implementing contravariant interface.
class Sample<A> : IContravariant<A> { }

class Program
{
    static void Test()
    {
        IContravariant<Object> iobj = new Sample<Object>();
        IContravariant<String> istr = new Sample<String>();

        // You can assign iobj to istr because
        // the IContravariant interface is contravariant.
        istr = iobj;
    }
}

반공변 제네릭 대리자

// Contravariant delegate.
public delegate void DContravariant<in A>(A argument);

// Methods that match the delegate signature.
public static void SampleControl(Control control)
{ }
public static void SampleButton(Button button)
{ }

public void Test()
{

    // Instantiating the delegates with the methods.
    DContravariant<Control> dControl = SampleControl;
    DContravariant<Button> dButton = SampleButton;

    // You can assign dControl to dButton
    // because the DContravariant delegate is contravariant.
    dButton = dControl;

    // Invoke the delegate.
    dButton(new Button()); 
}
반응형

'Develop > .NET 가이드' 카테고리의 다른 글

[C#] Delegates 대리자와 Events 이벤트 중 선택하기  (0) 2019.11.29
[C#] Events 이벤트  (0) 2019.11.23
[C#] Delegates 대리자  (0) 2019.09.07
[C#] 비동기 프로그래밍  (8) 2019.08.31
[C#] 코드 문서화  (0) 2019.06.11