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로 꼭꼭 숨겨놓은 멤버도 이 방식으로 접근하면 값을 읽어오거나 바꿀 수 있습니다. 이런 것을 주의해야 합니다.
댓글 없음:
댓글 쓰기