이 글은 오래된 전에 작성된 글입니다. 따라서 최신 버전의 기술에 알맞지 않거나 오류를 유발할 수 있습니다.
저자는 이 글에 대한 질문을 받지 않을 것입니다. 하지만 이 글이 리뉴얼 되면 이 글에 대한 질문을 하거나
토론을 할 수도 있습니다.
정말 오랜만에 포스트를 올려봅니다. 왜 이렇게 포스트가 없었는지 어쩌고 저쩌고 하는 말은 죄다 핑계로 밖에 안 들릴 터이니 그냥 닥치고 글이나 쓰렵니다.
우리는 흔히 잘 쓰는 기능들이지만 잘 알지 못하는 것이 많습니다. 여러 종류가 있지만 이번 포스트에서는 명시적 인터페이스 구현(explicit interface implementation)에 대해 살펴보도록 하겠습니다. 몰라도 밥 먹고 사는데 큰 지장이 없지만 알아두면 정신 건강에 이로우므로 시간을 좀 내어 보시는 것도 나쁘지 않으리라 생각됩니다. 실은... 앞으로 계획하는 포스트에 등장할 내용이라 미리 선수를 치고자 하는 필자의 얄팍한 속셈이기도 하답니다. 이번 포스트는 간만에 쓰는 거라 짧게 가려고 했는데... 짧게 가려고 했는데... T_T
Back to the Basic:
Explicit Interface Implementation
아놔... 최근 바쁘다는 핑계로 포스트도 안하고 여기 저기 기웃거려 보니 필자와 비슷한 어투로 글을 쓰는 필자들이 있는 것 같다. 흉내를 내려면 좀 더 멋지게 해서 청출어람 이란 소리를 듣던가 아니면 조금이나마 그럴듯하게 글을 쓰던가 아니면 아예 따라 하질 말아주기를 부탁해 본다. 싸가지 없는 글을 쓰는 사람은 필자 하나로 족하지 않은가? 필자의 싸가지에는 이렇게 시원스레 글을 적어야 글 쓰고 싶은 마음이 생긴다는...... 독자들의 넓은 아량으로 이해해 주기 바라는 바이지만 정 용서가 안 된다면 살포시 브라우저의 x 버튼을 눌러주면 된다.
이번 포스트는 많은 개발자들이 코드를 작성하면서 흔히 많이 사용하면서도 잘 알지 못하는 것으로 C#의 명시적 인터페이스 구현(Explicit Interface Implementation)에 대해 노가리를 풀어보고자 한다. 몰라도 되지만 알아두면 남이 짜놓은 코드를 읽을 때 도움이 될 뿐 더러, 필자와 같이 기술적인 글 질을 해대는 인간들이 독자들이 이것을 알까 모를까 하는 노심초사 할 것 없이 예제 코드를 구사하는데 도움이 된다. 필자를 위해서라도 알아두길 바라는 바이다. -_-;
인터페이스란 어떤 기능의 지원을 위해 구현해야 하는? 일련의 메쏘드들의 집합을 말한다. 예를 들어 IComparable 인터페이스는 CompareTo 메쏘드를 포함하고 있으며, 어떤 타입(클래스 혹은 구조체)이 비교 기능을 지원하는가 여부를 나타낸다. 만약 어떤 클래스가 IComparable 인터페이스를 구현한다면 이 클래스는 CompareTo 메쏘드를 통해 이 클래스의 인스턴스들을 비교할 수 있다.
????1?struct MyInt : IComparable
????2?{
????3???? public int Value;
????4?
????5???? public MyInt(int n)
????6???? {
????7???????? Value = n;
????8???? }
????9?
???10???? public int CompareTo(object obj)
???11???? {
???12???????? if (obj is MyInt)
???13???????????? return this.Value - ((MyInt)obj).Value;
???14???????? else
???15???????????? throw new InvalidOperationException("코딩 똑바로 하삼... 비교가 안되요...");
???16???? }
???17?}
리스트1. IComparable 인터페이스 구현 예제
[리스트1]은 IComparable 인터페이스를 구현하는 구조체 MyInt 의 예를 보여주고 있다. 이렇게 어떤 타입이 인터페이스의 메쏘드들을 모두 구현할 때 그 타입은 해당 인터페이스를 "구현(implement)" 한다고 한다. 혹자는 구현이란 용어 대신 "지원(support)" 한다고 표현하기도 하고 또 다른 사람은 "상속(inherit)"한다고 표현하기도 한다. 필자는 구현한다는 표현을 쭉 써왔고 앞으로도 그럴 것이다. 용어가 어찌됐건 MyInt 타입이 IComparable 인터페이스를 구현하므로 다음과 같이 CompareTo 메쏘드를 직접 호출할 수도 있다.
MyInt n1 = new MyInt(5);
MyInt n2 = new MyInt(10);
?
if (n1.CompareTo(n2) < 0)
??? Console.WriteLine("당연 빤쑤 !");
else
??? Console.WriteLine("미치지 않은 다음에야...");
그리고 MyInt 타입은 IComparable 인터페이스를 이용하여 컬렉션 아이템을 정렬(sort)하는 ArrayList를 통해 정렬도 가능하게 된다.
MyInt n1 = new MyInt(5);
MyInt n2 = new MyInt(10);
MyInt n3 = new MyInt(3);
?
ArrayList arr = new ArrayList();
arr.Add(n1);
arr.Add(n2);
arr.Add(n3);
arr.Sort();
여기까지는 대다수의 독자들이 잘 알고 있는 사항이며 공연히 필자가 아는 척을 좀 해본 것이 되겠다.
[리스트1]에서 보는 바와 같이 어떤 타입이 인터페이스를 "구현"하기 위해서는 인터페이스의 모든 메쏘드에 대한 실제 구현을 포함하면 된다. C# 컴파일러는 인터페이스의 각 메쏘드들과 동일한 시그니처를 가지며 public 한정자(modifier)를 가진 메쏘드를 구현하면 그 타입은 해당 인터페이스를 "구현"하는 것으로 간주된다. [리스트1]의 MyInt 타입은 IComparable 인터페이스의 CompareTo 메쏘드와 조낸 똑같은 CompareTo 메쏘드를 구현하기 때문에 IComparable 인터페이스를 구현하는 것으로 간주되므로 오류 없이 잘 컴파일이 되는 것이다.
이렇게 C# 컴파일러에 의해 인터페이스의 메쏘드가 구현된 것으로 인정되도록 인터페이스를 구현하는 것을 암시적 인터페이스 구현(Implicit Interface Implementation)이라고 한다. 사실 [리스트1]의 CompareTo 메쏘드 구현 코드만을 살펴볼 때 이놈의 메쏘드가 IComparable 인터페이스를 위한 메쏘드 구현이란 표시는 눈 씻고 찾아봐도 없다. 다만 MyInt 타입이 IComparable 인터페이스를 구현 한다고 1번째 라인에서 표시되어 있기 때문에 CompareTo 메쏘드가 IComparable 인터페이스의 CompareTo 메쏘드를 구현이구나 하고 통밥으로 때려 잡는 것일 뿐이다. 실제로 컴파일러가 컴파일을 할 때 어떤 타입이 어떤 인터페이스를 구현한다고 표시되어 있으면 곧 설명할 명시적으로 구현된 인터페이스 메쏘드를 찾고 그 메쏘드가 존재하지 않으면 일반 public 메쏘드들 중에서 인터페이스의 메쏘드와 동일한 이름, 매개변수, 리턴 타입을 구현하는 메쏘드들을 찾는다. 일반 메쏘드들 중 일치하는 것이 발견되면 그 타입은 그 인터페이스를 "구현"하고 있다고 인정해 주는 것이다. 이런 의미에서 "암시적"이란 표현이 사용된다고 생각하믄 되긋다.
인터페이스의 암시적 구현은 독자 여러분이 오래 전부터 인터페이스를 구현할 때 사용해 왔던 방법이며 권장되는 인터페이스 구현 방법이자 새로울 것도 없는 것이 되겠다.
암시적 인터페이스 구현이 있다면 눈치학적인 관점에서 명시적 인터페이스 구현도 있다는 말이 되겠다. 그렇다. 명시적 인터페이스 구현은 아싸리 메쏘드 구현 코드에다가 "이 메쏘드는 어느 인터페이스의 어떤 메쏘드를 구현하는 놈이다" 라고 표시를 하는 것을 말한다. IComparable 인터페이스를 명시적으로 구현하는 MyInt 타입의 예는 다음과 같다.
????1?struct MyInt : IComparable
????2?{
????3???? public int Value;
????4?
????5???? public MyInt(int n)
????6???? {
????7???????? Value = n;
????8???? }
????9?
???10???? int IComparable.CompareTo(object obj)
???11???? {
???12???????? if (obj is MyInt)
???13???????????? return this.Value - ((MyInt)obj).Value;
???14???????? else
???15???????????? throw new InvalidOperationException("코딩 똑바로 하삼... 비교가 안되요...");
???16???? }
???17?}
리스트2. IComparable 인터페이스의 명시적 구현
[리스트2]에서 CompareTo 메쏘드의 구현을 살펴보면 평소 보기 힘든 문법을 발견할 수 있을 것이다. 인터페이스 메쏘드의 명시적인 구현은 메쏘드 이름 앞에 인터페이스의 이름을 명확하게 밝히면 된다. 그리고 이 메쏘드에는 private, protected, internal public 등의 한정자를 붙일 수 없다(붙이면 C# 컴파일러의 가열찬 오류 메시지를 만날 것이다). 실제로 CLR(Common Language Runtime)은 명시적 구현에서도 한정자를 허용하지만 C# 컴파일러는 한정자를 허용하지 않으며 자동으로 private 한정자를 붙여 버린다.
인터페이스의 구현은 명시적 구현과 암시적 구현을 섞어 쓸 수 있다. 즉, 여러 메쏘드를 가진 인터페이스를 구현할 때 일부 메쏘드는 명시적으로 구현하고 일부 메쏘드는 암시적으로 구현할 수 있다는 말이 되겠다. 구체적인 예제는 지면관계상 생략하겠다. 어찌 되었건 이렇게 인터페이스의 메쏘드를 명시적으로 구현하면 CompareTo 메쏘드는 더 이상 public 메쏘드가 아니기 때문에 단순히 호출할 수 없다. 명시적으로 구현된 인터페이스 메쏘드는 반드시 인터페이스 타입을 통해서만 호출이 가능하다. 다음 코드는 그 예를 보여준다.
MyInt n1 = new MyInt(5);
MyInt n2 = new MyInt(10);
?
IComparable c1 = n1 as IComparable;
if (c1.CompareTo(n2) < 0)
??? Console.WriteLine("당연 빤쑤 !");
else
??? Console.WriteLine("미치지 않은 다음에야...");
명시적으로 구현된 인터페이스 메쏘드는 앞서 언급한 대로 private 한정자가 자동으로 붙기 때문에 직접적으로 MyInt 타입을 통해 호출이 불가능하다. 따라서 위 코드처럼 IComparable 타입으로 형 변환을 수행한 다음에라야 CompareTo 메쏘드를 호출할 수 있게 된다. 하지만 MyInt 타입은 IComparable 인터페이스를 요구하는 메쏘드에게는 여전히 예전처럼 사용할 수 있다. MyInt 타입이 IComparable 인터페이스를 "구현"하고 있음은 변화가 없기 때문이다. 따라서 앞서 보았던 ArrayList 예제 코드는 인터페이스를 암시적으로 구현했건 명시적으로 구현했건 MyInt 타입에 대해 문제가 없다.
이제서야 무릎을 탁 치면서 "아! Visual Studio 에도 명시적 구현에 대한 기능이 있지!"하며 소리치는 독자가 있다면 숏 잡고 반성해야 할 것이다. 그런 것을 봤다면 MSDN에서 검색 한 번만 해도 필자가 지금까지 나불거린 내용이 모두 설명되어 있으니 말이다.
그림1. Visual Studio의 인터페이스 구현 Refactoring 기능
멀쩡하게 잘 써왔고 권장까지 한다는 암시적 인터페이스 구현을 놔두고 명시적 인터페이스 구현을 써야 하나? 마이크로소프트의 닷넷 팀이 밥 먹고 할 일이 없어서 명시적 인터페이스 구현 기능을 제공하는 것은 아니다. 다 이유가 있기 때문이다. 암시적 인터페이스 구현 보다 명시적 인터페이스 구현을 사용해야 하는 구체적인 경우들을 살펴보자.
첫 번째 이유는 인터페이스 메쏘드의 구현 사실을 외부에 감추고 싶을 때이다. 모든 배열은 System.Array 클래스에서 파생되었고 System.Array 클래스는 ICloneable 인터페이스를 비롯하여 IList, ICollection, IEnumerable 인터페이스를 모두 구현하고 있다.
public abstract class Array : ICloneable, IList, ICollection, IEnumerable
{
??? ......?
}
IList 인터페이스에는 Add 메쏘드가 정의되어 있으므로 다음과 같은 호출을 기대할 수도 있을 것이다.
int[] arr = new int[32];
arr.Add(22);
하지만 위와 같은 코드는 여지없이 컴파일 오류를 발생한다. Array 클래스는 IList 인터페이스의 Add 메쏘드를 명시적으로 구현하고 있기 때문에 int[] 타입이나 Array 타입에는 Add 라는 메쏘드는 존재하지 않는 것처럼 보인다. 사실 배열 타입에 Add 메쏘드가 존재한다는 것 조차도 부자연스러운 일이다.
배열 타입은 그 정의 상 동일 타입을 가진 배열 요소들로 구성되며 그 크기가 고정된 목록이다. 따라서 Add 메쏘드를 배열 타입을 통해 호출하도록 할 수는 없다. 그럼에도 불구하고 배열은 IList 인터페이스를 구현하여 IList 인터페이스를 요구하는 많은 메쏘드와 타입들에 배열을 사용하도록 할 필요가 있다. 배열은 IList 인터페이스를 구현하는 리스트의 한 종류이기 때문이다.
배열이 리스트(list)적인 특성을 갖기 때문에 IList 인터페이스를 구현해야만 한다. 하지만 Add 메쏘드와 같이 배열 타입의 특성과 어울리지 않는 인터페이스의 메쏘드를 배열 타입에 포함하여 공개하고 싶지는 않을 것이다. 이런 이유에서 Array 타입이 IList 인터페이스를 구현하면서도 이 인터페이스의 메쏘드들의 구현을 외부에 감추는 이유이다. 배열 타입은 IList 인터페이스의 많은 메쏘드들을 명시적으로 구현하여 이들 메쏘드들의 구현 사실을 감춘다. 배열 타입을 통해 이들 메쏘드가 공개되는 것을 원하지 않기 때문이다. 실제로 명시적으로 구현된 인터페이스의 메쏘드들은 해당 타입을 통해 직접적으로 "보이지" 않으며 인텔리센스에도 나타나지 않는다. 배열 타입과 비슷하게 많은 타입들이 인터페이스를 명시적으로 구현하여 메쏘드가 그 타입에 존재하지 않는 것처럼 만들곤 한다.
명시적 인터페이스 구현하는 두 번째 이유는 IComparble 인터페이스나 IList 인터페이스와 같이 object 타입을 매개변수로 사용하는 인터페이스들의 비 효율성을 감추기 위해서 이다. [리스트1]을 다시 살펴보자. MyInt 타입에서 구현된 IComparable 인터페이스의 CompareTo 메쏘드는 매개변수 타입으로 object 타입을 사용한다. Object 타입을 사용함으로써 CompareTo 메쏘드는 다양한 타입에서 사용될 수 있다는 장점이 있다. 하지만 int 타입이나 MyInt 타입과 같은 구조체 타입은 모두 값 타입(value type)으로써 object 타입과 함께 사용될 때 불리한 점이 많다. 다시 한번 MyInt 타입을 사용하는 다음 코드를 살펴보자.
MyInt n1 = new MyInt(5);
MyInt n2 = new MyInt(10);
?
// 박싱(boxing)이 발생하며 CompareTo 메쏘드 내에서 다시 언박싱(unboxing)이 발생한다.
if (n1.CompareTo(n2) < 0)?????????????
??? Console.WriteLine("당연 빤쑤 !");
else
??? Console.WriteLine("미치지 않은 다음에야...");
이 코드는 CompareTo 메쏘드를 호출하기 위해 박싱(boxing)을 해야 하며 CompareTo 메쏘드 내에서 다시 언박싱(unboxing)을 해야만 한다. 이 뿐이던가? 더 큰 문제는 강력한 데이터 타입에 대한 검사가 안 된다는 것이다. MyInt 타입에 대한 다음 코드를 살펴보자.
MyInt n = new MyInt(3);
string s = "string data";
n.CompareTo(s);
분명 위 코드는 논리적으로나 물리적으로나 예외(exception)를 유발하는 코드이다. 하지만 위 코드는 그냥 쌩까고 아무런 문제 없이 컴파일 되어 버리며 위 코드가 실제로 수행되는 시점에서나 예외를 유발한다. 요약하자면 잠재적인 버그를 갖는 코드가 컴파일 타임에 검사되지 않는다는 것이다.
일반적으로 이러한 문제를 해결하는 방법은 object 타입 대신 구체적인(MyInt 타입같은) 타입의 CompareTo 메쏘드를 제공하는 것이다. 하지만 여전히 MyInt 타입이 IComparable 인터페이스를 요구하는 클래스나 메쏘드(ArrayList.Sort 와 같은 메쏘드)에서 사용되도록 하려면 IComparable 인터페이스를 구현해야만 한다.
느낌이 팍 오는가? 그렇다. IComparable.CompareTo 메쏘드는 명시적으로 구현하고 대신 MyInt 타입을 매개변수로 취하는 메쏘드를 제공하면 된다.
????1?struct MyInt : IComparable
????2?{
????3???? public int Value;
????4?
????5???? public MyInt(int n)
????6???? {
????7???????? Value = n;
????8???? }
????9?
???10???? public int CompareTo(MyInt val)
???11???? {
???12???????? return this.Value - val.Value;
???13???? }
???14?
???15???? int IComparable.CompareTo(object obj)
???16???? {
???17???????? if (obj is MyInt)
???18???????????? return this.Value - ((MyInt)obj).Value;
???19???????? else
???20???????????? throw new InvalidOperationException("코딩 똑바로 하삼... 비교가 안되요...");
???21???? }
???22?}
리스트3. 인터페이스의 명시적 구현을 통해 형 변환 문제를 해결(?)한 코드
[리스트3]와 같이 IComparable 인터페이스를 명시적으로 구현하면 MyInt 타입을 사용하여 직접적으로 IComparable 인터페이스의 CompareTo 메쏘드를 호출할 수는 없다. 대신 10번째 라인에 구현된 public 멤버인 CompareTo 메쏘드를 호출할 수 있을 것이다. 따라서 앞서 살펴보았던 박싱/언박싱 문제나 형 검사 문제는 발생하지 않을 것이다. 그렇다고 MyInt 타입이 IComparable 인터페이스를 구현하지 않는 것이 아니기 때문에 IComparable 인터페이스를 요구하는 ArrayList.Sort 와 같은 메쏘드는 여전히 MyInt 타입에 대해 잘 작동한다.
보다 세련된 구현 방법으로는 IComparable 인터페이스의 제너릭(generic) 버전인 IComparable 인터페이스를 구현하는 것이다. 그렇지만 ArrayList.Sort 메쏘드와 같이 닷넷 프레임워크 2.0 이전 코드들은 여전히 IComparable 인터페이스를 요구하기 때문에 IComparable 인터페이스를 구현해야 할 수도 있다. (점차로 ArrayList 클래스를 사용하는 빈도도 낮아질 것이므로 IComparable 인터페이스를 구현해야 할 가능성은 앞으로 점점 낮아질 것이다.) 그래서 닷넷 프레임워크에서 제공하는 많은 클래스들이 제너릭 버전의 인터페이스와 제너릭이 아닌 인터페이스를 모두 구현하고 있다. 제너릭 버전과 비 제너릭 버전의 두 인터페이스를 모두 구현해야 한다면 강력한 타입을 제공하는 제너릭 버전을 암시적으로 구현하고 타입에 민감하지 못한 비 제너릭 버전의 인터페이스를 명시적으로 구현하는 것이 좋다.
마지막으로 인터페이스의 명시적인 구현이 필요한 경우는, 명시적 인터페이스 구현을 쓸 수 밖에 없는 경우이다. 대표적인 예로써 어떤 타입이 IEnumerable 인터페이스를 구현해야 하는 상황을 생각해 보자. IEnumerable 인터페이스는 IEnumerable 인터페이스에서 파생된 인터페이스이다. 따라서 IEnumerable 인터페이스를 구현하고자 한다면 IEnumerable 인터페이스의 메쏘드들까지 모두 구현해야만 한다. 암시적 인터페이스 구현이라면 다음과 같이 구현해 봄직도 하지만...
class MyCollection : IEnumerable<MyInt>, IEnumerable
{
??? public IEnumerator<MyInt> GetEnumerator()
??? { ...... }
?
??? public IEnumerable GetEnumerator()
??? { ...... }
}
위 코드는 여지없이 GetEnumerator 메쏘드가 이미 정의 되었다는 내용의 컴파일러 오류를 발생한다. 암시적 인터페이스 구현은 타입의 일반 메쏘드를 C# 컴파일러가 인터페이스의 메쏘드로 자동으로 간주해 줄 뿐이다. 따라서 컴파일러가 MyCollection 타입이 인터페이스의 구현 여부를 판단하기도 전에 메쏘드 오버로드 규약에 어긋나 버리는 것이 되겠다.
이런 상황이라면 선택의 여지가 없다. IEnumerable 나 IEnumerable 인터페이스의 메쏘드 중 적어도 하나는 명시적으로 구현해야 한다. 두 인터페이스 다 명시적으로 구현하는 것 보다는 타입 정보가 보다 정확한 IEnumerable 인터페이스는 암시적으로 구현하고 메쏘드의 구현을 숨기고 싶은 IEnumerable 인터페이스는 명시적으로 구현하는 것이 좋을 것이다(앞서 입이 아프도록 이야기 한 바 있다).
class MyCollection : IEnumerable<MyInt>, IEnumerable
{
??? public IEnumerator<MyInt> GetEnumerator()
??? {
??????? // 구현 내용 생략 (이걸 이야기하려고 하는건 아니자나요?)
??? }
?
??? IEnumerator IEnumerable.GetEnumerator()
??? {
??????? // IEnumerablt.GetEnumerator 호출
??????? return this.GetEnumerator();
??? }
}
지금까지 쎄 빠지게 설명한 내용이 좀 무색하긴 하지만, 명시적 인터페이스의 구현은 피할 수 있으면 피하는 것이 좋다. 다시 말해 가급적이면 암시적 인터페이스 구현을 사용하는 것이 좋다는 말이다. 명시적 인터페이스의 첫 번째 단점은 구현 내용이 인텔리센스 등을 통해 드러나지 않기 때문에 MSDN 과 같은 도움말을 참조하지 않으면 어떤 타입이 어떤 인터페이스를 구현하고 있는지 불 분명하다는 점이다. 사실 독자들 중에서 배열 타입이 IList 인터페이스를 구현하고 있다는 사실을 처음 알게 된 사람도 있을 것이다. 이런 것이 바로 명시적 인터페이스 구현이 가져다 주는 "불 분명성" 이다.
두 번째 단점은 int 타입이나 우리의 예제인 MyInt 타입과 같은 값 타입(value type)에 명시적 인터페이스 구현은 좋지 않은 선택이 될 수 있다는 것이다. 명시적으로 구현된 인터페이스는 앞서 살펴본 대로 인터페이스 타입을 통해서만 호출이 가능하다. 하지만 인터페이스 타입은 참조 타입(reference type)이기 때문에 인터페이스의 메쏘드를 호출하기 위해 값 타입을 인터페이스 타입으로 변환한다면 원하지 않는 박싱이 발생하게 된다.
MyInt n1 = new MyInt(5);
MyInt n2 = new MyInt(10);
?
IComparable c1 = n1 as IComparable;??? // 박싱 발생 !!!
if (c1.CompareTo(n2) < 0)
??? Console.WriteLine("당연 빤쑤 !");
else
??? Console.WriteLine("미치지 않은 다음에야...");
마지막으로 명시적 인터페이스 구현의 단점은 파생 클래스에서 부모 클래스에서 명시적으로 구현한 인터페이스 메쏘드를 호출할 방법이 없다는 점이다. 파생 클래스에서 부모 클래스에서 명시적으로 구현된 인터페이스 메쏘드를 호출하고자 한다면 여러 번 언급한 대로 인터페이스 타입을 구해야 한다. 하지만 부모의 인터페이스 타입을 구할 방법이 없다. 구체적인 예제 코드는 다음과 같다.
class MyType : IComparable
{
??? int IComparable.CompareTo(object obj)? // 명시적으로 CompareTo 메쏘드 구현
??? { ...... }
}
?
class MyDerivedType : MyType, IComparable
{
??? public int CompareTo(object obj)
??? {
??????? base.CompareTo(obj);??? // 컴파일 오류. 부모의 CompareTo 메쏘드는 보이지 않는다.
?
??????? IComparable c = this as IComparable;
??????? c.CompareTo(obj);??????? // 자기 자신 호출. 무한 루프.
??? }
}
?부모 클래스인 MyType 클래스가 IComparable 인터페이스를 명시적으로 구현하고 있을 때, 파생 클래스인 MyDerivedType 클래스도 IComparable 인터페이스를 구현하여 자신만의 구현을 제공해야 할 수도 있다. 하지만 이러한 구현에서 때때로 부모의 구현을 그대로 사용해야 할 상황도 자주 발생한다. 부모의 구현을 사용하려고 base.CompareTo()를 호출하려고 하면, 명시적으로 구현된 ComapreTo 메쏘드는 C# 컴파일러에 의해 강제적으로 private 멤버로 사용되기 때문에 컴파일 오류를 유발한다. 따라서 인터페이스 타입으로 호출하기 위해IComparable 인터페이스 타입으로 형 변환을 시도하면 부모의 인터페이스 구현이 아니라 자기 자신에 대한 인터페이스 타입으로 형 변환이 수행되어 버린다. 이 문제를 해결하는 방법은 부모 클래스가 암시적으로 인터페이스를 구현하면서 virtual 키워드를 붙여야만 해결이 가능하다.
Summary
우리가 흔히 사용하는 인터페이스에도 알아두어야 할 것들이 존재한다. 몇 가지 사항들이 있지만 그 중 개발자들에게 잘 알려지지 않은 내용 중 하나가 인터페이스의 명시적인 구현이다. 사실 명시적 인터페이스 구현은 SI 프로젝트나 일반 어플리케이션을 개발할 때와 같은 일반적인 많은 상황에서 몰라도 되는 사항들 중 하나이다. 하지만 필자와 같이 클래스 라이브러리를 작성하는 일이 많거나 닷넷 프레임워크의 클래스 라이브러리를 분석할 때 반드시 알아 두어야 할 사항들 중 하나이다. 뜬금없이 나타나는 명시적인 인터페이스 구현 문법은 개발자를 당황스럽게 만들 수도 있다.
사실 클래스 라이브러리를 작성하다 보면 인터페이스를 암시적으로 구현하는 것보다 명시적으로 구현하는 것이 좋을 때가 종종 있다. 인터페이스의 구현 사실을 감춤으로써 내가 작성한 클래스 라이브러리를 보다 간단한 모습으로 개발자에게 제공할 수 있을 때가 많기 때문이다.
이번 포스트도 쓸데 없이 길어진 듯한 느낌이 든다. 사실 간략하게 인터페이스를 명시적으로 구현하는 방법이 있다는 것 정도만 소개하려고 했지만 이번에도 "아는 척하기 좋아하고 글 어렵게 쓰기 좋아하는" 필자의 고질병이 또 도진 것이다. 사실 필자가 일부러 그런 것은 아니다. 독자들에게 원리를 설명하고 "무엇"보다는 "왜"를 설명하고자 하다 보니 글의 내용도 길어지고 글의 내용도 어렵게 느껴지는 것 같다. 볼 맨 핑계 같지만 필자가 정말 일부러 글을 어렵게 쓰는 것도 아니며 많이 아는 척하고 싶어하는 것도 아니다. 다만 어떤 기술에 대한 원리와 "왜"를 설명하고자 하다 보니 내용이 길어지고 그렇게 된 것일 뿐이다. 원리를 이해하고 왜 그런 기술/기능을 사용해야 하는지 아는 것 만큼 실력을 쌓는데 도움되는 것도 없다. 이것이 프로그래밍이란 것을 20여 년 동안 해온 필자의 노하우이기도 하다.
Comments (read-only)
#re: Back to the Basic: 인터페이스의 명시적 구현 / lancers / 6/23/2008 12:26:00 AM
음, 핑계대지 말고 술이나 사삼~ ㅋㅋㅋ
#re: Back to the Basic: 인터페이스의 명시적 구현 / 웹지니 / 6/23/2008 9:33:00 AM
오랜만에 올려주신 포스트 잘 읽었습니다. =)
#re: Back to the Basic: 인터페이스의 명시적 구현 / 이경원 / 6/23/2008 10:19:00 AM
잘 봤습니다.....몇번 더 읽어바야 이해를 할듯.ㅡ.ㅡ......
담에도 좋은 포스트 기대하겠습니다...^^v
#re: Back to the Basic: 인터페이스의 명시적 구현 / 쟈카드 / 6/23/2008 11:25:00 AM
인터페이스의 명식적 구현에 대한 명확한 이해가 가는군요... 오랜만의 글 잘 읽었습니다...
#re: Back to the Basic: 인터페이스의 명시적 구현 / 탱옹 / 6/24/2008 10:56:00 AM
이번 포스트는 잼났어요. 앞으로도 잼나게 써주세연~
그리고, 유 수석님 블로그에서는 오프라인 벙개 없나연?
#re: Back to the Basic: 인터페이스의 명시적 구현 / 재허니 / 6/25/2008 5:55:00 PM
감사히 잘 읽고 갑니다.
#re: Back to the Basic: 인터페이스의 명시적 구현 / 어흥이 / 6/25/2008 11:58:00 PM
새 글 완전 반갑습니다.^^ 잘 읽고 갑니다~~
#re: Back to the Basic: 인터페이스의 명시적 구현 / Dish / 6/29/2008 5:36:00 PM
명시적 구현은 단순히 인터페이스 n개를 구현할 때 같은 심볼의 메소드가 겹치는 경우 쓰는 거라고 생각하고 있었는데
저런 그레이트한 차이가 있는 줄 몰랐네요.
#re: Back to the Basic: 인터페이스의 명시적 구현 / 우정환 / 6/30/2008 8:46:00 AM
마지막 볼드체 문장이 확와닿네요...
잘 읽고 갑니다.
감사합니다. 꾸뻑.
#re: Back to the Basic: 인터페이스의 명시적 구현 / 즈믄 / 7/7/2008 10:38:00 AM
오랜 만의 글 잘 읽고 갑니다.. ^_^
#re: Back to the Basic: 인터페이스의 명시적 구현 / 찌질개발자 / 7/14/2008 4:20:00 PM
오늘도 숏잡고 반성하고 갑니다. ㅜㅜ
좋은 포스팅 항상 감사합니다.^^
#re: Back to the Basic: 인터페이스의 명시적 구현 / 가이아 / 7/31/2008 8:58:00 AM
역시나 쌩유~~
#re: Back to the Basic: 인터페이스의 명시적 구현 / 산소나무 / 9/9/2008 2:55:00 PM
"필자를 위해서라도 알아두길 바라는 바이다. -_-;" 이곳에서 정말 너무 웃었습니다.
좋은 강좌 너무 감사합니다.