본문 바로가기

.NET/C# Reflection

[C# Reflection] Reflection이란?

리플렉션(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 클래스가 아닌 다른 클래스의 개체를 지정하더라도 말이다. (직접 위 코드를 복사해서 실행해보자.)

 

이번 글에서는 리플렉션의 기능을 맛보기 식으로 알아봤다.

다음 글에서 위 코드에서 사용한 리플렉션의 기능들을 상세히 짚어보도록 하겠다.