[C#] 리플렉션
댓글 0
댓글을 작성하려면 로그인이 필요합니다.
아직 댓글이 없습니다. 첫 번째 댓글을 작성해보세요.
리플렉션은 런타임에 타입 정보를 조회하고 조작하는 기능이다. System.Reflection 네임스페이스에 관련 클래스가 모여 있다.
일반적인 코드는 컴파일 시점에 어떤 타입의 어떤 멤버를 쓸지 결정된다. 리플렉션은 이 결정을 실행 시점으로 미룬다. 클래스가 어떤 필드와 메서드를 가지고 있는지 문자열 이름으로 찾아낼 수 있고, 접근 제한자와 무관하게 값을 읽거나 메서드를 호출할 수 있다. 타입 이름만으로 객체를 생성하는 것도 가능하다.
Player instance = new Player();
Type type = typeof(Player);
// private 필드 읽기
FieldInfo field = type.GetField("hp", BindingFlags.NonPublic | BindingFlags.Instance);
int hp = (int)field.GetValue(instance);
// 메서드 호출
MethodInfo method = type.GetMethod("TakeDamage");
method.Invoke(instance, new object[] { 10 });
// 타입 이름으로 객체 생성
Type resolved = Type.GetType("MyGame.Player");
object created = Activator.CreateInstance(resolved);
Type 객체가 시작점이다. typeof, GetType(), Type.GetType("네임스페이스.클래스명")으로 얻는다. 여기서 GetField, GetMethod, GetProperty, GetCustomAttributes 등을 호출해 멤버 정보를 가져온다.
| 클래스 | 역할 |
|---|---|
Type | 타입 자체의 정보. 모든 조회의 진입점 |
MemberInfo | 모든 멤버 정보의 공통 부모. 이름, 선언 타입, 어트리뷰트 조회 |
FieldInfo | 필드 정보. GetValue / SetValue |
PropertyInfo | 프로퍼티 정보. getter/setter 접근 |
MethodInfo | 메서드 정보. Invoke로 호출 |
Assembly | 어셈블리 단위 조회. 포함된 타입 목록 열거 |
BindingFlags | 검색 조건 지정. public/non-public, static/instance 등 |
BindingFlags를 생략하면 public 인스턴스 멤버만 검색된다. private 멤버를 찾으려면 NonPublic과 Instance를 함께 지정해야 한다.
어트리뷰트는 MemberInfo.GetCustomAttributes로 조회한다. 어트리뷰트 자체는 컴파일된 메타데이터로만 남아 있고, 이를 읽어 실제 동작으로 연결하는 쪽은 전부 리플렉션이다.
리플렉션은 대상 타입을 미리 알 수 없는 코드에서 쓰인다. 라이브러리나 프레임워크가 사용자가 정의한 임의의 타입을 다뤄야 할 때가 대표적이다.
[Test] 같은 어트리뷰트가 붙은 메서드를 찾아 실행한다.공통점은 라이브러리 작성 시점에 사용자 타입이 존재하지 않는다는 것이다. 이 간극을 메우는 게 리플렉션의 본래 역할이다.
리플렉션 호출은 직접 호출보다 크게 느리다. 멤버를 이름으로 검색하는 과정과 Invoke 시의 박싱·인자 배열 할당이 원인이다. 성능이 중요한 경로에 그대로 넣을 물건이 아니다.
쓴다면 초기화 시점에 MethodInfo나 FieldInfo를 캐싱해두고 재사용한다. 더 나아가 Delegate.CreateDelegate로 델리게이트를 만들어두면 호출 비용을 상당히 줄일 수 있다. 실제로 성능을 신경 쓰는 직렬화 라이브러리들은 첫 호출에서 타입 정보를 분석한 뒤 델리게이트나 동적 생성 코드로 바꿔 캐싱하는 방식을 쓴다.
멤버를 문자열로 지정하기 때문에 이름을 변경해도 컴파일러가 잡아주지 않는다. 오류는 런타임에 null 참조나 예외로 나타난다. nameof 연산자로 일부 완화할 수 있지만, 접근 가능한 멤버에 한한다. 다른 클래스의 private 멤버처럼 애초에 코드에서 참조할 수 없는 대상에는 쓸 수 없다.
NativeAOT나 PublishTrimmed 같은 환경은 코드상에서 직접 참조되지 않는 멤버를 빌드 결과물에서 제거한다. 리플렉션으로만 접근하는 타입은 정적 분석에 잡히지 않아 사라질 수 있다. 개발 환경에서는 동작하는데 배포 빌드에서만 실패하는 형태로 드러나기 때문에 발견이 늦다. 해당 환경을 쓴다면 보존 대상을 명시적으로 지정해야 한다.
private 멤버 접근은 리플렉션의 기능이지만, 동시에 남용하기 쉬운 지점이다. 대상 클래스의 내부 구현에 의존하게 되므로 그쪽이 바뀌면 조용히 깨진다.
타입에 따라 분기하거나 객체를 생성해야 한다면, 리플렉션보다 명시적인 방식이 대체로 낫다.
Dictionary<string, Func<T>>에 생성자를 등록하는 팩토리특히 Source Generator는 리플렉션이 런타임에 하던 일을 컴파일 시점으로 옮기는 접근이다. 속도와 타입 안전성을 모두 확보할 수 있어 최근 직렬화 라이브러리들이 이 방향으로 옮겨가고 있다.
리플렉션은 대상 타입을 미리 알 수 없는 프레임워크·라이브러리 레벨 코드에서 값어치가 있는 도구다. 애플리케이션 코드에서 쓰고 있다면 설계로 풀 수 있는 문제가 아닌지 한 번 의심해볼 만하다.
C심화 면접 대비(4)
C심화 면접 대비 (3)
C심화 면접 대비(2)