리플렉션이란 프로그램 실행 중에 코드의 구조(클래스, 메서드, 프로퍼티 등)에 접근하고, 조작할 수 있게 해주는 .NET의 강력한 기능이다. 즉, 컴파일된 코드(어셈블리)를 런타임에 분석할 수 있다.
이를 통해 객체의 타입, 메서드, 필드, 프로퍼티 등 다양한 정보를 동적으로 가져올 수 있고, 심지어 인스턴스 생성, 메서드 호출 등까지 할 수 있다.
리플렉션을 사용할 때 주로 사용하는 네임스페이스와 클래스는 아래와 같다.
Type 객체 얻기
Type 객체를 얻는 방법은 대표적으로 3가지가 있다.
// 방법 1: 인스턴스에서 얻기
object obj = new MyClass();
Type type1 = obj.GetType();
// 방법 2: 타입에서 바로 얻기
Type type2 = typeof(MyClass);
// 방법 3: 문자열로부터 얻기 (네임스페이스 포함)
Type type3 = Type.GetType("MyNamespace.MyClass");
어셈블리 정보 가져오기
어셈블리 전체에 대한 정보를 얻고 싶으면 아래와 같이 한다.
Assembly asm = Assembly.GetExecutingAssembly(); // 현재 실행중인 어셈블리
Type[] types = asm.GetTypes(); // 어셈블리에 정의된 모든 타입
필드, 프로퍼티, 메서드, 생성자 정보 얻기
Type type = typeof(MyClass);
// 필드
FieldInfo[] fields = type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
// 프로퍼티
PropertyInfo[] properties = type.GetProperties();
// 메서드
MethodInfo[] methods = type.GetMethods();
// 생성자
ConstructorInfo[] ctors = type.GetConstructors();
인스턴스 생성
Type type = typeof(MyClass);
// 매개변수 없는 생성자
object instance = Activator.CreateInstance(type);
// 매개변수가 있는 생성자
object instance2 = Activator.CreateInstance(type, new object[] { "파라미터값" });
메서드 호출
Type type = typeof(MyClass);
object obj = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("MethodName");
method.Invoke(obj, new object[] { /* 파라미터 */ });
필드, 프로퍼티 값 읽기/쓰기
// 필드 읽기/쓰기
FieldInfo field = type.GetField("fieldName", BindingFlags.NonPublic | BindingFlags.Instance);
var value = field.GetValue(obj);
field.SetValue(obj, "새 값");
// 프로퍼티 읽기/쓰기
PropertyInfo prop = type.GetProperty("PropertyName");
var propValue = prop.GetValue(obj);
prop.SetValue(obj, "새 값");
리플렉션에서 필드, 메서드 등 멤버를 찾을 때 접근 범위를 지정해야 한다. 그때 사용하는 게 BindingFlags다.
Public, NonPublic: 공개/비공개 멤버
Instance, Static: 인스턴스/정적 멤버
DeclaredOnly: 선언된 멤버만(상속 제외)
여러 플래그를 | 로 조합해서 사용
public class TestClass
{
private int _num = 10;
public string Name { get; set; }
public void Print(string msg)
{
Console.WriteLine($"{Name}: {msg}");
}
}
Type t = typeof(TestClass);
object inst = Activator.CreateInstance(t);
PropertyInfo pInfo = t.GetProperty("Name");
pInfo.SetValue(inst, "홍길동");
FieldInfo fInfo = t.GetField("_num", BindingFlags.NonPublic | BindingFlags.Instance);
fInfo.SetValue(inst, 999);
MethodInfo mInfo = t.GetMethod("Print");
mInfo.Invoke(inst, new object[] { "안녕!" });
일반 코드보다 속도가 느리다. (런타임에 정보를 분석하기 때문)
코드가 복잡해질 수 있다.
컴파일 타임 타입 체크가 불가, 런타임 에러 위험이 있다.
필요할 때만, 신중하게 사용해야 한다.
실제로 참조에 대한 구조 고민 때문에 리플렉션까지 찾아본 기억이 있다. 강력한 기능으로 보이지만 현실적으로 내가 만들 코드의 수준에서 정말 써야만 하는 순간은 생각보다 찾기가 힘들다.
만약 리플렉션을 사용해야 하는 순간이 찾아온다면 그것보다 먼저 다른 구조나 대안이 없는지 고민을 해보는게 먼저라고 생각한다.