
"만약 프로그램이 실행되는 도중에 자신의 구조를 뜯어보고,
심지어 그걸로 무언가를 할 수 있다면 어떨까요?" 마치 우리가
거울을 보면서 자신의 모습을 확인하고 머리를 정돈하는 것처럼 말이죠.
사실 C#에는 이런 마법 같은 기능을 하는 리플렉션(Reflection)이 존재합니다.
이번 글에서는 리플렉션이 무엇인지 그 '개념'에 대해 알아보겠습니다!
리플렉션(Reflection)은 프로그램이 실행 중(런타임)에
자기 자신의 정보(메타데이터)를 들여다보고, 이를 활용하여
객체를 생성하거나 메소드를 호출하는 등의 작업을 할 수 있게 해주는 기능입니다.
비유: 리플렉션은 거울에 비친 코드의 모습과 같아요.
- 거울 보기: 프로그램이 실행 중에 자신의 코드를 살펴봅니다.
- 거울에 비친 내 모습: 코드의 구조 정보, 즉 어떤 클래스가 있는지, 그 클래스에는
어떤 속성(Property)과 메소드(Method)가 있는지 등의 정보(메타데이터)를 확인합니다.- 내 모습을 보고 행동: 확인한 정보를 바탕으로, 특정 클래스의 인스턴스를 만들거나, 이름만 알고 있는 메소드를 호출할 수도 있습니다.
컴파일 시점에는 모든 것이 정해져 있지만, 리플렉션을 사용하면 런타임에
동적으로 코드의 구조를 파악하고 제어할 수 있는 유연함을 얻게 되는 것이죠.
리플렉션은 기능이 강력한 만큼, 꼭 필요한 곳에서 사용해야 하는데요,
주로 다음과 같은 시나리오에서 그 진가를 발휘합니다.
비주얼 스튜디오, 웹 브라우저에 확장 프로그램을 설치하는 것을 생각해 보세요.
메인 프로그램은 이 플러그인들이 어떤 클래스와 메소드를 가졌는지 알지 못합니다.
프로그램이 시작될 때 특정 폴더에 있는 DLL 파일들을 읽어오고, 리플렉션을 사용해
"DLL에서 IFilter인터페이스를 구현한 클래스가 있네! 이걸 메뉴에 추가해야겠다!"와 같이
동적으로 메인 프로그램의 기능을 확장할 수 있습니다.
Entity Framework 같은 ORM이나 Json.NET 같은 직렬화는
리플렉션을 통해 컴파일 시점에 알 수 없는 타입을 런타임에 처리할 수 있도록 하고,
그 이름과 타입을 알아내어 데이터베이스 테이블이나 JSON 데이터에 매핑합니다.
이처럼 리플렉션은 범용적이고 유연한 프레임워크를 구축하는 데 필수적인 기술입니다.
NUnit이나 xUnit 같은 테스트 프레임워크는 사용자가 작성한 어셈블리(DLL)를 로드한 후,
리플렉션을 사용하여 클래스와 메소드를 검사합니다. [Test]나 [Fact]같은
특정 애트리뷰트를 가진 테스트 코드를 찾아내고 동적으로 호출합니다.
리플렉션이 실제로 어떻게 동작하는지 간단한 코드로 살펴볼까요?
Person이라는 클래스의 정보를 런타임에 어떻게 가져오는지 보세요.
[코드]
using System;
using System.Reflection; // 리플렉션 기능을 사용하려면 필요
public class Person
{
public string Name { get; set; }
public int Age { get; private set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public void SayHello()
{
Console.WriteLine($"안녕하세요, 제 이름은 {Name}입니다.");
}
}
class Program
{
static void Main()
{
Person person = new Person("홍길동", 25);
Type type = person.GetType(); // 1. person 객체의 Type 정보 가져오기
Console.WriteLine($"[클래스 이름]: {type.Name}");
Console.WriteLine("--- 속성(Properties) 목록 ---");
// 2. 클래스의 모든 public 속성 정보 가져오기
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
Console.WriteLine($"# {property.PropertyType.Name} {property.Name}");
}
Console.WriteLine("\n--- 메소드(Methods) 목록 ---");
// 3. 클래스의 모든 public 메소드 정보 가져오기
MethodInfo[] methods =
type.GetMethods(
BindingFlags.Public | // public으로 선언된 메서드
BindingFlags.Instance | // 인스턴스에 속하는 메서드
BindingFlags.DeclaredOnly); // 해당 클래스에 선언된 메서드
foreach (MethodInfo method in methods)
{
Console.WriteLine($"# {method.ReturnType.Name} {method.Name}()");
}
}
}
[실행 결과]
[클래스 이름]: Person
--- 속성(Properties) 목록 ---
# String Name
# Int32 Age
--- 메소드(Methods) 목록 ---
# String get_Name()
# Void set_Name()
# Int32 get_Age()
# Void SayHello()
person객체만 가지고 GetType()메소드를 통해 Type객체를 얻어오면,
이 클래스가 어떤 속성과 메소드를 가졌는지 런타임에 모두 파악할 수 있습니다.
private멤버를 수정할 수 있습니다.리플렉션은 매우 유연하고 동적인 프로그래밍을 가능하게 하는 멋진 도구입니다.
하지만 강력한 만큼 성능과 유지보수 측면에서 고려해야 할 점도 분명히 존재하죠.
따라서 리플렉션을 꼭 필요한 순간에만 적절히 활용하는 것이 중요합니다.