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 라는 문자열이 그대로 유지된 것을 확인하실 수 있습니다.
위의 예에서 중요한 원리 하나를 확인할 수 있습니다. 첫 번째는 형변환에는 방향성이 존재한다는 것입니다. 형식의 변환이 추상화될 수록 (위로 갈수록) 가급적 공통 부분으로만 표현됩니다. 반대로 형식의 변환이 구체화될 수록 (아래로 갈수록) 가급적 상세하게 표현됩니다.
데브피아 남정현님