- 리플렉션
- Object.GetType()메소드와 Type클래스
- 리플렉션을 이용하여 객체를 생성하는 방법을 이해한다 .
- 애트리뷰트가 무엇인지 이해한다 .
- 애트리뷰트를 작성하고 사용하는 방법을 익힌다 .
[ 리플렉션 ]
[ 리플렉션 ]
- 객체를 X_RAY사진처럼 객체의 형식정보를 들여다보는 기능 .
- 프로그램 실행중 객체의 형식이름/프로퍼티 목록/메소드 목록/필드/이벤트 목록까지 열어 볼 수 있다 .
- 형식의 이름만 있다면 동적으로 인스턴스를 만들 수 있고 , 인스턴스의 메소드를 호출 가능하다 .
- 새로운 데이터 형식을 동적으로 만들 수 있다 .
- 런타임중 형식정보를 다룰 수 있는 리플렉션은 강력한 표현력을 선사한다 .
[ Object.GetType()메소드와 Type 클래스 ]
- GetType () 메소드는 Type 형식의 결과를 반환한다 .
- Type 형식은 .Net에서 사용하는 데이터 형식의 모든 정보를 담고 있다 .
int a = 0;
Type type=a.GetType();
FieldInfo[]fields = type.GetFields();
foreach(FieldInfo field in fields)
Console.WriteLine("Type : {0},Name : {1}",field.FieldType.Name,field.Name);
메소드 | 반환형식 | 설명 |
GetConstructors() | ConstructorInfo[] | 해당 형식의 모든 생성자 목록을 반환 . |
GetEvents() | EventsInfo[] | 해당 형식의 이벤트 목록을 반환 . |
GetFields() | FieldInfo[] | 해당 형식의 필드 목록을 반환 . |
GetGenericArguments() | Type[] | 해당 형식의 매개변수 목록을 반환 . |
GetInterfaces() | Type[] | 해당 형식이 상속하는 인터페이스 목록을 반환 . |
GetMembers() | MemberInfo[] | 해당 형식의 멤버 목록을 반환 . |
GetMethods() | MethodInfo[] | 해당형식의 메소드 목록을 반환 . |
GetNestedTypes() | Type[] | 해당 형식의 내장 형식 목록을 반환 . |
GetProperties() | PropertyInfo() | 해당 형식의 프로퍼티 목록을 반환 . |
Type type = a.GetType();
//public 인스턴스 필드 조회
var fields1 = type.GetFields(BindingFlags.Public | BindingFlags.Instance );
//비public 인스턴스 필드 조회
var field2 = type.GetFields(BindingFlags.NonPublic | BindingFlags.Instace );
//public 정적 필드 조회
var fields3 = type.GetFields(BindingFlags.Public | BindingFlags.Static );
//비public 정적 필드 조회
var field4 = type.GetFields(BindingFlags.NonPublic | BindingFlags.Static );
- GetFields() / GetMethods()는 검색옵션의 지정이 가능하다 .
- System.Reflection/BindingFlags 열거형을 이용해서 구성된다 .
- GetFields() / GetMethods()등의 메소드는 BindingFlags매개변수를 받지 않는다면 public 멤버만 반환한다 .
using System;
using System.Linq;
using System.Reflection;
namespace ConsoleApp;
class MainApp
{
static void PrintInterfaces(Type type)
{
Console.WriteLine("-----Interfaces-----");
Type[]interfaces = type.GetInterfaces();
foreach (Type interfaceType in interfaces)
{
Console.WriteLine("Name{0}",interfaceType.Name);
}
Console.WriteLine();
}
static void PrintFields(Type type)
{
Console.WriteLine("-----Fields-----");
FieldInfo[] info = type.GetFields(BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance);
foreach (FieldInfo field in info)
{
string accessLevel = "protected";
if (field.IsPublic) accessLevel = "public";
else if (field.IsPrivate) accessLevel = "private";
Console.WriteLine("Access : {0} , Name : {1} , Type : {2}",accessLevel,field.Name,field.FieldType.Name);
}
Console.WriteLine();
}
static void PrintMethod(Type type)
{
Console.WriteLine("-----Methods-----");
MethodInfo[] methos = type.GetMethods();
foreach(MethodInfo method in methos)
{
Console.Write("Type : {0} , Name : {1} , Parameter : ", method.ReturnType.Name, method.Name);
ParameterInfo[]args = method.GetParameters();
for(int i=0;i<args.Length; i++)
{
Console.Write("{0}", args[i].ParameterType.Name);
if(i<args.Length-1) Console.Write(", ");
}
Console.WriteLine();
}
Console.WriteLine();
}
static void PrintProperties(Type type)
{
Console.WriteLine("----Properties-----");
PropertyInfo[] props = type.GetProperties();
foreach(PropertyInfo property in props)
{
Console.WriteLine("Type:{0},Name:{1}",property.PropertyType.Name,property.Name);
}
Console.WriteLine();
}
static void Main(string[] args)
{
int a = 0;
Type type=a.GetType();
PrintInterfaces(type);
PrintFields(type);
PrintProperties(type);
PrintMethod(type);
}
}
- Object.GetType 연산자 이외에도 typeof 연산자와 Type.GetType()메소드가 제공된다 .
//typeof 연산자는 형식의 식별자 자체를 인수로 받는다
Type a = typeof(int);
//Type.GetType() 메소드의 인수는 형식의 네임스페이스를 포함하는 전체이름
Type b = GetType("System.Int32");
[ 리플렉션을 이용해서 객체 생성하기 ]
- Systme.Activator 클래스의 도움으로 리플렉션을 이용해서 동적으로 인스턴스를 만들 수 있다 .
- 또한 프로퍼티에 값을 할당하는 것도 동적으로 가능하다 .
- 메소드 역시 가능한데 , Invoe()메소드를 통해 동적으로 메소드 호출이 가능하다 .
using System;
using System.Linq;
using System.Reflection;
namespace ConsoleApp;
class MainApp
{
class Profile
{
string name;
string phone;
public Profile(string name,string phone)
{
this.name = name;
this.phone = phone;
}
public void Print()
{
Console.WriteLine("Name :{0} , Phone :{1}",name,phone);
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Phone
{
get { return phone; }
set { phone = value; }
}
}
static void Main(string[] args)
{
Type type = Type.GetType("ConsoleApp.Profile");
MethodInfo methodInfo = type.GetMethod("Print");
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, "123", null);
Console.WriteLine($"{nameProperty.GetValue(profile, null)}");
Console.WriteLine($"{phoneProperty.GetValue(profile, null)}");
}
}
[ 형식 내보내기 ]
- 프로그램 실행 중에 새로운 형식을 만들어낼 수 있는 기능을 제공한다 .
- System.Reflectioin.Emit(내보낸다) 네임스페이스의 클래스들을 통해 이루어진다 .
- 즉 , 프로그램이 실행 중에 만들어낸 새 형식을 CLR에 내보낸다는 뜻 .
- 다음 표는 Emit 네임스페이스에서 제공하는 클래스의 목록이다 .
클래스 | 설명 |
AssemblyBuilder | 동적 어셈블리를 정의하고 나타냄 |
ConstructorBuilder | 동적으로 만든 클래스의 생성자를 정의하고 나타냄 |
CustomAttributeBuilder | 사용자 정의 애트리뷰트를 만든다 |
EnumBuilder | 열거 형식을 정의하고 나타냄 |
EventBuilder | 클래스의 이벤트를 정의하고 나타냄 |
FieldBuilder | 필드를 정의하고 나타냄 |
GenericTypeParameterBuilder | 동적으로 정의된 형식(클래스)/메소드를 위한 일반화 형식 매개변수를 정의하고 생성 |
ILGenerator | MSIL(Microsoft Intermediate Language)명령어를 생성한다 . |
LocalBuilder | 메소드나 생성자 내의 지역변수를 나타낸다 . |
MethodBuilder | 동적으로 만든 클래스의 메소드(또는 생성자)를 정의하고 나타낸다 . |
ModuleBuilder | 동적 어셈블리 내의 모듈을 정의하고 나타낸다 . |
OpCodes | ILGenerator 클래스의 멤버를 이용한 내보내기 작업에 사용할 MSIL 명령어의 필드 표현을 제공한다 . |
ParametreBuilder | 매개변수 정보를 생성하거나 결합한다 |
PropertyBuilder | 형식(클래스)의 프로퍼티를 정의한다 . |
TypeBuilder | 실행 중에 클래스를 정의하고 생성한다 . |
- 해당 클래스의 사용 요령은 다음과 같다 .
- AssemblyBuilder를 이용해서 어셈블리를 만든다 .
- ModuleBuider를 이용해서 생성한 어셈블리 안에 모듈을 만들어 넣는다 .
- 모듈 안에 TypeBuilder로 클래스 (형식)을 만들어 넣는다
- 클래스안에 MethodBuilder / PropertyBuilder로 메소드나 프로퍼티를 만들어 넣는다
- 메소드를 생성했다면 ILGenerator를 이용 ,메소드 안에 CPU가 실행할 IL 명령어를 넣는다 .
using System;
using System.Reflection;
using System.Reflection.Emit ;
namespace ConsoleApp;
class MainApp
{
static void Main(string[] args)
{
//AssemblyBuilder는 생성자가 없어 팩토리 클래스의 도움이 필요 .DefineDynamicAssembly를 통해 AssemblyBuilder 인스턴스 생성
AssemblyBuilder newAssembly = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName("CalculateAssembly"),AssemblyBuilderAccess.Run);
//모듈 생성
ModuleBuilder newModule = newAssembly.DefineDynamicModule("Calculator");
//클래스 생성
TypeBuilder newType = newModule.DefineType("Sum1To100");
//메소드 생성
MethodBuilder newMehtod=newType.DefineMethod("Calculate",MethodAttributes.Public,typeof(int),new Type[0]);
//IL 명령어 (실행할 코드)생성
ILGenerator generator = newMehtod.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);//계산 스택에 담겨 있는 값 반환
newType.CreateType();//CLR에 클래스 제출
object sum1To100 = Activator.CreateInstance(newType);
MethodInfo Calculate = sum1To100.GetType().GetMethod("Calculate");
Console.WriteLine(Calculate.Invoke(sum1To100, null));
}
}
[ 애트리뷰트 ]
[ 애트리뷰트 ]
- 코드에 대한 부가 정보를 기록하고 읽을 수 있는 기능
- 주석은 사람이 읽고 쓰는 정보라면 애트리뷰트는 사람이 쓰고 컴퓨터가 읽는 것
[메타 데이터 ]
- 메타데이터란 데이터의 데이터 .
- C#코드도 데이터지만 해당 코드에 대한 정보도 존재하는데 , 이를 메타데이터라 한다 .
- 애트리뷰트 / 리플렉션을 통해 얻는 정보들도 C# 코드의 메타 데이터 .
[ 애트리뷰트 사용하기 ]
using System;
using System.Reflection;
using System.Reflection.Emit ;
namespace ConsoleApp;
class MyClass
{
[Obsolete("OldMethod는 폐기되었습니다 . NewMethod()를 사용해주세요")]
public void OldMethod()
{
Console.WriteLine("Old Method");
}
public void NewMethod()
{
Console.WriteLine("New Method");
}
}
class MainApp
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.OldMethod();
myClass.NewMethod();
}
}
- 정상적으로 실행하지만 컴파일시 경고목록에 다음과 같이 경고메시지를 띄운다 .
[ 호출자 정보 애트리뷰트 ]
- C,C++에서 사용하는 __FILENAME(소스파일 이름) / __LINE(행 번호) / __FUNCTION(함수이름) 매크로의 기능을
C#은 제공하지 않는다 .
- 위 매크로는 컴파일러에 의해 치환되어 코드에 들어간다 .
- C# 5.0부터는 호출자 정보 애트리뷰트로 위의 기능을 대신한다 .
- 호출자 정보는 메소드의 매개변수에 사용되며 , 메소드 호출자 이름/호출자 메서드 정의 소스파일 경로/소스파일내 번호
등을 알 수 있다 .
- 응용 프로그램의 이벤트를 로그 파일이나 화면에 출력하여 해당이벤트가 어느 코드에서 발생하는지 알 수 있다 .
애트리뷰트 | 설명 |
CallerMemberNameAttribute | 현재 메소드를 호출한 메소드 또는 프로퍼티의 이름을 나타낸다 . |
CallerFilePathAttribute | 현재 메소드가 호출된 소스 파일 경로 . 이때의 경로는 소스코드를 컴파일할 때의 전체경로 |
CallerLineNumberAttribute | 현재 메소드가 호출된 소스 파일 내의 행 번호를 나타낸다 . |
using System;
using System.Reflection;
using System.Reflection.Emit ;
using System.Runtime.CompilerServices;
namespace ConsoleApp;
public static class Trace
{
public static void WriteLine(string message, [CallerFilePath]string file = "", [CallerLineNumber]int line = 0, [CallerMemberName]string member="")
{
Console.Write("{0}(Line:{1}) {2} : {3}", file, line, member, message);
}
}
class MainApp
{
static void Main(string[] args)
{
Trace.WriteLine("즐거운 프로그래밍!");
}
}
[ 내가 만드는 애트리뷰트 ]
using System;
using System.Reflection;
using System.Reflection.Emit ;
using System.Runtime.CompilerServices;
namespace ConsoleApp;
//어트리뷰트의 어트리뷰트 . 클래스 대상으로 여러번 사용 가능하게 함 . Target은 논리합 연산자로 결합이 가능하다 .
[System.AttributeUsage(System.AttributeTargets.Class,AllowMultiple =true)]
class History:System.Attribute
{
string programmer;
public double version;
public string changes;
public History(string programmer)
{
this.programmer = programmer;
this.version = 1.0;
this.changes = "First Release";
}
public string GetProgrammer()
{
return programmer;
}
}
//커스텀 어트리뷰트
[History("sean",version =0.1,changes ="2017 - 11- 01 Created class stub")]
[History("Bob", version = 0.2, changes = "2017 - 11- 15 Added Func()Method")]
class MyClass
{
public void Func()
{
Console.WriteLine("Func");
}
}
class MainApp
{
static void Main(string[] args)
{
Type type = typeof(MyClass);
Attribute[] attributes = Attribute.GetCustomAttributes(type);
Console.WriteLine("MyClass Chage history ....");
foreach (Attribute attribute in attributes)
{
History h = attribute as History;
if (h != null)
Console.WriteLine("Ver:{0},Progremmer : {1},Chages : {2}",h.version,h.GetProgrammer(),h.changes);
}
}
}
- 어트리뷰트를 직접 만들어 활용 할 수 있다 .
- 위와 같이 History 어트리뷰트와 리플렉션을 활용 , 손쉽게 Release 노트를 만들 수 있다 .
'C# > 이것이 C#이다' 카테고리의 다른 글
[ 이것이 C#이다 ] Chapter 19. 스레드와 태스크 (0) | 2024.04.12 |
---|---|
[ 이것이 C#이다 ] Chapter 18. 파일 다루기 (0) | 2024.04.11 |
[ 이것이 C#이다 ] Chapter 15 . LINQ (0) | 2024.03.27 |
[ 이것이 C#이다 ] Chapter 14 . 람다식 (0) | 2024.03.25 |
[ 이것이 C#이다 ] Chapter 13 . 대리자와 이벤트 (0) | 2024.03.21 |