VisualStudio/C#

[C#] 리플렉션 개념 및 사용법(Reflection)

usingsystem 2022. 9. 14. 16:48
728x90

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

 

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

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

 

출처 - https://cs-solution.tistory.com/20

728x90