언어/C#

C# - 제네릭 2

tsyang 2021. 1. 18. 00:00

공변성과 반공변성 (델리게이트, 인터페이스)


 

델리게이트의 제네릭 타입 매개변수들에 공변성(Covariant)과 반공변성(Contra-Variant)의 성질을 나타낼 수 있다. 이런 기능일 이용하면 제네릭의 타입 인자가 달라도, 특정 제네릭 델리게이트 타입의 변수를 이와 호환되는 다른 델리게이트 타입으로 캐스팅할 수 있다.

 

제네릭 타입 매개변수는 다음 중 하나가 될 수 있다.

 

  • 고정(Invariant) : 제네릭 타입 매개변수는 변경될 수 없음
  • 반공변성 : 제네릭 타입 매개변수로 지정한 타입을 해당 타입을 상속한 (하위타입) 타입으로 변경할 수 있음. C#에서는 in 키워드를 사용. 입력 용도로 사용되는 인스턴스에 대해서만 적용 
  • 공변성 : 제네릭 타입 매개변수로 지정한 타입을 해당 타입이 상속한 (상위타입) 타입으로 변경할 수 있음. out 키워드를 사용하며 반환 용도로 사용되는 경우에만 적용 

공변성과 반공변성은 둘다 가변성(Variance)를 가진다. 고정(Invariant)의 반대임

 

말이 어려운데 대충 정리하자면 다음과 같은 제네릭 타입 매개변수를 사용하는 델리게이트가 있다면,

 

public delegate TResult Func<in T, out TResult>(T arg);

 

T와 TResult에 in과 out키워드로 각각 반공변성과 공변성이 적용된다. 그래서

 

Func<Object, ArgumentException> fn1 = null;
Func<string, Exception> fn2 = fn1; //명시적 캐스팅 필요 없음.

위처럼 fn1과 fn2가 암묵적 캐스팅이 된다.

 

참고로 가변성은 값 타입에 대해서는 작동하지 않는다. 또한 ref와 out키워드를 사용하는 제네릭 타입의 매개변수에 대해서는 허용되지 않는다.

 

 

인터페이스도 마찬가지로 공변성과 반공변성을 가지고 있다.

public interface IEnumerator<out T> : IEnumerator
{
    Boolean MoveNext();
    T Current { get; }
}

위와 같은 공변성을 가지는 인터페이스가 있다면,

 

// 이 메서드는 참조 타입 인자를 가지는 모든 IEnumerable 인터페이스 타입을 받을 수 있다.
int Count(IEnumerable<Object> collection) 
{
    //원소의 갯수를 반환
}

//IEnumerable<string> 컬렉션의 원소 갯수 반환
int c = Count(new[] { "Hello", "World" });

 

이런코드가 잘 작동한다.

 

 

제네릭 타입을 이용하는 델리게이트를 사용할 때는 항상 in과 out키워드를 지정하여 가변성의 장점을 활용해 주는 것이 좋다. 이러한 유연성이 나쁜 영향을 미치는 경우는 전혀 없으며 델리게이트를 좀 더 범용적으로 사용할 수 있게 해준다.

 

 

 

제너릭 메서드


 

 

단순하게는 아래와 같은 메서드가 제네릭 메서드

 

private  static void Swap<T>(ref T 01, ref T 02)
{
    T temp = o1;
    o1 = o2;
    o2 = temp;
}
int a = 1, b = 2;
Swap<int>(ref a, ref b);

 

제네릭 메서드에서는 타입 유추(Type Inference) 기능을 제공한다.

 

int a = 1, b = 2;
Swap(ref a, ref b); // Swap<int>(ref a, ref b) 수행

string s1 = "Hello";
object s2 = "World";
Swap(ref s1, ref s2); // 컴파일에러 (타입이 달라서 유추를 할 수 없음)

위 코드처럼 부등호 기호를 생략할 수 있다.

 

 

또한 특정 타입의 매개변수를 취하는 메서드와 제네릭 타입 매개변수를 취하는 메서드를 같이 정의할 수 있다.

 

private static void Display(String s)
{
    Console.WriteLine(s);
}

private static void Display<T>(T o)
{
    Display(o.ToString()); //Display(string) 메서드 호출
}
Display("Hello");               //Display(string) 메서드 호출
Display(123);                   //Display<T>(T) 메서드 호출
Display<string>("World");       //Display<T>(T) 메서드 호출

컴파일러는 같은 이름의 메서드가 있으면 비제네릭 버전의 메서드를 우선하여 호출한다. (만약 그렇지 않았으면 제너릭 버전의 Display메서드가 자기 자신을 계속 재귀적으로 호출했을 것이다.)

'언어 > C#' 카테고리의 다른 글

C# 인터페이스  (0) 2021.01.27
C# 제너릭 - 3  (0) 2021.01.20
C# 제너릭 - 1  (0) 2021.01.09
C# 이벤트  (1) 2021.01.02
C# 속성 (Property) - 2  (0) 2020.12.27