리플렉션은 객체의 형식(Type) 정보를 들여다보는 기능이다.
모든 데이터 형식의 조상인 Object는 다음과 같은 메소드를 물려받는다.
이 중 GetType() 메소드를 통해 객체의 형식 정보를 얻어낼 수 있다.
Type 형식은 .NET에서 사용되는 데이터 형식의 모든 정보를 담고 있다.
Type 형식의 메소드를 이용하면 다른 정보들도 뽑아낼 수 있다.
| 메소드 | 반환 형식 | 설명 |
|---|---|---|
| GetConstructors() | ConstructorInfo[] | 모든 생성자 목록을 반환 |
| GetEvents() | EventInfo[] | 이벤트 목록을 반환 |
| GetFields() | FieldInfo[] | 필드 목록을 반환 |
| GetGenericArguments() | Type[] | 형식 매개변수 목록을 반환 |
| GetInterfaces() | Type[] | 인터페이스 목록을 반환 |
| GetMembers() | MemberInfo[] | 멤버 목록을 반환 |
| GetMethods() | MethodInfo[] | 메소드 목록을 반환 |
| GetNestedTypes() | Type[] | 내장 형식 목록을 반환 |
| GetProperties() | PropertyInfo[] | 프로퍼티 목록을 반환 |
System.Reflection.BindingFlags 열거형을 이용하여 검색 옵션을 지정할 수 있다.
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace GetType
{
class MainApp
{
static void PrintInterfaces(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 -----");
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}");
}
}
static void PrintMethods(Type type)
{
Console.WriteLine("----- Methods -----");
MethodInfo[] methods = type.GetMethods();
foreach (MethodInfo 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 (PropertyInfo property in properties)
Console.WriteLine($"Type:{property.PropertyType.Name}, Name:{property.Name}");
Console.WriteLine();
}
static void Main()
{
int a = 0;
Type type = a.GetType();
PrintInterfaces(type);
PrintFields(type);
PrintProperties(type);
PrintMethods(type);
}
}
}
GetType 메서드는 반드시 인스턴스가 있어야 호출이 가능하다.
typeof 연산자는 형시그이 식별자 자체를 인수로 받고 Type.GetType() 메서드는 형식의 전체 이름을 인수로 받는다.
public static void Main()
{
Type a = typeof(int);
Console.ReadLine(a.FullName);
Type b = Type.GetType("System.Int32");
Console.ReadLine(b.FullName);
}
리플렉션을 이용해서 동적으로 인스턴스를 만들기 위해서는 System.Activatior 클래스의 도움이 필요하다.
인스턴스를 만들고자 하는 형식의 Type 객체를 매개변수에 넘기면, Activator.CreateInstace() 메소드는 입력받은 형식의 인스턴스를 생성하여 반환한다.
object a = Activator.CreateInstance(typoeof(int));
일반화를 지원하는 버전의 CreateInstance() 메소드도 존재한다.
List<int> list = Activator.CreateInstance<List<int>>();
동적으로 인스턴스 생성뿐만 아니라 객체의 프로퍼티에 값을 할당할 수 도 있으며 메소드도 동적으로 호출할 수 있다.
PropertyInfo 클래스에는 SetValue()와 GetValue() 메소드가 있으며 각각 값을 읽고 값을 할당하기가 가능하다.
MethodInfo 클래스에는 Invoke() 메소드가 있다.
using System;
using System.Reflection;
namespace DynamicInstance
{
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; }
}
}
class MainApp
{
static void Main()
{
Type type = Type.GetType("DynamicInstance.Profile");
MethodInfo methodInfo = type.GetMethod("Print");
PropertyInfo nameProperty = type.GetProperty("Name");
PropertyInfo phoneProperty = type.GetProperty("Phone");
object profile = Activator.CreateInstance(type, "임재준", "010-1234-5678");
methodInfo.Invoke(profile, null);
profile = Activator.CreateInstance(type);
nameProperty.SetValue(profile, "홍길동", null);
phoneProperty.SetValue(profile, "010-9876-5432", null);
Console.WriteLine($"{nameProperty.GetValue(profile, null)} {phoneProperty.GetValue(profile, null)}");
}
}
}
동적으로 새로운 형식을 만드는 작업은 System.Reflection.Emit 네임스페이스에 있는 클래스를 통해 이루어진다.
Emit 네임스페이스에서 제공하는 클래스의 목록은 MSDN에서 확인해보자
클래스를 사용하는 요령 순서
1. AssemblyBuilder를 이용해서 어셈블리를 만든다.
2. ModuleBuilder를 이용해서 1에서 생성한 어셈블리 안에 모듈을 만들어 넣습니다.
3. 2에서 생성한 모듈 안에 TypeBuilder로 클래스를 만들어 넣습니다.
4. 3에서 생성한 클래스 안에 메소드나 프로퍼티를 만들어 넣습니다.
5. 4에서 생성한 것이 메소드라면 ,ILGenerator를 이용해서 메소드 안에 CPU가 실행할 IL 명령들을 넣는다.
using System;
using System.Reflection;
using System.Reflection.Emit;
namespace EmitTest
{
class MainApp
{
static void Main()
{
AssemblyBuilder newAssembly = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("CalculatorAssembly"),
AssemblyBuilderAccess.Run);
ModuleBuilder newModule = newAssembly.DefineDynamicModule("Calculator");
TypeBuilder newType = newModule.DefineType("Sum1To100");
MethodBuilder newMethod = newType.DefineMethod(
"Calculate",
MethodAttributes.Public,
typeof(int), // 반환 형식
new Type[0]); // 매개변수
ILGenerator generator = newMethod.GetILGenerator();
generator.Emit(OpCodes.Ldc_I4, 1);
for (int i = 2; i <= 100; i++)
{
generator.Emit(OpCodes.Ldc_I4, i);
generator.Emit(OpCodes.Add);
}
generator.Emit(OpCodes.Ret);
newType.CreateType();
object sum1To100 = Activator.CreateInstance(newType);
MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");
Console.WriteLine(Calculate.Invoke(sum1To100, null));
}
}
}