using System;
using System.Reflection;
using System.Reflection.Emit;
/*
리플렉션 이밋(Reflection Emit):
리플렉션을 이용하여 프로그램 실행 중에(=동적으로=런타임에) 새로운 형식을 만들어낼 수 있는 기능.
프로그램이 실행 중에 만들어낸 새 형식을 CLR의 메모리에 내보내는 것.
런타임에 IL(Intermediate Language) 코드를 생성하여, 새로운 어셈블리, 모듈, 타입, 메서드를 동적으로 생성하는 기능
*/
namespace EmitTest
{
public class MainApp
{
public static void Main()
{
// (1) 에셈블리를 만듭니다.
// AssemblyBuilder 클래스를 이용해야하지만, AssemblyBuilder는 스스로를 생성하는 생성자가 없습니다.
// 따라서 다른 팩토리 클래스의 도움을 받아야 합니다.
// DefineDynamicAssembly() 메서드를 호출하면 AssemblyBuilder의 인스턴스를 만들 수 있습니다.
// "CalculatorAssembly"라는 이름의 동적 어셈블리를 생성합니다.
AssemblyBuilder newAssembly =
AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("CalculatorAssembly"), AssemblyBuilderAccess.Run);
// (2) 모듈을 만듭니다.
// AssemblyBuilder는 동적 모듈을 생성하는 DefineDynamicModule() 메서드를 가지고 있으므로,
// 이 메서드를 호출해서 모듈을 만들면 됩니다.
// "Calculator"라는 이름의 모듈을 만듭니다.
ModuleBuilder newModule = newAssembly.DefineDynamicModule("Calculator");
// (3) 클래스를 만듭니다.
// ModuleBuilder의 DefineType() 메서드를 이용해서 클래스를 생성합니다.
// "SumTo100"이라는 이름의 클래스를 만듭니다.
TypeBuilder newType = newModule.DefineType("Sum1To100");
// (4) 메서드를 만듭니다.
// TypeBuilder 클래스의 DefineMethod() 메서드를 호출해서,
// "Calculate()" 라는 이름의 메서드를 만듭니다.
// Calculate() 메서드의 접근성은 Public이며, 반환 형식은 int, 매개변수는 없습니다.
MethodBuilder newMethod = newType.DefineMethod(
"Calculate",
MethodAttributes.Public,
typeof(int), // 반환 형식
new Type[0]); // 매개 변수
// (5) 앞에서 생성한 것이 메서드이므로, 메서드가 실행할 코드(IL 명령어)를 채워 넣습니다.
// 이 작업은 ILGenerator 객체를 통해 이루어집니다.
// MethodBuilder 클래스의 GetILGenerator() 메서드를 통해 ILGenerator 객체를 얻을 수 있습니다.
ILGenerator generator = newMethod.GetILGenerator();
generator.Emit(OpCodes.Ldc_I4, 1); // 32비트 정수(1)를 계산 스택에 넣습니다.
for (int i = 2; i <= 100; i++)
{
generator.Emit(OpCodes.Ldc_I4, i); // 32비트 정수(i)를 계산 스택에 넣습니다.
generator.Emit(OpCodes.Add); // 계산 후 계산 스택에 담겨 있는 두 개의 값을 꺼내서 더한 후,
// 그 결과를 다시 계산 스택에 넣습니다.
}
generator.Emit(OpCodes.Ret); // 계산 스택에 담겨 있는 값을 반환합니다.
// (6) Calculate() 메서드 안에 IL 명령어를 모두 채워 넣었으니,
// SumTo100 클래스를 CLR에 제출합니다.
// newType 객체에 CreateType() 메서드를 호출합니다.
// 여기까지가 새로운 형식을 만드는 과정입니다.
newType.CreateType();
// (7) 이제부터는 이 형식(동적으로 생성한 Sum1To100 타입)의
// 인스턴스를 동적으로 생성해서 이용할 수 있습니다.
object sum1To100 = Activator.CreateInstance(newType);
MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");
Console.WriteLine(Calculate.Invoke(sum1To100, null));
}
}
}
/*
출력 결과
5050
*/
코드 설명
이 C# 코드는 리플렉션 방출(Reflection Emit)을 사용하여 1부터 100까지의 합을 계산하는 프로그램을 동적으로 생성하고 실행하는 예제입니다. 리플렉션 방출은 런타임에 IL(Intermediate Language) 코드를 생성하여 새로운 어셈블리, 모듈, 타입, 메서드를 동적으로 생성하는 기능입니다.
MainApp 클래스
AssemblyBuilder newAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("CalculatorAssembly"), AssemblyBuilderAccess.Run);: "CalculatorAssembly"라는 이름의 동적 어셈블리를 생성합니다. AssemblyBuilderAccess.Run은 어셈블리를 실행 가능하도록 설정합니다.ModuleBuilder newModule = newAssembly.DefineDynamicModule("Calculator");: "Calculator"라는 이름의 동적 모듈을 생성합니다.TypeBuilder newType = newModule.DefineType("Sum1To100");: "Sum1To100"라는 이름의 동적 타입을 생성합니다.MethodBuilder newMethod = newType.DefineMethod("Calculate", MethodAttributes.Public, typeof(int), new Type[0]);: "Calculate"라는 이름의 동적 메서드를 생성합니다. MethodAttributes.Public은 메서드를 public으로 설정하고, typeof(int)는 반환 타입을 int로 설정하고, new Type[0]는 매개변수가 없음을 나타냅니다.ILGenerator generator = newMethod.GetILGenerator();: IL 생성기를 가져옵니다. IL 생성기를 사용하여 메서드의 IL 코드를 생성할 수 있습니다.generator.Emit(OpCodes.Ldc_I4, 1);: 1을 스택에 로드합니다.for (int i = 2; i <= 100; i++) { ... }: 2부터 100까지 반복하며, 각 숫자를 스택에 로드하고 OpCodes.Add 명령어를 사용하여 스택의 두 값을 더합니다.generator.Emit(OpCodes.Ret);: 메서드를 반환합니다.newType.CreateType();: 동적 타입을 생성합니다.object sum1To100 = Activator.CreateInstance(newType);: 동적 타입의 인스턴스를 생성합니다.MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");: 동적 타입에서 "Calculate" 메서드를 가져옵니다.Console.WriteLine(Calculate.Invoke(sum1To100, null));: "Calculate" 메서드를 호출하고 결과를 출력합니다.출력 결과
5050
리플렉션 방출의 장점
리플렉션 방출의 단점
리플렉션 방출은 강력한 기능이지만, 복잡하고 사용하기 어려울 수 있으므로 주의해서 사용해야 합니다.
newType.CreateType();
// (7) 이제부터는 이 형식(동적으로 생성한 Sum1To100 타입)의 인스턴스를 동적으로 생성해서 이용할 수 있습니다.
object sum1To100 = Activator.CreateInstance(newType);
MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");
Console.WriteLine(Calculate.Invoke(sum1To100, null));
이 코드에서 "이 형식"은 바로 앞에서 동적으로 생성한 Sum1To100 타입을 의미합니다.
newType.CreateType(); 이 코드를 통해 Sum1To100 타입이 실제로 생성됩니다.
Activator.CreateInstance(newType); 이 코드는 Sum1To100 타입의 인스턴스(객체)를 생성합니다.
즉, 런타임에 동적으로 Sum1To100 타입을 정의하고, 그 타입의 객체를 생성하여 사용할 수 있게 됩니다.
이처럼 리플렉션 방출을 사용하면 컴파일 시점에 존재하지 않는 타입을 런타임에 동적으로 생성하고 사용할 수 있습니다.