티스토리 뷰

 

제네릭 메서드를 포함한 여러 개의 오버로드된 메서드가 있는 경우, 컴파일러는 제네릭 메서드의 타입 매개변수가 다른 타입으로 다양하게 변경될 수 있음을 고려하여 오버로드된 메서드 중 하나를 선택한다. 그런데 자칫 이러한 동작 방식을 제대로 인지하지 못 할 경우, 응용프로그램이 이상하게 동작할 수도 있다.

하지만, 개발자는 컴파일러가 오버로드된 메서드 중 어떤 메서드를 선택하는지 정확히 알고 있어야 한다. 우선, 코드부터 살펴보자.


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("Calling 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); } }

이번 파트를 좀 더 이해하고 싶다면 위 코드만 보고 Program 클래스의 Main 메서드의 출력값이 어떻게 될지 추측해보자. 제네릭 메서드가 정의되어 있는 경우 필요한 메서드의 원형에 정확하게 부합하도록 닫힌 메서드가 생성된다는 것을 참고하자.

....

......

........

다 생각했으면 이제 정답을 공개한다.

Calling Program.WriteMessage Inside WriteMessage<T>(T): Item14.MyDerived Calling through IMessageWriter interface Inside WriteMessage(IMessageWriter): Inside MyDerived.WriteMessage Cast to base object Inside WriteMessage(MyBase) Anther Type test: Inside WriteMessage<T>(T): Item14.AnotherType Cast to IMessageWriter: Inside WriteMessage(IMessageWriter): Inside AnotherType.WriteMessage

어떤가? 생각대로 정답을 맞췄는가. 글쓴이는 5번째 빼고는 맞추긴 했다..

첫 번째 테스트의 결과는 메서드 확인 규칙에서 가장 중요한 개념 중 하나를 부여준다.

MyBase를 상속한 MyDerived 클래스는 WriteMessage(MyBase b)보다 WriteMessage<T> (T obj)가 더 정확히 일치한다. 왜냐하면 제네릭 메서드의 타입 매개변수인 T를 MyDerived로 대체하면 컴파일러 입장에서는 요청한 메서드와 정확히 일치하는 메서드를 찾을 수 있기 때문이다. 반면 WriteMessage(MyBase b)는 암시적 형 변환이 필요하다.

두 번째, 세 번째 테스트는 MyBase나 IMessageWriter로의 명시적 형변환이 메서드 확인 규칙에 어떻게 영향을 미치는지를 보여준다.

예상한대로 제네릭 메서드가 아닌 두 메서드가 호출 되었다.

네 번째와 마지막 테스트는 상속 관계는 없지만 특정 인터페이스를 구현하고 있는 타입(IMessageWriter)을 사용할 경우 어떤 메서드가 선택되는지를 보여준다.

네 번째 경우엔 해당 타입의 매개변수를 필요로 하는 메서드가 없어 제네릭 메서드를 이용하고 마지막은 IMessageWriter 인터페이스로 명시적 형 변환을 해주었기 때문에 IMessageWriter 인터페이스의 매개변수를 가지는 메서드가 호출되었다.

결론

베이스 클래스와 이로부터 파생된 클래스에 대해서 모두 수행 가능하도록 하기 위해서 베이스 클래스를 이용하여 제네릭을 특화(specialization)하려는 시도는 바람직하지 않다. 특히, 인터페이스에 대해 제네릭을 특화하게 되면 오류가 발생할 가능성이 너무 높다.

아니면 앞서 아이템 18(반드시 필요한 제약 조건만 설정하라)처럼 타입 매개변수로 지정할 수 있는 타입별로 각기 특화된 코드를 작성하는 편이 나을 수 있다. 런타임에 타입을 확인하도록 코드를 추가하는 것보단 차라리 컴파일러의 타입 확인 기능을 활용하는 것이 낫다.

그렇지만 런타임에 확인할 조건이 몇 없다면 런타임 중에 타입 확인하는 것도 나쁘지 않다.


제네릭이라는 개념 자체가 아직 완벽히 와닿지 않은 것 같다.

코딩을 하면서 제네릭을 좀더 친숙하게 이용할 수 있도록 해야겠다.

참조 - Effective C# <강력한 C# 코드를 구현하는 50가지 전략과 기법, 이펙티브>, 빌 와그너, 김명신, 한빛미디어

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
글 보관함