[중요] 리플렉션 강좌 #1

리플렉션 강좌의 첫 테이프를 끊게 되었습니다. 내용이 두서없거나 미숙하더라도 넓은 아량으로 이해해주시면서 지적해주시면 감사하겠습니다. ^^;


1. 리플렉션의 의미와 그 필요성


리플렉션은 사전적 의미인 “어떤 사물을 비추어 보다”와 동일한 기능상 의미를 지닙니다. 즉, 리플렉션은 코드 구문 상에서는 사용할 수 없는 제 3의 형식 개체 (클래스, 대리자, 나열 상수, 구조체, 인터페이스 등)를 동적으로 다루는 기술을 의미합니다.


리플렉션은 그닥 활용하기 쉽지도 않을 뿐더러 활용할 만한 곳을 찾기도 어렵습니다. 따라서 사람들이 잘 뒤져보지 않는 기술이기도 합니다. 하지만 리플렉션을 빼면 닷넷은 이빨빠진 호랑이, 더 심한 표현을 쓰면 뼈대 없는 건물로 전락하고야 맙니다. 그만큼 리플렉션을 활용하는 곳이 많다는 것을 강조하고 싶습니다.


2. 쉬운 리플렉션부터 살펴봅시다!


리플렉션에 대해서는 차근차근히 수순을 밟아나가는 것이 이해에 도움이 됩니다. 가장 쉬운 리플렉션부터 공략해 봅시다.


사용하는 디자인 패턴에 따라서 상속을 받는 자식 클래스를 만드는 디자인 패턴을 사용하는 경우도 가끔있습니다. 이 경우 리플렉션의 가장 대표적인 예를 시험해 볼 수 있습니다. 한가지 실험을 해봅시다.


배열을 가지고 우리는 두 가지의 리플렉션을 시험해 볼 수 있겠습니다. 프레임워크 (주: CLS 규격을 구현하는 프레임워크의 수가 많기 때문에 특정 프레임워크를 지칭할 수는 없습니다. 따라서 Microsoft .NET, Rotor, Mono, DotGNU 등을 한꺼번에 아울러 프레임워크라고 표현하겠습니다.)는 언어 문법에서 사용하는 배열의 인스턴스를 System.Array 클래스를 기초해서 동적으로 생성합니다.


System.Array 클래스는 배열에 관한 기본적인 기능을 정의한 클래스이지만 System.Array 클래스의 생성자를 우리가 직접 호출할 수는 없습니다. 그리고 CLS 규격에서 명시하였던 바와 같이 System.Object가 System.Array에 대한 상위 클래스임은 변함이 없습니다. 이와 같은 전제 조건하에서 시험을 해보겠습니다.


2.1. System.Array의 활용


배열은 가지고 있는 원소 (Atom/Element)의 형식에 따라서 각각 다른 배열로 취급되며 배열 vs. 배열 단위의 형변환은 비록 두 배열의 원소의 형식 관계가 상속 관계라고 할지라도 기본적으로는 허용되지 않습니다. 하지만 “배열” 이라는 특성은 원소의 형식과는 관계없이 공통적인 것입니다. 우리는 각기 다른 원소 형식을 가지는 배열을 System.Array 라는 클래스로 동일하게 취급할 수 있습니다. 어떤 형태의 원소 형식을 가지는 배열이 되었던간에 상관없이 말입니다.


Array test = new string [5];
test.SetValue(“Test”, 2);
string[] test2 = (string[])test;
Console.WriteLine(test2[2]);


기본적으로 System.Array는 추상 클래스이므로 System.Array의 생성자는 사용할 수 없습니다. 하지만 어떻게 인스턴스가 만들어지게 된 것일까요? 명시되어 있지는 않지만 컴파일러 내부적으로는 또 하나의 클래스를 System.Array로부터 동적으로 파생시켰다고 가정하고 다루게 됩니다. 다시 말하여 test 라는 인스턴스는 파생된 클래스의 멤버들 가운데서 System.Array의 멤버들과 일치하는 멤버들만을 가르키게 됩니다.


Array 클래스에 값을 대입하기 위해서 SetValue 메서드를 호출합니다. Array 클래스에서는 인덱서 연산자를 구현하지 않으므로 원시 함수인 SetValue 메서드를 직접 호출하게 되었습니다. 이 상태에서 string[] 클래스로 형변환을 다시 시도합니다. 그리고 값을 확인해 봅니다. Test 라는 문자열이 그대로 유지된 것을 확인하실 수 있습니다.


위의 예에서 중요한 원리 하나를 확인할 수 있습니다. 첫 번째는 형변환에는 방향성이 존재한다는 것입니다. 형식의 변환이 추상화될 수록 (위로 갈수록) 가급적 공통 부분으로만 표현됩니다. 반대로 형식의 변환이 구체화될 수록 (아래로 갈수록) 가급적 상세하게 표현됩니다.


2.2. System.Object의 활용


프레임워크에서 정의되는 모든 개체들의 최상위 클래스는 반드시 System.Object입니다. 심지어는 구체적인 형식을 알 수 없는 개체마저도 System.Object로 형변환 처리가 되는 것도 정당합니다. 이럴 경우 형식의 모호성이라는 문제점이 발생합니다. 즉, 전달받은 개체가 구체적으로 어떤 개체인지 알 길이 없을 수도 있으며 본래의 형식으로 복원하는 것이 불가능해질 수도 있다는 것입니다.


앞의 경우와는 달리 System.Object로의 형변환은 매우 극단적으로 추상화된 형변환이기 때문에 이러한 문제점이 발생하게 됩니다. 하지만 반드시 심각한 문제점인 것만은 아닙니다. 오히려 이것이 리플렉션이 왜 중요한지 알려주는 중요한 연습 문제가 되는 것입니다.


오늘 강좌에서는 언어 구문을 활용한 원시적인 리플렉션을 사용해 보도록 하겠습니다. 바로 is 연산자입니다. is 연산자의 문법 구조는 다음과 같습니다.


[생성된 개체명] is [확인하고자 하는 형식명];


is 연산자로 계산된 값은 System.Boolean 형식으로 반환됩니다. (즉, True 또는 False입니다.) is 연산자가 예외를 일으키지 않고 True 또는 False를 반환하려면 개체가 null이 아니어야 하고 확인하고자 하는 형식명이 컴파일러가 인식할 수 있는 형식 이름이어야 한다는 것, 그리고 생성된 개체가 해당 형식을 직접 구현했거나 해당 형식이 생성된 개체 형식보다는 추상 형식이어야 한다는 것입니다.


이것을 사용하여 예상할 수 있는 형식 몇가지를 검사해 보는 것이 가능합니다. 하지만 여기에는 몇 가지 한계가 있습니다.



  • 찾고자 하는 형식이 상속을 활용한다면 정확히 들어맞는 형식임을 보장할 수는 없게 된다.
  • 찾고자 하는 형식의 수가 많다면 굉장히 많은 수의 조건문이나 3항 연산자를 중첩 사용해야 한다.
  • 찾고자 하는 형식이 컴파일러가 인지할 수 있는 이름 목록 가운데 없으면 이 방법은 사용할 수 없다.

이런 문제점을 해결하기 위해서 몇가지 방법을 더 찾아야 하겠습니다.


오늘 강좌는 우선 여기까지 쓰도록 하겠습니다. 다음 강좌를 보시기 전까지 과제가 하나 있습니다. 과연 위에서 언급했던 문제점을 해결하기 위하여 어떤 방법을 어떻게 활용하여야 할지에 대해서 진지하게 고민해 보십시오. 다음 강좌에서 그 답을 속시원하게 알려드리도록 하겠습니다. ^^

댓글 남기기