1. 서론
C# 언어와 .NET 플랫폼은 고급 개발자들이 효율적으로 프로그래밍을 할 수 있게 해주는 여러 가지 기능을 제공합니다. 그중에 Reflection은 클래스, 메서드, 프로퍼티 등 객체의 메타데이터를 탐색하고 활용할 수 있는 강력한 메커니즘입니다. Reflection을 통해 개발자는 코드의 구조를 동적으로 조작하거나, 런타임 중에 타입을 검사하고, 객체를 생성하는 등 다양한 고급 메타프로그래밍 기법을 사용할 수 있습니다. 이 글에서는 Reflection의 원리, 활용 방법, 그리고 실제 예제들을 살펴보겠습니다.
2. Reflection의 기본 원리
Reflection은 .NET의 System.Reflection
네임스페이스에 포함된 클래스를 통해 구현됩니다. 기본적으로 Reflection은 다음과 같은 기능을 제공합니다:
- 타입 정보를 얻어오기
- 메서드, 속성, 필드 등의 정보를 동적으로 호출하거나 수정하기
- 인스턴스를 동적으로 생성하기
- 애트리뷰트를 통해 메타데이터에 접근하기
Reflection을 사용하면 컴파일 타임에 알 수 없는 정보들을 런타임에 추출할 수 있습니다. 예를 들어, 동적으로 로딩된 어셈블리의 타입 정보를 미리 알지 못하더라도 Reflection을 통해 해당 정보에 접근할 수 있습니다.
2.1 Reflection의 주요 클래스
– Assembly
: 어셈블리에 대한 정보 제공
– Type
: 타입에 대한 정보를 제공
– MethodInfo
: 메서드 정보 제공
– PropertyInfo
: 프로퍼티 정보 제공
– FieldInfo
: 필드 정보 제공
– ConstructorInfo
: 생성자 정보 제공
3. Reflection의 활용
Reflection의 다양한 활용 사례를 살펴보겠습니다. 이 섹션에서는 인스턴스 생성, 메서드 호출, 속성 접근 및 애트리뷰트 사용 예제를 다룰 것입니다.
3.1 인스턴스 생성
Reflection을 사용하여 객체를 동적으로 생성할 수 있습니다. 아래는 SampleClass
라는 클래스를 정의하고 Reflection을 사용하여 인스턴스를 생성하는 예제입니다.
using System;
using System.Reflection;
public class SampleClass
{
public string Name { get; set; }
public SampleClass(string name)
{
Name = name;
}
}
class Program
{
static void Main(string[] args)
{
Type sampleType = typeof(SampleClass);
ConstructorInfo constructor = sampleType.GetConstructor(new Type[] { typeof(string) });
object instance = constructor.Invoke(new object[] { "Reflection Example" });
Console.WriteLine(((SampleClass)instance).Name);
}
}
3.2 메서드 호출
Reflection을 통해 메서드를 동적으로 호출할 수 있습니다. 아래의 예제는 메서드를 호출하는 방법을 보여줍니다.
public class Calculator
{
public int Add(int a, int b)
{
return a + b;
}
}
class Program
{
static void Main(string[] args)
{
Type calculatorType = typeof(Calculator);
object calculatorInstance = Activator.CreateInstance(calculatorType);
MethodInfo addMethod = calculatorType.GetMethod("Add");
object result = addMethod.Invoke(calculatorInstance, new object[] { 5, 10 });
Console.WriteLine("Addition Result: " + result);
}
}
3.3 속성 접근
Reflection을 사용하여 객체의 속성에 접근할 수도 있습니다. 아래의 예제는 속성 값을 읽고 변경하는 방법을 보여줍니다.
public class Person
{
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
Person person = new Person();
Type personType = typeof(Person);
PropertyInfo nameProperty = personType.GetProperty("Name");
nameProperty.SetValue(person, "John Doe");
Console.WriteLine("Person Name: " + nameProperty.GetValue(person));
}
}
3.4 애트리뷰트 사용
클래스나 메서드에 붙은 애트리뷰트는 Reflection을 통해 쉽게 접근할 수 있습니다. 아래는 사용자 정의 애트리뷰트를 정의하고 그것에 접근하는 예제입니다.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AuthorAttribute : Attribute
{
public string Name { get; }
public AuthorAttribute(string name)
{
Name = name;
}
}
[Author("Jane Doe")]
public class Sample
{
[Author("John Smith")]
public void SampleMethod() { }
}
class Program
{
static void Main(string[] args)
{
Type sampleType = typeof(Sample);
AuthorAttribute classAttribute = (AuthorAttribute)Attribute.GetCustomAttribute(sampleType, typeof(AuthorAttribute));
Console.WriteLine("Class Author: " + classAttribute.Name);
MethodInfo methodInfo = sampleType.GetMethod("SampleMethod");
AuthorAttribute methodAttribute = (AuthorAttribute)Attribute.GetCustomAttribute(methodInfo, typeof(AuthorAttribute));
Console.WriteLine("Method Author: " + methodAttribute.Name);
}
}
4. 고급 메타프로그래밍 기법
Reflection을 활용한 고급 메타프로그래밍의 예시는 많습니다. 여기에는 플러그인 아키텍처, ORM(Object-Relational Mapping), 동적 프록시 생성 등 여러 가지가 포함됩니다.
4.1 플러그인 아키텍처
Reflection을 사용하여 런타임 시 동적으로 플러그인을 로드하고 사용할 수 있습니다. 이를 통해 애플리케이션의 기능을 쉽게 확장할 수 있습니다.
using System;
using System.IO;
using System.Reflection;
public interface IPlugin
{
void Execute();
}
class Program
{
static void Main(string[] args)
{
// 플러그인 DLL을 동적으로 로드
string pluginPath = "path/to/plugin.dll"; // 실제 플러그인 DLL 경로로 바꿔주세요
Assembly pluginAssembly = Assembly.LoadFrom(pluginPath);
Type[] pluginTypes = pluginAssembly.GetTypes();
foreach (Type type in pluginTypes)
{
if (typeof(IPlugin).IsAssignableFrom(type))
{
IPlugin plugin = (IPlugin)Activator.CreateInstance(type);
plugin.Execute();
}
}
}
}
4.2 ORM(Object-Relational Mapping)
Reflection을 사용하여 데이터베이스와 객체 간의 매핑을 자동으로 수행하는 ORM 기능을 구현할 수 있습니다. 아래는 간단한 ORM 예제입니다.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
public class SqlMapper
{
private string connectionString;
public SqlMapper(string connectionString)
{
this.connectionString = connectionString;
}
public List Query(string sql) where T : new()
{
List result = new List();
using (SqlConnection conn = new SqlConnection(connectionString))
{
SqlCommand cmd = new SqlCommand(sql, conn);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
while (reader.Read())
{
T item = new T();
for (int i = 0; i < reader.FieldCount; i++)
{
PropertyInfo prop = typeof(T).GetProperty(reader.GetName(i));
if (prop != null && prop.CanWrite)
{
prop.SetValue(item, reader.GetValue(i));
}
}
result.Add(item);
}
}
}
return result;
}
}
class Program
{
static void Main(string[] args)
{
SqlMapper sqlMapper = new SqlMapper("YourConnectionString");
var users = sqlMapper.Query("SELECT * FROM Users");
}
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
5. 결론
Reflection은 C#에서 강력한 메타프로그래밍 도구로, 런타임에 타입에 대한 정보에 접근하고 조작할 수 있게 해줍니다. 이를 통해 우리는 코드의 유연성을 높이고, 애플리케이션의 기능을 모듈화하고 확장할 수 있습니다. 이 글에서 소개한 예제와 개념들 외에도 Reflection은 각종 프레임워크 및 라이브러리에서도 광범위하게 활용되고 있어, 그 중요성을 알 수 있습니다. 앞으로도 Reflection과 메타프로그래밍에 대한 깊이 있는 이해와 활용이 중요할 것입니다.