ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 닷넷 클래스 리플렉션 reflection
    .NET/C# Basic 2008. 10. 15. 18:00
    반응형
    클래스 라이브러리   
    닷넷 기본 클래스 라이브러리를 사용하면 개발 시간을 많이 단축할 수 있다 
    제공된 클래스를 생성하거나 상속받아서 새로운 클래스를 정의 

    System 네임스페이스는 기본 클래스를 모아둔 네임스페이스
    System.Object 객체를 비롯해서 기본 데이터형과 같은 기본 클래스가 정의  




    리플렉션

    GetType 메서드는 클래스를 나타내는 Type 객체를 리턴하는 메서드
     Type 객체를 이용하면 실행 타임에 클래스에 대한 다양한 정보를 구할 수 있다
     
    Type type = pt.GetType();
         FieldInfo [] finfo = type.GetFields();

    Type 객체로부터 클래스 정보를 구할 수 있는 방법을 리플렉션(reflection) 이라 한다
    리플렉션을 통해서 알아낼 수 있는 정보에는 네임스페이스, 메서드, 필드, 생성자
    그리고 각 메서드에 들어가는 인자 리스트 등등이 있다

     
    리플렉션

    ////////////

    리플렉션이란 실행중에 클래스나 객체의 타입 정보를 조사하는 기능이다, 타입정보는 보통컴파일 중에만 사용되며 커파일러에 의해 기계어로 바뀌고 나면 사라지는 것이 일반적이다. 그러나 C#은 컴파일된 결과 코드뿐만 아니라 타입에 대한 메타 데이터를 실행 파일에 같이 기록해놓기 때문에 실행중에도 정보를 조사 수 있다.이기능을 사용하면 실행중에 다른 모듈에 선언된 인스턴스를 생성할 수 있고 메서드를 호출 수도 있다.

     클래스에 대한 타입 정보는 Type이라는 클래스 또는 그 파생 클래스로 표현한다. 모든것이 객체 여야 하므로 클래스의 정보를 표현하는 것도 클래스이다. Type은 추상클래스로서 타입을 표현하는 대표 클래스 역할을 하며 실제 정보는 type파팽 클래스들이 가진다. 이클래스들이 제공하는 정보는 모두 읽기 전용이므로 정보를 조사할 수만 있으며 실행중에 변경할 수는 없다. 특정 클래스나 객체의 타입 정보를 조사하고 싶을 떄는 Type 객체를 먼저 얻어야 하는데 다음 세가지 방법으로 얻는다

    1. 루트 클래스인 object 의 GetType메서드를 호출한다. 정적메서드가 아니므로 객체가 있어야만 이 메서드를 호출 할 수 있다 Time형의 Now객체가 있을때 Now.GetType()을 호출하면 Type객체가 리턴되며 이객체로부터 Now의 타입 정보를 조사한다.
    2. Type의 정적 메서드인 GetType을 사용한다. 인수로 문자열 형식의 타입 이름을 전달하며 대소문자 구분 여부와 타입이 발견되지 않을 떄 예외 처리 여부를 추가로 지정할 수 있다. 타입의 이름만 전달하면 대소문자를 구분하며 예외를 던지지는 않는다 Time 클래스의 정보를 읽고 싶다면Type.GetType("Time")을 호출한다. 클래스명으로부터 정보를 조사하므로 객체가 없어도 호출할 수 있다
    3. typeof 연산자를 사용하며 인수로 클래스명을 전달하는데 문자열이 아니므로 클래스명을 바로전달하면된다.Time클래스의 경우라면typeof(Time)으로 Type객체를 얻는다. 언어가 제공하는 연산자이므로 가장 간단하다.

    어떤방법을 쓰든 리턴되는 객체는 Type의 파생 클래스 객체이므로 Type형의 객체를 선언한 후 리턴값을 대입받으면 된다. Type 클래스에는 타입에 대한 정보를 제공하는 여러가지 프로퍼티와 메서드들이 제공된다 MSDN의 레퍼런스를 참고하기 바란다

    타입의 이름과 소속된 네임스페이스는 물론이고 부모 클래스와 내부 타입의 이름가지 조사할 수 있는 Is로 시작되는 프로퍼티는 타입이 특정속성을 가지고 있는지를 조사하는데 이름이 아주 설명적이다. 메서드도 수십개나 정의되어 있는데 이메서드를 사용하면 클래스에 어떤 맴버들이 포함되어있는지 상세하게 조사할 수있다. 너무 수가 많으므로 일일이 설명할수는 없고 대표적으로  GetFields 메서드에 대새서만 알아보자

    public FieldInfo[] GetFields()

    이메서드는 클래스에 포함된 필드의 목록을 조사한다. 개별 필드하나에 대한 정보는FieldOnfo 클래스로 표현하므로 FieldInfo의 배열을 통해 모든 필드에 대한 정보를 리턴 수 있다 FieldInfo는 또 다른 클래스인데 필드의 여러가지 정보를 조사하는 프로퍼티와 메서드들이 맴버로 포함되어있다 
    레퍼런스를 몇가지 예만 들면 Name은 필드의 이름이고 IsPublic, IsPrivate, IsStatic은 필드에 어떤 지정자가 적용되었는지를 조사한다.

     FiedlType 프로퍼티는 필드의 타입인데 이프로퍼티가 리턴하는 것은 또다른 Type 객체이다 클래스의 타입 정보로부터 필드 목록을 구했는데 필드의 타입이 또 다른 타입정보를 가지므로 재귀적이다. 클래스의 객체가 다른 클래스에 포함될 수 있고 이클래스를 포함하는 또 다른 외부 클래스가 있을 수도 있기 때문이다. 그래서 타입을 조사하는 과정도 여러단계로 중첩될 수 밖에 없다.

    개별 필드를 조사할 때는 다음
    메서드를 호출한다.

    public abstract FieldInfo GetField(string name, BindingFlags bindingAttr)
                                                            필드이름         필드를 검색하는방법 지정
    첫번째 인수는 필드의 이름이며 두번째 인수는 필드를 검색하는 방법을 지정하는데 생략할 수도 있다
    두번째 인수로 대소문자 구분 여부와 속성을 지정하는데 공개된 필드만 조사한다든가 정적필드만 조사한다든가 할 수 있다. 필드를 조사하는 메서드외에도 GetEvent(s), GetProperty(ies), GetInterface(s), GetConstructor(s) ,GetMember(s) 등의 메서드들이있는데 리턴하는 타입만다를뿐 GetField(s)와 사용방법은 동일하다


     타입 정보로부터 모든 맴버의 정보를 시시콜콜하게 조사할 수있으므로 실행중에 클래스 선언문을 복원해낼수 있을정도다 . 다음예제는 Time 클래스의 정보를 조사하여 화면으로 출력한다. Time 클래스는 지금까지 줄곧 사용해왔으므로 늘 봐오던 그대로이되 단, 네임스페이스를 조사할 수 있다는 것을 보이기 위해 네임스페이스 안에 선언했다.


    using System;
    using System.Reflection;
    using MySpace;
    namespace MySpace
    {
     class Time
     {
      public int hour;
      public int min;
      public int sec;
      public Time(int h, int m, int s)
      {
       this.hour = h;
       this.min = m;
       this.sec = s;
      }
      public void OutTimeTypeime()
      {
       Console.WriteLine("현재시간 {0}시{1}분{2}초", hour,min,sec);
      }
     }
     class CStest
     {
      static void Main()
      {
       Time Now = new Time(1,3,5);
       Type TT = Now.GetType();                         // 같은 결과값 3가지방법
    //Type TT = Type.GetType("MySpace.Time"); // 같은 결과값 3가지방법
    //Type TT = typeof(MySpace.Time);              // 같은 결과값 3가지방법
       Console.WriteLine(TT.Name); // 현제 맴버의 이름을 가져온다
       Console.WriteLine(TT.FullName); //어셈블리를 제외한 모든정규화된type을 가져온다
       Console.WriteLine(TT.Namespace); //system.네임스페이스가져온다
       Console.WriteLine(TT.BaseType.Name); // 직접상속된형식가져온다
       Console.WriteLine(TT.UnderlyingSystemType);// cls에서 제공되는 형식을나타낸다
       if (TT.IsValueType)
        Console.WriteLine("값타입입니다");

       FieldInfo[] TF = TT.GetFields();
       for (int i=0; i < TF.Length; i++ )
       {
        Console.WriteLine("{0}번째 필드 = {1}",i ,TF[i].Name );
       }
       MethodInfo[] TM = TT.GetMethods();
       for (int i = 0;i < TM.Length ; i++ )
       {
        Console.WriteLine("{0}번째 메서드 = {1}", i, TM[i].Name);
       }
      }
     }
    }
    리플렉션 관련 클래스들은 System.Refection 네임스페이스에 정의되어 있으므로 이기능을 사용하려면 using 선언을 먼저 해야한다. Main 에서 Time객체 Now를 하나 선언하고 Now의 GetType메서드를 호출하여 Type객체 TimeType을 조사했다주석 처리된 두 행도 똑같은 동작을 하는데 클래스의 이름을 전달 할 때는 소속된 네임스페이스의 이름도 같이 밝혀야한다 조사된 TimeType으로 클래스 자체와 필드 , 메서드의 정보를 조사해 출력했다..

    ========= 실행 결과 ========
    Time
    MySpace.Time
    MySpace
    Object
    MySpace.Time
    0번째 필드 = hour
    1번째 필드 = min
    2번째 필드 = sec
    0번째 메서드 = OutTimeTypeime
    1번째 메서드 = GetType
    2번째 메서드 = ToString
    3번째 메서드 = Equals
    4번째 메서드 = GetHashCode
    =============================
    TimeType객체의 프로퍼티를 읽어 이름, 네임스페이스, 부모 클래스등을 조사해 보았다. 그리고 GetFields와 GetMethods메서드를 호출하여 필드와 메서드의 목록을 조사해 보았다 필드는직접 선언한 건들만 출력되지만 메서드는 object로 부터 상속받은것들도 출력된다 상속받은 맴버도 모두 조사되는데 object에는 필드가 하나도 없다Time 클래스는 구조가 간단해서 이정도만 출력되지만 내부타입을 중첩하고 있다거나 포함된 객체를 가지고 있다면 춸씬 더 복잡해질것이다

    리플렉션 기술은 우리가 직접사용한다기보다는 시스템에 의해 사용되는 것이으로 세부적인 사항에 대해서 당장 상세하게 연구 할 필요까는 없다. 물론 가끔은 리플렉션을 직접해야하는 경우도 있는데 필요할 떄 연구해 보아라. 관련 클래스들이 워낙 많고 방대해서 레퍼런스를 참고하면서 사용해야한다. 중요한것은 닷넷에는 실행중에 타입의 정보를 조사할수 있다는 것이며 이로 인해 여러가지 안정적인  타입 관련 작업이 가능하다는 것이다

    아래예제를보며 알아보자 이 예제는 Equals 를 재정의하여 시간 객체의 내용끼리 비교하는데 Equals의 본체 코드가 너무 대충 작성되어 있어 위험하다. Main 다음과 같이 수정한 후 테스트해 보자

    class CStest
    {
             static void Main()
            {
                    Time A = new Time(1,3,5);
                    Time B = null;
                    A.Equals(B);
    }
    }

    비교 대상 객체인 B를 제대로 초기화하지 않고 null을 초기화 한 후 Equals로 전달했다 이렇게 하면 다운되어 버리는데 왜냐면 Equals는 인수로 받은 obj를 (Time)으로 무조건 캐스팅함으로써 null에 대한 처리를 하지 않기 때문이다  또한 A.Equals("언제든지") 처럼 Time 객체가 아닌 엉뚱한 객체를 전달해도 마찬가지로 다운된다 Equals는 object 타입을 인수로 취하므로 임의의 객체를 다대입받을 수 있기 떄문에 컴파일 에러는 아니다.

    하지만 문자열에 hour, min, sec 같은 맴버가 있을 리가 없으므로 다운되어 버리는 것이다. 문제는 전달받은 인수를 강제캐스팅하다는데 있다.
    Time Other =(Time)obj 문에서 obj 반드시 Time의 객체이며 null이 아니라는 보장이 없는 것이다. 어떤 경우라도 다운되지 않도록 하려면 obj의 타입정보를 실행 중에 조사해야한다. Equals의 선두에 다음문장을 추가해 보자

    public override bool Equals(object obj)
    {
              if(obj == null || obj.GetType() != this.GetType())
                  return false;
              Time Other = (Time)obj;
               return (Other.hour == hour  && Other.min = min && Other.sec == sec);
    }

    obj가 null이거나 또는obj의 타입정보가 이 클래스의 타입과 다를 경우 무조건 false를 리턴하도록 에러 처리했다 이런조건처리가 가능하려면 인수로 전달된 obj의 타입정보를 실행중에 조사할 수 있어야하는데 이 기능이 바로 리플력션인것이다 좀더 간단하게 작성한다면 다음고 같이 깔끔하게 정리할 수 있다

    public override bool Equals(object obj)
    {
               Time Other = obj as Time;
                if(Other == null)  return false;
                return (Other.hour == hour && Other.min == min && Other.sec ==sec);
    }


    as 연산자로 안전하게 캐스팅해 보고 null이 리턴되면 obj는 제대로 된 Time 객체가 아니라고 판단할 수 있다
    is, as 같은 연산자들이 동작할 수 있는 이유도 리플력션이 가능하기때문이다
    as 연산자는 내부적으로 리플렉션을 통해 obj의 타입 정보를 조사하여 obj 가 Time 의 객체가 맞는지를 판별할 것이다
    .
    그래서 우리는 is, as연산자만 잘 써먹어도 리플렉션의 이점을 충분히 활용할 수 있다.

    /////////////// 리플렉션 예제 ///////////////////
     
    using System;
    using System.Reflection;


    namespace Reflection
    {
    public class simple
    {
    public simple(){} //생성자
    public int n; //필드
    public void SetCount(int n){} //메서드
    class class1
    {
    static void Main()
    {
    //type 객체 구하기
    Type type1 = typeof(simple);
    //필드 정보 구하기
    FieldInfo [] finfo = type1.GetFields();
    foreach(FieldInfo f in finfo )
    {
    Console.Write(f.IsPublic ? "public?":"");
    Console.WriteLine(f);
    }
    //메서드 정보구하기
    MethodInfo[] minfo = type1.GetMethods();


    foreach (MethodInfo m in minfo)
    {
    Console.Write(m.IsPublic ? "public!!" : "");
    Console.WriteLine(m);
    }
    }
    }
    }
    }








    반응형

    댓글

Designed by Tistory.