리플렉션(Reflection)은 어떤 Type에 대한 정보를 가져오거나 접근하는 등의 작업을 런타임에 동적으로 수행할 수 있도록 해주는 기능이다. 리플렉션을 사용하면 런타임에서 메서드를 호출하거나 필드의 값을 바꾸는 등의 작업을 할 수 있다.
실제로 어셈블리는 미리 생성된 스크립트를 기준으로 생성이 되며, 어셈블리가 동작할 때 메서드가 호출되거나 필드의 값을 변경하는 행위는 모두 프로그래머가 어셈블리를 빌드하기 전에 스크립트에 정의해놓은 일련의 작업일 뿐이다.
예를 들어 클래스 A에 존재하는 a, b, c라는 세 개의 필드에 사용자로부터 값을 입력받아 지정하고자 한다면 프로그래머는 클래스 A의 구성 요소에 대해 알고 있고, 수행해야 할 동작들에 대해서도 이미 정의되어 있기 때문에 아래와 같이 코드를 작성할 수 있다.
using System;
namespace ConsoleApp1 {
public class A
{
public int a;
public int b;
public int c;
}
class Program {
static void Main(string[] args)
{
var obj = new A();
var select = -1;
Console.WriteLine("어떤 필드의 값을 변경할까?");
Console.WriteLine("1. 필드 a");
Console.WriteLine("2. 필드 b");
Console.WriteLine("3. 필드 c");
Console.Write("=> ");
if(int.TryParse(Console.ReadLine(), out select) && select >= 1 && select <= 3)
{
switch(select)
{
case 1:
if(getValue(out int tmp))
Console.WriteLine("a의 값을 " + (obj.a = tmp).ToString() + "로 변경했습니다.");
else Console.WriteLine("값을 변경하지 못했습니다.");
break;
case 2:
if(getValue(out tmp))
Console.WriteLine("b의 값을 " + (obj.b = tmp).ToString() + "로 변경했습니다.");
else Console.WriteLine("값을 변경하지 못했습니다.");
break;
case 3:
if(getValue(out tmp))
Console.WriteLine("c의 값을 " + (obj.c = tmp).ToString() + "로 변경했습니다.");
else Console.WriteLine("값을 변경하지 못했습니다.");
break;
}
}
else
{
Console.WriteLine("잘못 입력하셨습니다.");
}
}
static bool getValue(out int value)
{
Console.WriteLine("변경할 값은?(정수만 입력)");
Console.Write("=> ");
if(int.TryParse(Console.ReadLine(), out value)) return true;
else return false;
}
}
}
위 코드에서는 단순히 이미 알고있는 필드와 형식에 대해 미리 정의된 동작만을 수행한다.
리플렉션은 위와 다르게 형식에 대해 정의된 필드나 메서드를 찾아서 값을 가져오거나 설정하고 호출할 수 있다. 위처럼 각 필드에 대해 일일이 미리 코드를 구현할 필요조차 없다. (위 코드에서는 a, b, c 필드에 대해 각각 switch-case가 생성되어 있다.)
코드가 길어지겠지만 리플렉션을 사용하면 아래와 같은 코드가 된다.
using System.Reflection; // 리플렉션을 사용하기 위해 추가
using System.Linq; // Where() 함수를 사용하기 위해 추가
using System;
namespace ConsoleApp1
{
public class A
{
public int a;
public int b;
public int c;
}
class Program
{
static void Main(string[] args)
{
var obj = new A();
var select = -1;
Console.WriteLine("어떤 필드의 값을 변경할까?");
// 코드 한 줄이 길어 알아보기 어려운 독자들을 위해 . 단위로 줄바꿈하였다. 실제로는 한 줄로 적어도 무방하다.
var fields = obj.GetType()
.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.Where(i => i.FieldType == typeof(int)) // 여기서는 정수 형식 필드만 사용하자.
.ToArray();
for(var i = 0; i < fields.Length; i++)
Console.WriteLine($"{i + 1}. 필드 {fields[i].Name}");
Console.Write("=> ");
if(int.TryParse(Console.ReadLine(), out select) && select >= 1 && select <= fields.Length)
{
if(getValue(out int value))
{
fields[select - 1].SetValue(obj, value);
Console.WriteLine($"{fields[select - i].Name}의 값을 {value}로 변경했습니다.");
}
else
{
Console.WriteLine("값을 변경하지 못했습니다.");
}
}
else
{
Console.WriteLine("잘못 입력하셨습니다.");
}
}
static bool getValue(out int value)
{
Console.WriteLine("변경할 값은?(정수만 입력)");
Console.Write("=> ");
if(int.TryParse(Console.ReadLine(), out value)) return true;
else return false;
}
}
}
이렇게 하면 최대의 장점은, 클래스 A에 필드가 추가되거나, 제거되거나, 변경되더라도 코드 부분은 이를 반영하여 결과를 표시할 수 있다는 것이다. 심지어 obj에 A 클래스가 아닌 다른 클래스의 개체를 지정하더라도 말이다. (직접 위 코드를 복사해서 실행해보자.)
이번 글에서는 리플렉션의 기능을 맛보기 식으로 알아봤다.
다음 글에서 위 코드에서 사용한 리플렉션의 기능들을 상세히 짚어보도록 하겠다.
'.NET > C# Reflection' 카테고리의 다른 글
[C# Reflection] 런타임에 객체 생성하기 (0) | 2022.10.14 |
---|---|
[C# Reflection] GetValue()와 SetValue() 메서드 (0) | 2021.09.20 |
[C# Reflection] GetField(), GetFields() 메서드와 FieldInfo 클래스 (0) | 2021.09.20 |