CodeGym /행동 /C# SELF /리플렉션: System.Reflection

리플렉션: System.Reflection

C# SELF
레벨 63, 레슨 0
사용 가능

1. 소개

리플렉션(영어 Reflection)은 프로그램이 실행 중에 자신의 구조를 조사할 수 있는 기능이에요. 프로그램이 스스로에게 물어보는 걸 상상해보세요: '내가 어떤 메서드들을 가지고 있지? 이 타입에 어떤 속성들이 있지?' 그리고 런타임에 그 답을 얻을 수 있습니다.

리얼 월드에서 리플렉션을 쓰는 예

  • 플러그인과 모듈 생성: 어떤 클래스를 다룰지 미리 알 필요가 없어요 — DLL을 로드해서 런타임에 메서드를 확인하면 됩니다.
  • 테스트 자동화: 테스트 프레임워크들(예: xUnit, NUnit)은 리플렉션으로 테스트 메서드를 찾습니다.
  • 직렬화: 리플렉션을 사용하면 수동으로 속성을 순회하지 않고도 어떤 객체든 JSON/XML로 자동 변환할 수 있어요.
  • 어트리뷰트 기반 데이터 검증: 예를 들어 속성에 [Required]를 붙였으면 그런 속성들을 자동으로 검사할 수 있습니다.

면접에서 왜 알아야 하나요

리플렉션 사용 능력은 .NET의 내부 작동 원리를 이해하고 동적 문제를 해결할 줄 안다는 걸 보여주기 때문에 면접에서 자주 묻습니다. 자체 프레임워크나 확장 기능을 만들고 싶다면 리플렉션은 필수입니다.

2. 리플렉션 네임스페이스와 기본 클래스들

C#에서 리플렉션 기능 전체는 System.Reflection 네임스페이스를 통해 제공됩니다. 파일 상단에 잊지 말고 추가하세요:

using System.Reflection;

리플렉션의 주요 타입들

클래스/타입 설명
Type
.NET에서 타입의 설명을 나타냅니다(예: 클래스, 인터페이스, enum 등)
PropertyInfo
타입의 속성(property)
MethodInfo
타입의 메서드
FieldInfo
타입의 필드(field)
ConstructorInfo
타입의 생성자
Assembly
어셈블리(EXE 또는 DLL)를 나타냅니다

Type 클래스의 주요 메서드

메서드 설명
GetProperties()
모든 public 속성을 반환합니다
GetFields()
모든 public 필드를 반환합니다
GetMethods()
모든 public 메서드를 반환합니다
GetConstructors()
모든 public 생성자를 반환합니다
GetInterfaces()
타입이 구현하는 모든 인터페이스를 반환합니다
GetCustomAttributes()
타입에 적용된 모든 어트리뷰트를 반환합니다

이 메서드들에는 종종 비공개나 정적 멤버에 접근하기 위해 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: 성능 민감 코드에서 리플렉션 사용.
리플렉션은 직접 호출보다 느립니다. 성능이 중요한 곳에서는 메타데이터 캐싱이나 델리게이트를 사용하세요.

2
과제
C# SELF, 레벨 63, 레슨 0
잠금
타입의 속성, 메서드, 필드 목록 가져오기
타입의 속성, 메서드, 필드 목록 가져오기
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION