Reflection은 강력하지만, 잘못 사용하면 성능을 급격히 저하시킨다. 실무에서 Reflection을 사용하는 대부분의 프레임워크는 그대로 호출하지 않는다.
대신 다음과 같은 기법을 사용한다.
- 메타데이터 캐싱
- Expression Tree 컴파일
- IL Emit (동적 메서드 생성)
1️⃣ Reflection이 느린 이유
Reflection 호출은 다음 단계를 거친다.
- 메타데이터 탐색
- 접근 제어 검사
- 런타임 바인딩
- Boxing / Unboxing
즉, 일반 메서드 호출 대비 수십 배 느릴 수 있다.
Reflection 직접 호출 예제
MethodInfo method = typeof(User).GetMethod("GetName");
string name = (string)method.Invoke(user, null);
이 코드는 단순하지만, 반복 호출 시 성능 병목이 된다.
📘 Microsoft 공식 문서: Reflection 개요
.NET의 리플렉션
.NET에서 리플렉션을 검토합니다. 로드된 어셈블리 및 클래스, 인터페이스, 구조체 및 열거형과 같이 그 안에 정의된 형식에 대한 정보를 가져옵니다.
learn.microsoft.com
2️⃣ 기본 최적화: 캐싱
가장 기본적인 최적화는 Reflection 결과 캐싱이다.
static Dictionary<string, MethodInfo> _cache = new();
MethodInfo GetMethod(Type type, string name)
{
string key = type.FullName + name;
if (!_cache.TryGetValue(key, out var method))
{
method = type.GetMethod(name);
_cache[key] = method;
}
return method;
}
하지만 이것만으로는 충분하지 않다. Invoke 자체가 여전히 느리다.
3️⃣ Expression Tree란?
Expression Tree는 코드를 데이터 구조로 표현한 것이다.
이것을 컴파일하면, 런타임에 일반 메서드 호출과 거의 동일한 성능을 얻을 수 있다.
📘 Microsoft 공식 문서: Expression Tree 개요
식 트리 - C#
표현식 트리에 대해 알아봅니다. 각 노드가 식인 이러한 데이터 구조로 표현되는 코드를 컴파일하고 실행하는 방법을 알아보세요.
learn.microsoft.com
Expression Tree로 Method 호출 래핑
using System.Linq.Expressions;
using System.Reflection;
static Func<object, object> CreateGetter(PropertyInfo property)
{
var instance = Expression.Parameter(typeof(object), "instance");
var castInstance = Expression.Convert(instance, property.DeclaringType);
var propertyAccess = Expression.Property(castInstance, property);
var castResult = Expression.Convert(propertyAccess, typeof(object));
return Expression
.Lambda<Func<object, object>>(castResult, instance)
.Compile();
}
이렇게 생성된 Delegate는 Invoke 대신 직접 호출된다.
사용 예
PropertyInfo prop = typeof(User).GetProperty("Name");
var getter = CreateGetter(prop);
string name = (string)getter(user);
Reflection Invoke 대비 10배 이상 빠른 성능을 보인다.
4️⃣ 실무 패턴: Mapper 구현
AutoMapper 같은 라이브러리는 내부적으로 Expression Tree를 활용한다.
foreach (var prop in sourceType.GetProperties())
{
var targetProp = targetType.GetProperty(prop.Name);
if (targetProp == null) continue;
var getter = CreateGetter(prop);
var setter = CreateSetter(targetProp);
// 캐시에 저장
}
이 구조는 Reflection을 단 1회만 사용하고, 이후는 Delegate 호출로 처리한다.
5️⃣ IL Emit이란?
IL Emit은 중간 언어(IL)를 직접 생성하는 방식이다.
Expression Tree보다 더 빠르지만,
- 가독성 낮음
- 유지보수 어려움
- 실수 시 런타임 크래시
때문에 정말 필요한 경우에만 사용한다.
📘 Microsoft 공식 문서: System.Reflection.Emit
System.Reflection.Emit 네임스페이스
컴파일러 또는 도구가 메타데이터 및 MSIL(Microsoft 중간 언어)을 내보내고 필요에 따라 디스크에 PE 파일을 생성할 수 있도록 하는 클래스를 포함합니다. 이러한 클래스의 기본 클라이언트는 스크
learn.microsoft.com
DynamicMethod 예제
DynamicMethod dm = new DynamicMethod(
"GetName",
typeof(string),
new[] { typeof(object) }
);
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, typeof(User));
il.Emit(OpCodes.Callvirt, typeof(User).GetProperty("Name").GetGetMethod());
il.Emit(OpCodes.Ret);
var func = (Func<object, string>)dm.CreateDelegate(typeof(Func<object, string>));
이 방식은 거의 네이티브 호출에 가까운 성능을 낸다.
6️⃣ Expression Tree vs IL Emit
| 구분 | Expression Tree | IL Emit |
|---|---|---|
| 난이도 | 중 | 상 |
| 가독성 | 좋음 | 매우 나쁨 |
| 성능 | 매우 좋음 | 최상 |
| 추천 용도 | 대부분의 실무 | 프레임워크 핵심부 |
7️⃣ 실무 주의사항
- Reflection 결과는 반드시 캐싱
- Expression Compile 비용도 캐싱
- Generic Delegate 활용
- IL Emit은 테스트 필수
8️⃣ 마무리
Reflection은 느리다는 인식은 반은 맞고 반은 틀리다.
제대로 최적화된 Reflection은 매우 빠르다.
이 기법들을 이해하면?
- DI 컨테이너 내부 이해
- ORM 구조 파악
- 고급 면접 질문 대응
까지 자연스럽게 연결된다.
'C#' 카테고리의 다른 글
| Event 메모리 누수와 WeakEvent 패턴 완전 분석 (0) | 2026.02.14 |
|---|---|
| Delegate vs Interface - 설계 관점에서의 선택 기준 (0) | 2026.02.12 |
| C# 비동기 / 병렬 처리 + GC 최적화 실전 가이드 (0) | 2026.02.08 |
| C# 개발자를 위한 고급 메모리 관리: GC를 넘어서 Span과 Memory까지 (0) | 2025.12.14 |
| [GC편 #2] .NET GC 로그 분석 방법 (0) | 2025.12.14 |