C# 리플렉션(Reflection) 마스터하기: 런타임 타입 발견과 동적 프로그래밍
리플렉션(Reflection) 객체는 런타임에 타입 정보를 얻는 데 사용됩니다. 실행 중인 프로그램의 메타데이터에 접근하는 클래스들은 System.Reflection 네임스페이스에 있습니다.
System.Reflection 네임스페이스는 애플리케이션 정보를 얻고, 런타임에 동적으로 타입, 값, 객체를 추가할 수 있는 클래스들을 포함합니다.
리플렉션의 응용
- 런타임에 어트리뷰트 정보 보기
- 어셈블리의 다양한 타입 조사 및 인스턴스화
- 메서드와 프로퍼티에 지연 바인딩(Late Binding)
- 런타임에 새 타입 생성 후 작업 수행
메타데이터 보기
앞 장에서 언급했듯 리플렉션으로 어트리뷰트 정보를 볼 수 있습니다.
System.Reflection.MemberInfo 객체를 초기화하여 클래스와 연관된 어트리뷰트 발견. 대상 클래스 객체 정의:
System.Reflection.MemberInfo info = typeof(MyClass);
기본 예제: 어트리뷰트 읽기
using System;
[AttributeUsage(AttributeTargets.All)]
public class HelpAttribute : System.Attribute
{
public readonly string Url;
public string Topic { get; set; }
public HelpAttribute(string url) { Url = url; }
}
[HelpAttribute("Information on the class MyClass")]
class MyClass { }
namespace AttributeAppl
{
class Program
{
static void Main(string[] args)
{
System.Reflection.MemberInfo info = typeof(MyClass);
object[] attributes = info.GetCustomAttributes(true);
for (int i = 0; i < attributes.Length; i++)
{
Console.WriteLine(attributes[i]);
}
Console.ReadKey();
}
}
}
출력: HelpAttribute
종합 예제: DeBugInfo 어트리뷰트 리플렉션으로 읽기
using System;
using System.Reflection;
namespace BugFixApplication
{
// 커스텀 어트리뷰트 정의
[AttributeUsage(
AttributeTargets.Class |
AttributeTargets.Constructor |
AttributeTargets.Field |
AttributeTargets.Method |
AttributeTargets.Property,
AllowMultiple = true
)]
public class DeBugInfo : System.Attribute
{
private int bugNo;
private string developer;
private string lastReview;
public string Message { get; set; }
public DeBugInfo(int bg, string dev, string d)
{
bugNo = bg; developer = dev; lastReview = d;
}
public int BugNo => bugNo;
public string Developer => developer;
public string LastReview => lastReview;
}
// 어트리뷰트 적용
[DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")]
[DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")]
class Rectangle
{
protected double length, width;
public Rectangle(double l, double w) { length = l; width = w; }
[DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")]
public double GetArea() { return length * width; }
[DeBugInfo(56, "Zara Ali", "19/10/2012")]
public void Display()
{
Console.WriteLine("Length: {0}", length);
Console.WriteLine("Width: {0}", width);
Console.WriteLine("Area: {0}", GetArea());
}
}
class ExecuteRectangle
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(4.5, 7.5);
r.Display();
Type type = typeof(Rectangle);
// 클래스 레벨 어트리뷰트 읽기
Console.WriteLine("=== 클래스 어트리뷰트 ===");
foreach (object attr in type.GetCustomAttributes(false))
{
if (attr is DeBugInfo dbi)
{
Console.WriteLine($"Bug No: {dbi.BugNo}");
Console.WriteLine($"Developer: {dbi.Developer}");
Console.WriteLine($"Last Reviewed: {dbi.LastReview}");
Console.WriteLine($"Remarks: {dbi.Message}");
}
}
// 메서드 레벨 어트리뷰트 읽기
Console.WriteLine("\n=== 메서드 어트리뷰트 ===");
foreach (MethodInfo m in type.GetMethods())
{
foreach (Attribute a in m.GetCustomAttributes(true))
{
if (a is DeBugInfo dbi)
{
Console.WriteLine($"Bug No: {dbi.BugNo}, Method: {m.Name}");
Console.WriteLine($"Developer: {dbi.Developer}");
Console.WriteLine($"Last Reviewed: {dbi.LastReview}");
Console.WriteLine($"Remarks: {dbi.Message}");
}
}
}
Console.ReadLine();
}
}
}
출력:
Length: 4.5
Width: 7.5
Area: 33.75
=== 클래스 어트리뷰트 ===
Bug No: 49, Developer: Nuha Ali, Last Reviewed: 10/10/2012, Remarks: Unused variable
Bug No: 45, Developer: Zara Ali, Last Reviewed: 12/8/2012, Remarks: Return type mismatch
=== 메서드 어트리뷰트 ===
Bug No: 55, Method: GetArea, Developer: Zara Ali, Last Reviewed: 19/10/2012, Remarks: Return type mismatch
Bug No: 56, Method: Display, Developer: Zara Ali, Last Reviewed: 19/10/2012, Remarks:
리플렉션 핵심 클래스
| 클래스 | 용도 |
Type | 타입 정보의 루트. 타입 메타데이터 접근 시작점 |
Assembly | 어셈블리 로드, 타입 열거, 리소스 접근 |
MemberInfo | 멤버(필드, 메서드, 프로퍼티 등) 공통 기본 클래스 |
FieldInfo | 필드 정보 (이름, 타입, 값 읽기/쓰기) |
MethodInfo | 메서드 정보 (호출, 매개변수, 반환 타입) |
PropertyInfo | 프로퍼티 정보 (값 읽기/쓰기) |
ConstructorInfo | 생성자 정보 (인스턴스 생성) |
ParameterInfo | 매개변수 정보 |
EventInfo | 이벤트 정보 |
CustomAttributeData | 어트리뷰트 데이터 (로드하지 않고 검사 가능) |
주요 리플렉션 작업
타입 정보 얻기
Type t = typeof(string); // 컴파일 타임 타입에서
Type t2 = "hello".GetType(); // 인스턴스에서
Type t3 = Type.GetType("System.String"); // 문자열 이름으로
멤버 열거
Type t = typeof(MyClass);
// 모든 public 멤버
MemberInfo[] members = t.GetMembers();
// 특정 멤버 타입만
MethodInfo[] methods = t.GetMethods();
PropertyInfo[] props = t.GetProperties();
FieldInfo[] fields = t.GetFields();
ConstructorInfo[] ctors = t.GetConstructors();
// 바인딩 플래그로 비공개 포함
var allMethods = t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
동적 인스턴스 생성 및 메서드 호출
Type t = typeof(MyClass);
// 기본 생성자로 생성
object obj = Activator.CreateInstance(t);
// 매개변수 있는 생성자
object obj2 = Activator.CreateInstance(t, new object[] { "arg1", 123 });
// 메서드 호출
MethodInfo method = t.GetMethod("MyMethod");
object result = method.Invoke(obj, new object[] { "param1" });
// 프로퍼티 설정/가져오기
PropertyInfo prop = t.GetProperty("Name");
prop.SetValue(obj, "New Name");
string name = (string)prop.GetValue(obj);
제네릭 타입 다루기
Type openGeneric = typeof(List<>);
Type closedGeneric = openGeneric.MakeGenericType(typeof(int)); // List
object list = Activator.CreateInstance(closedGeneric);
MethodInfo add = closedGeneric.GetMethod("Add");
add.Invoke(list, new object[] { 42 });
성능 고려사항
- 리플렉션은 느립니다 (직접 호출 대비 10~100배). 핫 경로에서는 캐싱 필수
MethodInfo, PropertyInfo 등은 static readonly 필드로 캐싱
- 자주 쓰는 경우
Delegate.CreateDelegate로 델리게이트 변환 후 호출
DynamicMethod나 Expression Tree로 더 빠른 동적 코드 생성 가능
// 캐싱 예제
private static readonly MethodInfo CachedMethod = typeof(MyClass).GetMethod("MyMethod");
public void FastInvoke(MyClass obj, string param)
{
CachedMethod.Invoke(obj, new object[] { param });
}
// 델리게이트 변환으로 더 빠르게
private static readonly Func CachedDelegate =
(Func)Delegate.CreateDelegate(
typeof(Func),
typeof(MyClass).GetMethod("MyMethod")
);
public int FastestInvoke(MyClass obj, string param)
{
return CachedDelegate(obj, param);
}
핵심: 리플렉션은 플러그인 아키텍처, 직렬화, ORM, 테스트 프레임워크, 의존성 주입 컨테이너 등 프레임워크/라이브러리 개발에 필수적입니다. 애플리케이션 코드에서는 신중히 사용하세요.