728x90
컴파일러는 제네릭 메서드의 타입 매개변수가 다른 타입으로 다양하게 변경될 수 있음을 고려하여 오버로드된 메서드 중 하나를 선택한다.
그런데 이런 동작 방식을 제대로 인지하지 못하면, 프로그램이 이상하게 동작할 수 있다.
namespace _210210_Item24Ex
{
public class MyBase { }
public interface IMessageWriter { void WriteMessage(); }
public class MyDerived : MyBase, IMessageWriter
{
void IMessageWriter.WriteMessage() => WriteLine("Inside MyDerived.WriteMessage");
}
public class AnotherType : IMessageWriter
{
public void WriteMessage() => WriteLine("Inside AnotherType.WriteMessage");
}
class Program
{
static void WriteMessage(MyBase b)
{
WriteLine("Inside WriteMessage(MyBase)");
}
static void WriteMessage<T>(T obj)
{
Write("Inside WriteMessage<T>(T): ");
WriteLine(obj.ToString());
}
static void WriteMessage(IMessageWriter obj)
{
Write("Inside WriteMessage(IMessageWriter): ");
obj.WriteMessage();
}
static void Main(string[] args)
{
MyDerived d = new MyDerived();
WriteLine("Callling Program.WriteMessage");
WriteMessage(d);
WriteLine();
WriteLine("Calling through IMessageWriter interface");
WriteMessage((IMessageWriter)d);
WriteLine();
WriteLine("Cast to base object");
WriteMessage((MyBase)d);
WriteLine();
WriteLine("Another Type test: ");
AnotherType anObject = new AnotherType();
WriteMessage(anObject);
WriteLine();
WriteLine("Cast to IMessageWriter: ");
WriteMessage((IMessageWriter)anObject);
}
}
}
결과
Callling Program.WriteMessage // 1
Inside WriteMessage<T>(T): _210210_Item24Ex.MyDerived
Calling through IMessageWriter interface // 2
Inside WriteMessage(IMessageWriter): Inside MyDerived.WriteMessage
Cast to base object // 3
Inside WriteMessage(MyBase)
Another Type test: // 4
Inside WriteMessage<T>(T): _210210_Item24Ex.AnotherType
Cast to IMessageWriter: // 5
Inside WriteMessage(IMessageWriter): Inside AnotherType.WriteMessage
설명
첫 번째
MyBase를 상속한 MyDerived 클래스는 WriteMessage(MyBase b)보다 WriteMessage<T> (T obj)에 더 정확히 일치한다.
제네릭 메서드 타입 매개변수인 T를 MyDerived로 대체하면 컴파일러 입장에서는 요청한 메서드와 정확히 일치하는 메서드를 찾을 수 있기 때문이다.
반면 WriteMessage(MyBase b)는 암시적 형변환이 필요하다.
두 번째, 세 번째
MyBase나 IMessageWriter로의 명시적 형변환이 메서드 확인 규칙에 어떻게 영향을 주는지 보여준다.
네 번째, 다섯 번째
상속 관계는 없지만 특정 인터페이스를 구현하고 있는 타입을 사용할 경우 어떤 메서드가 선택되는지 보여준다.
결론
베이스 클래스와 파생된 클래스에 대해 모두 수행 가능하도록 하기 위해 인터페이스에 대해 제네릭을 특화하면 오류가 발생할 가능성이 높다.
런타임에 타입을 확인하도록 코드를 추가하는 것 보단 차라리 컴파일러의 타입 확인 기능을 활용하는 것이 낫다.
반응형
'개발공부 > C#' 카테고리의 다른 글
[Effective C#] 아이템 26 : 제네릭 인터페이스와 논제네릭 인터페이스를 함께 구현하라 (0) | 2021.12.02 |
---|---|
[Effective C#] 아이템 25 : 타입 매개변수로 인스턴스 필드를 만들 필요가 없다면 제네릭 메서드를 정의하라 (0) | 2021.12.01 |
[Effective C#] 아이템 23 : 타입 매개변수에 대해 메서드 제약 조건을 설정하려면 델리게이트를 활용하라 (0) | 2021.11.29 |
[Effective C#] 아이템 22 : 공변성과 반공변성을 지원하라 (0) | 2021.11.28 |
[Effective C#] 아이템 21 : 타입 매개변수가 IDisposable을 구현한 경우를 대비하여 제네릭 클래스를 작성하라 (0) | 2021.11.27 |