1. 소개
리플렉션(영어 Reflection)은 프로그램이 실행 중에 자신의 구조를 조사할 수 있는 기능이에요. 프로그램이 스스로에게 물어보는 걸 상상해보세요: '내가 어떤 메서드들을 가지고 있지? 이 타입에 어떤 속성들이 있지?' 그리고 런타임에 그 답을 얻을 수 있습니다.
리얼 월드에서 리플렉션을 쓰는 예
- 플러그인과 모듈 생성: 어떤 클래스를 다룰지 미리 알 필요가 없어요 — DLL을 로드해서 런타임에 메서드를 확인하면 됩니다.
- 테스트 자동화: 테스트 프레임워크들(예: xUnit, NUnit)은 리플렉션으로 테스트 메서드를 찾습니다.
- 직렬화: 리플렉션을 사용하면 수동으로 속성을 순회하지 않고도 어떤 객체든 JSON/XML로 자동 변환할 수 있어요.
- 어트리뷰트 기반 데이터 검증: 예를 들어 속성에 [Required]를 붙였으면 그런 속성들을 자동으로 검사할 수 있습니다.
면접에서 왜 알아야 하나요
리플렉션 사용 능력은 .NET의 내부 작동 원리를 이해하고 동적 문제를 해결할 줄 안다는 걸 보여주기 때문에 면접에서 자주 묻습니다. 자체 프레임워크나 확장 기능을 만들고 싶다면 리플렉션은 필수입니다.
2. 리플렉션 네임스페이스와 기본 클래스들
C#에서 리플렉션 기능 전체는 System.Reflection 네임스페이스를 통해 제공됩니다. 파일 상단에 잊지 말고 추가하세요:
using System.Reflection;
리플렉션의 주요 타입들
| 클래스/타입 | 설명 |
|---|---|
|
.NET에서 타입의 설명을 나타냅니다(예: 클래스, 인터페이스, enum 등) |
|
타입의 속성(property) |
|
타입의 메서드 |
|
타입의 필드(field) |
|
타입의 생성자 |
|
어셈블리(EXE 또는 DLL)를 나타냅니다 |
Type 클래스의 주요 메서드
| 메서드 | 설명 |
|---|---|
|
모든 public 속성을 반환합니다 |
|
모든 public 필드를 반환합니다 |
|
모든 public 메서드를 반환합니다 |
|
모든 public 생성자를 반환합니다 |
|
타입이 구현하는 모든 인터페이스를 반환합니다 |
|
타입에 적용된 모든 어트리뷰트를 반환합니다 |
이 메서드들에는 종종 비공개나 정적 멤버에 접근하기 위해 BindingFlags를 넘길 수 있지만, 지금은 기본 시나리오만 다룹니다.
3. 첫 번째 단계: Type 객체 얻기
모든 리플렉션 작업은 Type 객체를 다루는 것에서 시작합니다.
이 객체를 얻는 방법은 여러 가지가 있으니 각각 살펴봅시다:
방법 1: 객체를 통해
string text = "Hello, Reflection!";
Type type1 = text.GetType();
Console.WriteLine(type1.Name); // String
방법 2: 타입 이름을 통해
Type type2 = typeof(int);
Console.WriteLine(type2.FullName); // System.Int32
방법 3: 문자열로(동적으로)
Type type3 = Type.GetType("System.Double");
Console.WriteLine(type3); // System.Double
주의하세요! 다른 어셈블리의 사용자 정의 클래스는 어셈블리까지 포함한 완전한 타입 이름을 지정해야 합니다.
4. 타입 구조 조사: 속성, 메서드, 필드, 생성자
진짜 핵심이에요: Type 객체를 얻었으니 이제 원하는 모든 정보를 꺼낼 수 있습니다.
속성 목록 얻기
Type type = typeof(DateTime);
PropertyInfo[] properties = type.GetProperties();
foreach (var prop in properties)
{
Console.WriteLine($"{prop.PropertyType.Name} {prop.Name}");
}
출력 예:
Int32 Day
Int32 Month
Int32 Year
DayOfWeek DayOfWeek
...
메서드 목록 얻기
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.WriteLine($"{method.ReturnType.Name} {method.Name}()");
}
큰 타입은 메서드가 매우 많을 수 있습니다! 보통은 상속된 object의 메서드를 제외한 '자기 것'만 필요할 때가 많죠. 그건 나중에 다룹니다.
필드 목록 얻기
FieldInfo[] fields = type.GetFields();
foreach (var field in fields)
{
Console.WriteLine($"{field.FieldType.Name} {field.Name}");
}
일반적인 클래스는 public 필드를 거의 갖지 않아요 — 캡슐화가 중요하니까요 :)
생성자 얻기
ConstructorInfo[] constructors = type.GetConstructors();
foreach (var ctor in constructors)
{
Console.WriteLine($"생성자: {ctor}");
}
5. 실습: 우리 애플리케이션 분석
우리 학습 프로젝트에 TaskItem 클래스가 있고 거기에 작업 항목을 저장한다고 했죠. 리플렉션으로 이 클래스를 조사해봅시다.
public class TaskItem
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DueDate { get; set; }
public bool IsCompleted;
}
런타임에 타입의 '내부'를 어떻게 확인할 수 있는지 예시는 다음과 같습니다:
Type taskType = typeof(TaskItem);
Console.WriteLine("속성:");
foreach (var prop in taskType.GetProperties())
Console.WriteLine($" {prop.PropertyType.Name} {prop.Name}");
Console.WriteLine("필드:");
foreach (var field in taskType.GetFields())
Console.WriteLine($" {field.FieldType.Name} {field.Name}");
Console.WriteLine("메서드:");
foreach (var method in taskType.GetMethods())
Console.WriteLine($" {method.ReturnType.Name} {method.Name}()");
클래스에 새 속성을 추가하고 리플렉션이 그것들을 어떻게 보는지 확인해보세요. 보편적인 직렬화기나 객체 인스펙터가 이렇게 동작합니다.
6. 시각화: Type 객체 구조(도식)
+-------------------------+
| Type |
+-------------------------+
| .Name |
| .FullName |
| .Namespace |
| .Assembly |
+-------------------------+
| .GetProperties() |
| .GetFields() |
| .GetMethods() |
| .GetConstructors() |
| .GetInterfaces() |
| .GetCustomAttributes() |
+-------------------------+
이게 타입의 '초상'이에요 — 이름, 어셈블리, 멤버들, 어트리뷰트 등이 있습니다.
타입의 기본 정보 얻기
Type 객체로 타입에 대해 많은 걸 알 수 있습니다:
Type t = typeof(double);
Console.WriteLine(t.Name); // Double
Console.WriteLine(t.FullName); // System.Double
Console.WriteLine(t.Namespace); // System
Console.WriteLine(t.IsClass); // False
Console.WriteLine(t.IsValueType); // True
Console.WriteLine(t.IsEnum); // False
Console.WriteLine(t.IsPrimitive); // True
7. 이름으로 특정 속성/메서드/필드 찾기
속성
var prop = taskType.GetProperty("Title");
if (prop != null)
{
Console.WriteLine($"속성 'Title'의 타입: {prop.PropertyType}");
}
메서드
var method = taskType.GetMethod("ToString");
if (method != null)
{
Console.WriteLine($"메서드 'ToString'의 반환 타입: {method.ReturnType}");
}
필드
var field = taskType.GetField("IsCompleted");
if (field != null)
{
Console.WriteLine($"필드 'IsCompleted'의 타입: {field.FieldType}");
}
8. 값에 대한 동적 접근에 리플렉션 사용하기
이제 단순히 보는 걸 넘어서 값에 '손대는' 법을 배웁니다!
속성 값 읽기
TaskItem item = new TaskItem { Id = 1, Title = "리플렉션 테스트", DueDate = DateTime.Today };
PropertyInfo titleProp = item.GetType().GetProperty("Title");
if (titleProp != null)
{
object value = titleProp.GetValue(item);
Console.WriteLine($"제목: {value}");
}
속성 값 설정
titleProp.SetValue(item, "변경된 제목");
Console.WriteLine(item.Title);
필드 작업
FieldInfo completedField = item.GetType().GetField("IsCompleted");
completedField.SetValue(item, true);
Console.WriteLine(item.IsCompleted); // true
private 필드나 속성에 접근하려면 BindingFlags.NonPublic 같은 특별한 플래그가 필요합니다. 그건 나중에 설명할게요.
9. 리플렉션으로 메서드 호출하기
메서드를 코드 작성 시점에 몰라도 호출할 수 있습니다!
TaskItem task = new TaskItem { Title = "리플렉션 — 멋지다!" };
MethodInfo method = task.GetType().GetMethod("ToString");
if (method != null)
{
object result = method.Invoke(task, null);
Console.WriteLine(result);
}
파라미터가 있는 메서드는 object 배열로 전달해야 합니다:
public class Calculator
{
public int Add(int a, int b) => a + b;
}
var calc = new Calculator();
var addMethod = typeof(Calculator).GetMethod("Add");
object[] parameters = { 5, 3 };
object sumResult = addMethod.Invoke(calc, parameters);
Console.WriteLine(sumResult); // 8
10. 생성자를 통해 동적으로 객체 생성하기
리플렉션으로는 new 연산자 없이도 객체를 만들 수 있습니다!
Type taskType = typeof(TaskItem);
object newTask = Activator.CreateInstance(taskType);
// 이것은 같은 TaskItem이지만 타입은 object입니다
Console.WriteLine(newTask.GetType().Name); // TaskItem
생성자 파라미터도 전달할 수 있어요:
public class User
{
public string Name { get; }
public User(string name) { Name = name; }
}
Type userType = typeof(User);
object user = Activator.CreateInstance(userType, "엘리스");
Console.WriteLine(((User)user).Name); // 엘리스
11. System.Reflection 작업 시 흔한 실수들
오류 #1: BindingFlags 없이 비공개 멤버에 접근하려 함.
기본적으로 리플렉션은 public 멤버만 봅니다. BindingFlags.NonPublic 없이 private 필드나 메서드에 접근할 수 없습니다.
오류 #2: null 체크를 무시함.
GetProperty, GetMethod 등은 멤버를 못 찾으면 null을 반환합니다. 체크하지 않으면 NullReferenceException이 발생합니다.
오류 #3: 잘못된 오버로드 선택.
메서드에 여러 오버로드가 있으면 GetMethod가 다른 버전을 반환할 수 있습니다. 파라미터 타입을 명시해서 정확한 오버로드를 선택하세요.
오류 #4: 성능 민감 코드에서 리플렉션 사용.
리플렉션은 직접 호출보다 느립니다. 성능이 중요한 곳에서는 메타데이터 캐싱이나 델리게이트를 사용하세요.
GO TO FULL VERSION