public class Exam
{
    public int korean;
    public int english;
    public int math;
}
public class ExamWrite
{
    public void Write(Exam exm, string subject)
    {
        if(subject == "korean")
            Console.WriteLine("korean : {0}", exm.korean);
        if(subject == "english")
            Console.WriteLine("english : {0}", exm.english);
        if(subject == "math")
            Console.WriteLine("math : {0}", exm.math);
    }
}
와 같은 프로그램이 있다고 해 봅시다. subject를 검색해서 같은 이름의 멤버변수값을 출력하는 프로그램이죠.
그런데 만약 과목이 수십개라면 어떨까요? 저런 if문이 수십개가 들어가야 합니다. 이럴 경우에는 문자열로 직접 변수이름을 가져올 수 있습니다. 이런 기능을 reflection이라고 합니다. 그러므로 다음과 같은 using문이 필요합니다.
using System.Reflection;
            Type tp = typeof(Exam);
다음에는 이 Exam이라는 클래스의 정보가 담긴 tp라는 객체에서 필드의 정보를 얻어와야겠죠. 필드의 정보를 얻기 위해서는 GetField라는 메서드를 사용합니다.
즉 다음 명령어는 subject에 담겨있는 문자열과 같은 이름의 멤버변수를 찾으라는 명령입니다.
            FieldInfo fld = tp.GetField(subject, BindingFlags.Instance |
                                                 BindingFlags.Static |
                                                 BindingFlags.Public |
                                                 BindingFlags.NonPublic);
만약 제대로 찾았다면 fld에는 그 변수에 대한 정보가 들어갑니다. 찾지 못했다면 null이 반환됩니다.
이 변수의 값을 얻기 위해서는
            object point = fld.GetValue(exm);
와 같이 할 수 있습니다. 위에서 찾은 subject라는 변수의 정보를 exm이라는 객체에서 찾아 그 값을 반환하라는 명령이죠. 다만 아직까지 이 필드의 정확한 타입을 알 수 없기에 반환값은 object형입니다. 하지만 이 명령어가 제대로 실행되었다면 (점수는 모두 정수형이므로) point에는 정수형 값이 들어가 있을 것입니다. 그러므로 값을 출력하기 위해서는
            if (point is int)
                Console.WriteLine("{0} : {1}", fld.Name, (int)point);
즉,
using System.Reflection;
....
public class ExamWrite
{
    public void Write(Exam exm, string subject)
    {
        Type tp = typeof(Exam);
        FieldInfo fld = tp.GetField(subject, BindingFlags.Instance |
                                             BindingFlags.NonPublic |
                                             BindingFlags.Public |
                                             BindingFlags.NonPublic);
        object point = fld.GetValue(exm);
        if (point is int)
            Console.WriteLine("{0} : {1}", fld.Name, (int)point);
    }
}
그런데 이런 식으로 하면 한번에 하나의 성적만 출력 가능하죠. 모든 성적을 한번에 출력할 방법은 없을까요?
GetField가 필드 하나의 정보를 얻어오는 것이라면 GetFields는 모든 필드의 정보를 가져오는 메서드입니다. 그러므로 이 함수를 사용하면 모든 필드에 대한 조작을 할 수 있습니다.
using System.Reflection;
....
public class ExamWrite
{
    public void WriteAll(Exam exm)
    {
        Type tp = typeof(Exam);
        FieldInfo[] flds = tp.GetFields(BindingFlags.Instance |
                                        BindingFlags.Static |
                                        BindingFlags.Public |
                                        BindingFlags.NonPublic);
        foreach (var f in flds)
        {
            object point = f.GetValue(exm);
            if (point is int)
                Console.WriteLine("{0} : {1}", f.Name, (int)point);
        }
    }
}
마찬가지로 멤버변수에 값을 넣기 위해서는 FieldInfo의 GetValue 대신 SetValue를 사용할 수 있습니다.
using System.Reflection;
....
public class ExamWrite
{
    public void Perfect(Exam exm)
    {
        Type tp = typeof(Exam);
        FieldInfo[] flds = tp.GetFields(BindingFlags.Instance |
                                        BindingFlags.Static |
                                        BindingFlags.Public |
                                        BindingFlags.NonPublic);
        foreach (var f in flds)
        {
            object point = f.GetValue(exm);
            if (point is int)
                f.SetValue(exm, 100);
        }
    }
}
만약 다음과 같이
public class Exam
{
    public const int korean;
    public const int english;
    public const int math;
}
와 같이 모두 const로 설정되어 있을 경우에도 이상없이 실행됩니다(다만 Perfect메서드에서는 const변수의 값을 바꾸려 시도하고 있으므로 TargetInvocationException이 뜹니다).
---------------------------
마찬가지로 메서드 역시 이름으로 호출이 가능합니다. 이 경우에는 GetField 대신 GetMethod를 사용할 수 있습니다.
        Type tp = typeof(ExamWrite);
        MethodInfo method = tp.GetMethod("WriteAll");
그리고 이 메서드를 호출하기 위해서는 Invoke 메서드를 사용할 수 있죠.
        method.Invoke(ew, new object[] { exm });
using System.Reflection;
....
        static void Main(string[] args)
        {
            Exam exm = new Exam();
            ExamWrite ew = new ExamWrite();
            Type tp = typeof(ExamWrite);
            MethodInfo method = tp.GetMethod("WriteAll");
            method.Invoke(ew, new object[] { exm });
        }
---------------------------
멤버변수나 메서드들은 모두 접근한정자(private, public 등)를 가지고 있습니다. 이 접근한정자는 컴파일시 확인을 하게 됩니다.
그러나 이 방식으로 멤버에 접근하는 것은 컴파일시가 아니라 실행시이기 때문에 접근한정자가 의미가 없습니다. 즉 private로 꼭꼭 숨겨놓은 멤버도 이 방식으로 접근하면 값을 읽어오거나 바꿀 수 있습니다. 이런 것을 주의해야 합니다.
댓글 없음:
댓글 쓰기