C#에서 제공하는 형식의 메타 정보를 의미하며 이 형식 정보를 이용하여 직접 개체를 생성하거나 프로퍼티를 사용, 메서드를 호출할 수 있다.
Object의 GetType() 메서드
모든 개체의 공통 조상인 Object에는 GetType() 메서드를 가지고 있다. 이 메서드가 반환하는 것은 해당 타입 정보를 가지고 있는 Type 이라는 클래스의 인스턴스이다.
int a = 0;
Type type = a.GetType();
FieldInfo[] fields = type.GetFields();
foreach (FieldInfo field in fields)
Console.WriteLine($"Type : {field.FieldType.Name}, Name : {field.Name}");
위와 같이 Type 클래스는 a라는 인스턴스의 타입에 대한 모든 정보를 가지고 있다. 생성자, 필드, 프로퍼티, 메서드, 상속하는 인터페이스 등을 GetXXX()을 통해 가져올 수 있다. 이러한 Type의 정보를 가져오기 위해 System.Reflection 네임스페이스를 포함시킨다.
Type을 통해 가져올 수 있는 정보
메서드 | 반환 형식 | 설명 |
GetConstructors() | ConstructorInfo[] | 모든 생성자 목록을 반환한다. |
GetFields() | FieldInfo[] | 모든 필드 목록을 반환한다. |
GetInterfaces() | Type[] | 상속하는 모든 인터페이스 목록을 반환한다. |
GetProperties() | PropertyInfo[] | 모든 프로퍼티 목록을 반환한다. |
GetMethods() | MethodInfo[] | 모든 메서드 목록을 반환한다. |
GetMembers() | MemberInfo[] | 모든 멤버 목록을 반환한다. |
BindingFlag
필드나 메서드의 경우 접근자가 public/private/static 등으로 다양할 수 있다. GetFields()나 GetMethods()의 기본 행동은 public 공개되어있는 것들만 가져오는데, 해당 함수에서 BindingFlag.Public / BindingFlag.NonPublic / BindingFlag.Static / BindingFlag.Instance를 지칭하여 가져오는 대상을 지칭할 수 있다.
// 공개 인스턴스 필드
var public_instance_fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance);
// 비공개 인스턴스 필드
var private_instance_fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
// 공개 정적 필드
var public_static_fields = type.GetFields(BindingFlags.Public | BindingFlags.Static);
// 비공개 정적 필드
var private_static_fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static);
Object.GetType() 메서드를 사용하지 않고 형식 정보를 얻기
Object.GetType() 메서드는 반드시 인스턴스가 있어야 호출가능하다.
1) typeof(형식) 이용하기
Type a = typeof(int);
Console.WriteLine(a.FullName);
2) Type 클래스의 GetType(string typeName) 이용하기
네임스페이스를 포함한 전체 이름을 전달한다.
Type b = Type.GetType("System.Int32");
Console.WriteLine(b.FullName);
리플렉션을 이용한 객체의 생성과 사용
객체의 생성
System.Activator 클래스의 정적 메서드 CreateInstance(Type type)를 사용한다. 혹은 CreateInstance<T>()를 사용.
object a = Activator.CreateInstance(typeof(int));
List<int> list = Activator.CreateInstance<List<int>>();
Profile 클래스
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; } }
}
프로퍼티 정보 개체 (PropertyInfo)를 이용하여 해당 클래스의 프로퍼티를 수정
GetProperty(string propertyName) 을 이용하여 PropertInfo 인스턴스를 가져오고 SetValue나 GetValue로 프로퍼티 값을 수정하거나 읽을 수 있다. SetValue의 세번째 인자는 인덱서를 위한 인덱스 매개변수이다.
Type type = Type.GetType("DynamicInstance.Profile");
object profile = Activator.CreateInstance(type, "김아무개", "010-2333-2211");
profile = Activator.CreateInstance(type);
PropertyInfo nameProperty = type.GetProperty("Name");
PropertyInfo phoneProperty = type.GetProperty("Phone");
nameProperty.SetValue(profile, "박찬호", null);
phoneProperty.SetValue(profile, "992-020202", null);
메서드 정보를 담는 MethodInfo를 이용하여 해당 클래스의 메서드를 호출
GetMethod(string methodName)을 이용하여 MethodInfo 인스턴스를 가져오고 Invoke를 통해 함수를 호출한다. Invoke 함수는 첫번째 인자에 호출할 인스턴스와 매개변수를 전달한다.
MethodInfo methodInfo = type.GetMethod("Print");
methodInfo.Invoke(profile, null);
Console.WriteLine($"{nameProperty.GetValue(profile, null)}, {phoneProperty.GetValue(profile, null)}");
형식 내보내기
C#에서는 System.Reflection.Emit 네임스페이스를 포함하여 동적으로 클래스를 만들어서 새로운 모듈을 컴파일한 모듈을 만들어낼 수 있다.
순서
1) 코드 정의를 담는 어셈블리를 만든다.
2) 어셈블리안에 모듈을 만들어 담는다.
3) 모듈안에 정의할 클래스를 만든다.
4) 클래스에 각종 필드, 프로퍼티, 메서드를 만들고 메서드의 경우 CPU가 실행할 코드를 ILGenerator로 만들 수 있다.
5) 클래스 타입을 완성하고 CLR에 제출한다.
이렇게 생성한 타입을 새롭게 정의한 클래스처럼 사용할 수 있다.
예시)
static void Example2()
{
// 1. 어셈블리를 만든다.
AssemblyBuilder newAssemby = AssemblyBuilder.DefineDynamicAssembly(
new AssemblyName("CalculatorAssembly"), AssemblyBuilderAccess.Run);
// 2. 모듈을 만든다.
ModuleBuilder newModule = newAssemby.DefineDynamicModule("Calculator");
// 3. 클래스를 만든다.
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); // 32 bit 정수(1)를 계산 스택에 넣는다.
for(int i =2; i <= 100; ++i)
{
generator.Emit(OpCodes.Ldc_I4, i); // 32 bit 정수(i)를 계산 스택에 넣는다.
generator.Emit(OpCodes.Add); // 계산 후 계산 스택에 담겨있는 두개의 값을 더한 후 그 결과를 다시 계산 스택에 넣는다.
}
generator.Emit(OpCodes.Ret); // 계산 스택에 담겨 있는 값을 반환한다.
// 6 클래스를 CLR에 제출한다.
newType.CreateType();
// 1 ~ 100을 더하는 클래스를 이용한 계산
object sum1To100 = Activator.CreateInstance(newType);
MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");
Console.WriteLine(Calculate.Invoke(sum1To100, null));
}
애트리뷰트 Attribute
클래스/프로퍼티/필드/메서드에 메타 정보를 추가하는 기능이다.
[애트리뷰트_이름( 애트리뷰트_매개변수)]로 사용하며 애트리뷰트도 System.Attribute를 상속한 클래스이며 타입 Type 정보에 포함된다.
컴파일러 경고를 나타내는 Obsolete 애트리뷰트
class MyClass
{
[Obsolete("MyMethod is Deprecated")]
public void MyMethod()
{
Console.WriteLine("Deprecated Method");
}
public void NewMethod()
{
Console.WriteLine("OK");
}
}
호출자 정보 애트리뷰트
C/C++에서 제공하는 __FILENAME__, __LINE__, __FUNCTION__ 등의 매크로 기능이 C# 5.0 이상부터 애트리뷰트를 사용하여 사용할 수 있다. System.Runtime.CompilerServices 네임스페이스에 포함되어 있다.
public static class Trace
{
public static void Method(string message,
[CallerFilePath] string file = "",
[CallerLineNumber] int line = 0,
[CallerMemberName] string member = "")
{
Console.WriteLine("{0}(Line:{1}) {2} : {3}", file, line, member, message);
}
}
- CallerFilePath : 호출된 메서드가 포함된 소스 파일의 전체 경로
- CallerLineNumber : 소스 파일에서 호출된 메서드의 행 번호
- CallerMemberName : 소스 파일에서 호출된 메서드 혹은 프로퍼티 이름을 나타낸다.
애트리뷰트로 수식한 매개변수는 사용자가 해당 매개변수를 전달하지 않아도 된다.
새로운 애트리뷰트를 정의하고 사용하기
애트리뷰트는 System.Attribute를 상속하는 클래스이므로 이를 상속하여 새로운 애트리뷰트를 정의할 수 있다.
[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple = true)]
class History : System.Attribute
{
private string writer;
public string Date { get; set; }
public string Notes { get; set; }
public History(string writer)
{
this.writer = writer;
Date = "2021.10.10";
Notes = "First Release";
}
public string Writer
{
get {return writer; }
}
}
[History("Sean", Date = "2021.10.10", Notes = "Created MyClass2")]
[History("Ahn", Date = "2021.10.12", Notes = "Added Func() Method")]
class MyClass2 {
public void Func() { }
}
System.AttributeUsage를 통해 애트리뷰트 클래스에도 애트리뷰트를 만들 수 있다. 해당 애트리뷰트는 클래스를 설명하고 둘 이상의 설명 애트리뷰트를 사용하기 위해 AllowMultiple = true를 지정하여 사용하고 있다.
'C# > C# 기본' 카테고리의 다른 글
[C#] 파일 입출력 (0) | 2021.10.21 |
---|---|
[C#] dynamic 타입 (0) | 2021.10.19 |
[C#] LINQ (0) | 2021.10.18 |
[C#] 람다식 (0) | 2021.10.15 |
[C#] 대리자와 이벤트 (0) | 2021.10.15 |