1. 리플렉션이란
객체의 형식 정보를 들어다보는 기능 ( X-Ray )
프로그램 실행 중에, 객체의 형식 이름, 프로퍼티 목록, 메소드 목록, 필드, 이벤트 목록 등을 확인 가능
모든 데이터 형식의 조상인 Object 형식에 GetType() 메소드를 생성
2. 리플렉션
2.1 Object.GetType() 메소드와 Type 클래스
GetType() 메소드는 Type 형식의 결과를 반환하는데,
Type 형식은 .NET에서 사용하는 데이터 형식의 모든 정보를 담고 있다.
( 모든 생성자, 이벤트, 필드, 형식 매개변수, 상속하는 인터페이스, 멤버, 메소드, 내장 형식, 프로퍼티 목록 등 )
System.Type 클래스의 메소드 [msdn] System.Type
Type 클래스 (System)
클래스 형식, 인터페이스 형식, 배열 형식, 값 형식, 열거형 형식, 형식 매개 변수, 제네릭 형식 정의 및 개방형 생성 제네릭 형식이나 폐쇄형 생성 제네릭 형식에 대한 형식 선언을 나타냅니다.
learn.microsoft.com
* 주의! Obejct.GetType() 메소드는 반드시 인스턴스가 있어야 호출이 가능
using System;
using System.Reflection;
namespace ThisIsCSharp
{
internal class MainApp
{
static void PrintInterfacs(Type type)
{
Console.WriteLine(" === Interfaces === ");
Type[] interfaces = type.GetInterfaces();
foreach(Type i in interfaces)
{
Console.WriteLine($"Name:{i.Name}");
}
Console.WriteLine();
}
static void PrintFields(Type type)
{
Console.WriteLine(" === Fields === ");
// Default : Public
FieldInfo[] fields = type.GetFields(
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Static |
BindingFlags.Instance);
foreach(FieldInfo field in fields )
{
String accessLevel = "protected";
if(field.IsPublic)
{
accessLevel = "public";
}
else if(field.IsPrivate)
{
accessLevel = "private";
}
Console.WriteLine($"Access:{accessLevel}, Type:{field.FieldType.Name}, Name:{field.Name}");
}
Console.WriteLine();
}
static void PrintMethods(Type type)
{
Console.WriteLine(" === Methods === ");
MethodInfo[] methods = type.GetMethods();
foreach (var method in methods)
{
Console.Write($"Type:{method.ReturnType.Name}, Name:{method.Name}, Parameter:");
ParameterInfo[] args = method.GetParameters();
for(int i = 0; i < args.Length; i++)
{
Console.Write($"{args[i].ParameterType.Name}");
if(i < args.Length - 1)
{
Console.Write(", ");
}
}
Console.WriteLine();
}
Console.WriteLine();
}
static void PrintProperties(Type type)
{
Console.WriteLine(" === Properties === ");
PropertyInfo[] properties = type.GetProperties();
foreach(var property in properties)
{
Console.WriteLine($"Type:{property.PropertyType.Name}, Name:{property.Name}");
}
Console.WriteLine();
}
static void PrintConstructors(Type type)
{
Console.WriteLine(" === Constructors === ");
ConstructorInfo[] constructors = type.GetConstructors(
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.Static |
BindingFlags.Instance);
foreach (var constructor in constructors)
{
Console.WriteLine($"Name:{constructor.Name}");
}
Console.WriteLine();
}
static void PrintNestedTypes(Type type)
{
Console.WriteLine(" === NestedTypes === ");
Type[] nestedTypes = type.GetNestedTypes();
foreach (var nestType in nestedTypes)
{
Console.WriteLine($"Name:{nestType.Name}");
}
Console.WriteLine();
}
static void Main(string[] args)
{
// 데이터를 초기화 해주지 않는다면, GetType이 호출이 불가능하다.
String a = "";
Type type = a.GetType();
PrintInterfacs(type);
PrintFields(type);
PrintProperties(type);
PrintMethods(type);
PrintConstructors(type);
PrintNestedTypes(type);
}
}
}
- GetType과 동일하게 사용 가능한 함수
Type a = typeof(int);
Type b = Type.GetType("System.Int32");
2.2 리플렉션을 이용해서 객체 생성
리플렉션을 이용해서 동적으로 인스턴스를 만들기 위해서는 System.Activator 클래스가 필요
using System;
using System.Reflection;
namespace ThisIsCSharp
{
class Profile
{
private string name;
private string phone;
public Profile()
{
name = "";
phone = "";
}
public Profile(string name, string phone)
{
this.name = name;
this.phone = phone;
}
public void Print()
{
Console.WriteLine($"{name}, {phone}");
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Phone
{
get { return phone; }
set { phone = value; }
}
}
internal class MainApp
{
static void Main(string[] args)
{
Type type = Type.GetType("ThisIsCSharp.Profile");
MethodInfo methodInfo = type.GetMethod("Print");
// 특정 이름의 프로퍼티를 찾아 그 프로퍼티의 정보를 담은 PropertyInfo 객체를 반환
PropertyInfo nameProperty = type.GetProperty("Name");
PropertyInfo phoneProperty = type.GetProperty("Phone");
// 입력받은 형식의 인스턴스를 생성
object profile = Activator.CreateInstance(type, "박상현", "512-1234");
// ( 인스턴스, 메소드의 인수 )
methodInfo.Invoke(profile, null);
profile = Activator.CreateInstance(type);
// ( 인스턴스, _ , 인덱서 )
nameProperty.SetValue(profile, "박찬호", null);
phoneProperty.SetValue(profile, "997-5511", null);
Console.WriteLine($"{nameProperty.GetValue(profile, null)}, {phoneProperty.GetValue(profile, null)}");
}
}
}
2.3 형식 내보내기
동적으로 새로운 형식을 만들기 위해서는 System.Reflection.Emit 네임스페이스에 클래스들이 필요. ( 이름의 형식 : ~Builder )
System.Reflection.Emit 클래스 정보들 [ msdn ] SystemReflection.Emit
System.Reflection.Emit 네임스페이스
컴파일러 또는 도구가 메타데이터 및 MSIL(Microsoft 중간 언어)을 내보내고 필요에 따라 디스크에 PE 파일을 생성할 수 있도록 하는 클래스를 포함합니다. 이러한 클래스의 기본 클라이언트는 스크
learn.microsoft.com
* 클래스를 사용하는 요령
1. AssemblyBuilder를 이용해서 어셈블리를 생성
2. ModuleBuilder를 이용해서 1에서 생성한 어셈블리 안에 모듈을 만들어 넣기
3. 2에서 생성한 모듈 안에 TypeBuilder로 클래스를 생성
4. 3에서 생성한 클래스 안에 MethodBuilder 나 PropertyBuilder를 생성
5. 4에서 생성한 것이 메소드라면, ILGenerator를 이용해서 메소드 안에 CPU가 실행할 IL 명령 넣기
[ 어셈블리 ] -> [ 모듈 ] -> [ 클래스 ] -> [ 메소드 ] or [ 프로퍼티 ]
.NET 프로그램의 계층 구조 ( p578 참조 )
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace ThisIsCSharp
{
internal class MainApp
{
static void Main(string[] args)
{
// AssemblyBulder는 스스로 생성하는 생성자가 없으므로, 팩토리 클래스를 사용
// 1. AssemblyBuilder의 인스턴스 생성
AssemblyBuilder newAssembly =
AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("CalculatorAssembly"),
AssemblyBuilderAccess.Run);
// 2. 어셈블리의 내부에 모듈을 생성 DefineDynamicModule(모듈_이름)
ModuleBuilder newModule = newAssembly.DefineDynamicModule("Calculator");
// 3. 클래스 생성 DefineType(클래스_이름)
TypeBuilder newType = newModule.DefineType("Sum1To100");
// 4. 메소드 생성
MethodBuilder newMethod = newType.DefineMethod(
"Calculate",
MethodAttributes.Public,
typeof(int), // 반환 형식
new Type[0]); // 매개 변수
// 5. 메소드가 실행할 코드 (IL 명령어)를 채움.
ILGenerator generator = newMethod.GetILGenerator();
generator.Emit(OpCodes.Ldc_I4, 1); // 32bit 정수를 계산 스택에 넣기
for(int i = 2; i <= 100; i++)
{
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Add); // 계산 후 계산 스택에 담겨 있는 값 꺼내서 더한 후, 그 결과를 다시 계산 스택에 넣기
}
generator.Emit(OpCodes.Ret); // 계산 스택에 담겨 있는 값을 반환
// 6. CLR에 생성된 클래스를 제출
newType.CreateType();
// 7. 생성된 객체를 가져와서 실행
object sum1To100 = Activator.CreateInstance(newType);
MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");
Console.WriteLine(Calculate.Invoke(sum1To100, null));
}
}
}
5. 메소드가 실행할 코드 (IL 명령어)를 채움
스택에 있는 과정 ( 1 + 2 + 3 ) 일 경우,
1. 1
2. 1 + 2
3. 3 // 1+2 의 값을 빼고 계산 후 다시 넣기
4. 3 + 3
5. 6 // 3+3 의 값을 뺴고 계산 후 다시 넣기
3. 참고자료
'[ 공 부 ] > [ C# ]' 카테고리의 다른 글
19장. Task (0) | 2025.01.30 |
---|---|
19장. 스레드 (0) | 2025.01.29 |
18장. 파일 다루기 (0) | 2025.01.29 |
17장 Dynamic 형식 (0) | 2025.01.28 |
16장. 애트리뷰트 (0) | 2025.01.28 |