[C#] 12.2 리플렉션

리플렉션은 프로그램 런타임에 형식의 멤버를 반영하는 기술입니다. 이 기술은 명시적으로 라이브러리를 로딩하여 사용할 때 이용합니다.

리플렉션을 이용하여 명시적 라이브러리 로딩하여 사용하는 것을 보여주기 위해 다음의 클래스를 정의한 라이브러리를 작성합시다.

솔루션 이름을 리플렉션으로 정하고 ManLib 이름의 클래스 라이브러리 프로젝트를 추가하세요.

솔루션 생성
[그림 12.2] 솔루션 생성

 그리고 ManLib에 기본으로 제공하는 Class1.cs 파일명을 Man.cs로 변경하세요. Microsoft Visual Studio.NET은 자동으로 클래스 이름을 Man으로 변경합니다.

테스트를 Man 형식을 정의합시다.

using System;
namespace ManLib
{
    public class Man
    {
        //상수 멤버를 추가합시다.
        public const int max_iq=200;
        int iq;

        //멤버 속성도 추가합시다.
        public int IQ
        {
            get
            {
                return iq;
            }
            private set
            {
                if (value > max_iq)
                {
                    value = max_iq;
                }
                iq = value;
            }
        }
        public int Num
        {
            get;
            private set;
        }
        public string Name
        {
            get;
            private set;
        }

        //생성자도 추가합시다.
        public Man(string name,int num)
        {
            Name = name;
            Num = num;
            iq = 0;
            Console.WriteLine("{0} 개체 생성", this);
        }

        //공부하다 메서드도 추가하세요.
        public int Study(int scnt)
        {
            Console.WriteLine("Study");
            IQ += scnt;
            return IQ;
        }

        //정적 메서드도 추가합시다.
        static public Man MoreSmart(Man m1, Man m2)
        {
            Console.WriteLine("Man.MoreSmart");
            if (m1.IQ > m2.IQ){    return m1;    }
            return m2;
        }
        public override string ToString()
        {
            Console.WriteLine("ToString");
            return string.Format("이름:{0} 번호:{1} 아이큐:{2}", Name, Num, IQ);
        }
    }
}

이제 ManLib를 명시적으로 로딩하여 사용하는 리플렉션을 알아봅시다. 콘솔 응용 프로그램을 추가하시고 출력 폴더에 ManLib.dll 파일을 직접 추가하세요. 명시적으로 로딩하여 사용한다는 것은 라이브러리 배포자와 라이브러리 사용하는 응용 배포자가 다를 때 많이 사용합니다.

예를 들어 미디어 플레이어 응용과 코덱 라이브러리는 배포자가 다를 수 있습니다. 미디어 플레이어 응용에서 미디어를 선택하여 재생 버튼을 누르면 미디어의 압축 형태에 맞는 코덱 라이브러리를 로딩하여 재생합니다. 만약 코덱 라이브러리가 없으면 코덱이 없다고 메시지 창을 띄워줍니다.

이처럼 동작하려면 미디어 플레이어 응용은 코덱을 참조하여 구현하는 것이 아니라 명시적으로 로딩하여 사용하게 구현해야 합니다. 만약 참조하여 구현하면 응용이 시작할 때 해당 라이브러리가 없으면 예외를 발생하여 프로그램은 종료합니다.

따라서 추가한 콘솔 응용 프로그램에 ManLib를 참조 추가하지 마시고 ManLib.dll 파일을 복사하여 테스트 콘솔 응용 프로그램의 출력 폴더에 붙여넣기 하세요.

이제 테스트 프로그램의 진입점에 명시적으로 ManLib를 로딩합시다. 명시적으로 라이브러리를 로딩하려면 Assembly 클래스의 정적 메서드 Load를 호출합니다. 만약 명시한 라이브러리가 없으면 예외를 발생합니다.

try
{
    Assembly asm = Assembly.Load("ManLib");
}
catch
{
    Console.WriteLine("ManLib가 없습니다.");
}

Assembly 개체의 GetTypes 메서드를 호출하면 public으로 접근 지정한 형식 목록을 반환합니다.

Type[] types = asm.GetTypes();
Console.WriteLine("ManLib에 제공하는 형식들");
foreach (Type type in types)
{
    Console.WriteLine(type);
}

Assembly 개체의 GetType 메서드에 네임스페이스와 형식 이름을 전달하면 형식 개체를 반환합니다.

Type man_type = asm.GetType("ManLib.Man");
if (man_type == null)
{
    Console.WriteLine("Man 형식 리플렉션 실패");    
}
else
{
    Console.WriteLine("Man 형식 리플렉션 성공");    
}

Type 형식 개체의 GetMembers 메서드를 호출하면 public 접근 지정한 목록을 얻을 수 있습니다.

MemberInfo[] mis = man_type.GetMembers();
Console.WriteLine("Man 형식의 멤버 목록");
foreach (MemberInfo mi in mis)
{
    Console.WriteLine("   {0}",mi);
}

▶ 실행 결과

Man 형식의 멤버 목록
    Int32 get_IQ()
    Int32 get_Num()
    System.String get_Name()
    Int32 Study(Int32)
    ManLib.Man MoreSmart(ManLib.Man man, ManLib.Man)
    System.String ToString()
    Boolean Equals(System.Object)
    Int32 GetHashCode()
    System.Type GetType()
    Void .ctor(System.String, Int32)
    Int32 IQ
    Int32 Num
    System.String Name
    Int32 max_iq

멤버 속성은 컴파일러 번역 과정에서 만들어진 메서드도 존재함을 기억합시다. 그리고 .ctor은 생성자를 말합니다. 기반 형식인 object에 정의한 멤버도 있습니다.

이 외에도 Type 형식에는 많은 멤버를 제공하여 리플렉션을 통해 사용할 수 있습니다.

먼저 리플렉션으로 개체를 생성합시다. 생성자에 입력 인자가 있으면 object 배열 개체를 생성하세요.

object[] objs = new object[] { "홍길동", 27 };

Activator 형식의 정적 메서드 CreateInstance에 생성할 형식을 리플렉션한 Type 개체와 입력 인자 목록을 담은 배열을 전달하면 개체를 생성합니다. 물론 참조 추가한 것이 아니므로 Man 형식 변수를 선언하는 것이 아니라 object 형식 변수를 선언합니다.

object mins = Activator.CreateInstance(man_type,objs);
if (mins == null)
{
    Console.WriteLine("Man 개체 생성 실패");
    return;
}
Console.WriteLine(mins);

리플렉션 형식으로 멤버 속성을 사용하는 방법을 알아봅시다. Type 형식 개체의 GetProperty 메서드를 이용하면 리플렉션 속성 정보를 얻어올 수 있습니다.

PropertyInfo pi = man_type.GetProperty("Num");
if (pi == null)
{
    Console.WriteLine("Num 속성 리플렉션 실패");
    return;
}

리플렉션 속성 개체로 get 블록의 값을 얻어올 때는 GetValue 메서드를 사용합니다. 첫 번째 인자는 리플렉션 형식 개체를 전달하고 두 번째 인자는 null을 전달합니다. 만약 인덱서의 get 블록을 사용할 때는 두 번째 인자도 전달합니다.

object num_val=pi.GetValue(mins, null);
Console.WriteLine("Num:{0}", num_val);

리플렉션 형식으로 멤버 메서드를 호출하는 방법을 알아봅시다. 리플렉션 형식 개체의 GetMethod를 호출하면 리플렉션 메서드 정보 개체를 반환합니다.

MethodInfo study_mi = man_type.GetMethod("Study");
if (study_mi == null)
{
    Console.WriteLine("Study 메서드 리플렉션 실패");
    return;
}

리플렉션 메서드 정보 개체의 Invoke 메서드를 호출합니다. 입력 인자로 인스턴스와 인자 목록을 전달합니다. 그리고 반환 형식은 object로 전달받을 수 있습니다.

object[] sobjs = new object[] { 3 };
Console.WriteLine("결과:{0}",study_mi.Invoke(mins, sobjs));

정적 멤버를 사용하는 방법은 인스턴스를 전달하는 곳에 null을 전달합니다.

MethodInfo smart_mi = man_type.GetMethod("MoreSmart");
if (smart_mi == null)
{
    Console.WriteLine("MoreSmart 정적 메서드 리플렉션 실패");    return;
}
object[] objs2 = new object[] { "강감찬", 3 };
object mins2 = Activator.CreateInstance(man_type, objs2);
if (mins2 == null)
{
    Console.WriteLine("Man 개체 생성 실패");    return;
}
object[] sm_objs = new object[2]{mins,mins2};
Console.WriteLine("MoreSmart 호출 결과:{0}",smart_mi.Invoke(null, sm_objs));

이제 C#언어의 기본적인 부분에 소개를 마쳤습니다. 앞으로 닷넷 리모팅, 마이그레이션 등을 익혀 보다 심화 학습을 해 나가세요.

그리고 ADO.NET, XML.NET, Windows Form with C#, ASP.NET, 웹 서비스를 학습하신 후에 WinFX 기술로 부르는 WPF와 Silverlight, LINQ, WCF 등을 학습하시면 더욱 발전할 수 있을 것입니다. 그리고 이들 기술과 접목할 수 있는 XNA나 Open API, Open CV, Open GL 등의 기술을 익혀 다양한 프로젝트 수행해 보시기 바랍니다. 윈도우즈 시스템 엔지니어를 꿈꾸신다면 DDK나 WDF 등을 학습하는 것도 괜찮을 거예요.

처음 C#을 접하는 이들도 볼 수 있게 책을 구성해서 보다 심화된 내용을 포함하지 못하고 있지만, 반드시 다뤄야 한다고 생각하는 것들은 전달했다고 생각합니다. 아무쪼록 대한민국의 건강한 IT 환경을 구축하는데 밀알이 되길 기대할게요.