전체 페이지뷰

2017년 1월 9일 월요일

C# Reflection

리플렉션 객체는 런타임에 형식정보를 얻는데에 이용됩니다. 이 기능을 사용하여 실핼 중에 객체의 형식 이름, 포로퍼티, 메소드, 필드, 이벤트 목록을 모두 볼수 있고, 해당 메소드를 호출하거나 필드, 프로퍼티에 접근하는 것도 가능합니다.  프로그램의 metadata에 접근할 수 있도록 해주는 이 클래스는 System.Refelection namespace에 정의되어 있습니다.



모든 데이터 형식의 조상인 Object 형식에 GetType() 메소드를 만들어 둠으로써, 파생되는 모든 형식에서 Type을 볼수 있게 되었습니다.

Type 클래스

Object는 다음의 5개 메소드를 가지고 있습니다.
Equals()
GetHashCode()
GetType()
ReferenceEquals()
ToString()

그 말은 곧 모든 데이터 형식이 저 5개의 메소드를 가지고 있다는 뜻입니다. Object는 모든 형식의 조상이기 때문입니다.

그 중 GetType() 메소드는 Type 형식의 결과를 반환합니다. Type은 .NET에 있는 모든 데이터 형식의 정보를 담고 있습니다.

using System;
using System.Reflection;
namespace GetTypeExample
{    
    class Program
    {
        static void Main(string[] args)
        {
            int i = 42;
            Type type = i.GetType();
            Console.WriteLine(type);
            FieldInfo[] fields = type.GetFields(); // 필드 목록 조회
            
            foreach (var field in fields)
            {
                Console.WriteLine("Type:{0}, Name:{1}", field.FieldType.Name, field.Name);
            }
        }
    }
}
cs

위의 예제는 int 형식의 필드 목록을 뽑아볼수 있는 예제입니다.

위의 예제에서는 GetType() 메소드를 이용해서 타입 정보를, GetFields()를 이용해서 FieldInfo를 빼냈지만, 이것 말고도 이용할 수 있는 메소드는 많이 있습니다.

메소드
반환 형식
설명
GetConstructors() ConstructorInfo[] 생성자 목록 반환
GetEvents() EventInfo[] 이벤트 목록 반환
GetFields() FieldInfo[] 필드 목록 반환
GetGenericArguments() Type[] 매개 변수 목록 반환
GetInterfaces() Type[] 인터페이스 목록 반화
GetMembers() MemberInfo[] 멤버 목록 반화
GetMethods() MethodInfo[] 메소드 목록 반환
GetNestedType() Type[] 내장 형식 목록 반환
GetProperties() PropertyInfo[] 포로퍼티 목록 반환

이 외에도 어마어마한 양의 Type 메소드들을 MSDN에서 찾아 볼 수 있습니다.

System.Reflection.BindingFlags 열거형 플래그를 이용해서 검색 조건을 부여할 수가 있습니다(물론 매개변수 없는 메소드도 당연히 가능합니다).

// public 인스턴스 필드 조회
GetFields( BindingFlags.Public | BindingFlags.Instance )

// NonPublic 인스턴스 필드 조회
GetFields( BindingFlags.NonPublic | BindingFlags.Instance )

// public 정적 필드 조회
GetFields( BindingFlags.Public | BindingFlags.Static )

// NonPublic 정적필드 조회
GetFields( BindingFlags.NonPublic | BindingFlags.Static )


처럼 사용 가능합니다.

사용 가능한 플래그들은 다음과 같습니다.
Identified by Accessibility
Identified by Binding Argument
Identified by Operation
DeclaredOnly
FlattenHierarchy
IgnoreCase
IgnoreReturn
Instance
NonPublic
Public
Static
ExactBinding
OptionalParamBinding
CreateInstance
GetField
SetField
GetProperty
SetProperty
InvokeMethod
PutDispProperty
PutRefDispProperty

이제 예제 프로그램을 한번 작성해 보겠습니다.

using System;
using System.Reflection;
namespace GetTypeExample
{    
    class Program
    {
        static void ShowInterfaces(Type type)
        {
            Console.WriteLine("--------Interfaces-------");
            Type[] interfaces = type.GetInterfaces();
            foreach (Type i in interfaces)
                Console.WriteLine("Name: {0}", i.Name);
            Console.WriteLine();
        }
        static void ShowFields(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:{0}, Type:{1}, Name:{2}"
                    accessLevel, field.FieldType.Name, field.Name);
            }
            Console.WriteLine();
        }
        static void ShowMethods(Type type)
        {
            Console.WriteLine("--------Methods-------");
            MethodInfo[] methods = type.GetMethods();
            foreach (MethodInfo method in methods)
            {
                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 ShowConstructors(Type type)
        {
            Console.WriteLine("--------Constructors-------");
            ConstructorInfo[] constructors = type.GetConstructors();
            foreach (ConstructorInfo constructor in constructors)
                Console.WriteLine("Name:{0}", constructor.Name);
        }
        static void ShowProperties(Type type)
        {
            Console.WriteLine("--------Properties-------");
            PropertyInfo[] properties = type.GetProperties();
            foreach (PropertyInfo property in properties)
                Console.WriteLine("Type:{0}, Name:{1}",
                    property.PropertyType.Name, property.Name);
            Console.WriteLine();
        }
        static void Main(string[] args)
        {
            int i = 1;
            Type type = i.GetType();
            ShowInterfaces(type);
            ShowFields(type);
            ShowProperties(type);
            ShowMethods(type);
            ShowConstructors(type);
        }
    }
}
cs

결과)
--------Interfaces-------
Name: IComparable
Name: IFormattable
Name: IConvertible
Name: IComparable`1
Name: IEquatable`1

--------Fields-------
Access:protected, Type:Int32, Name:m_value
Access:public, Type:Int32, Name:MaxValue
Access:public, Type:Int32, Name:MinValue

--------Properties-------

--------Methods-------
Type:Int32, Name:CompareTo, Parameter:Object
Type:Int32, Name:CompareTo, Parameter:Int32
Type:Boolean, Name:Equals, Parameter:Object
Type:Boolean, Name:Equals, Parameter:Int32
Type:Int32, Name:GetHashCode, Parameter:
Type:String, Name:ToString, Parameter:
Type:String, Name:ToString, Parameter:String
Type:String, Name:ToString, Parameter:IFormatProvider
Type:String, Name:ToString, Parameter:String, IFormatProvider
Type:Int32, Name:Parse, Parameter:String
Type:Int32, Name:Parse, Parameter:String, NumberStyles
Type:Int32, Name:Parse, Parameter:String, IFormatProvider
Type:Int32, Name:Parse, Parameter:String, NumberStyles, IFormatProvider
Type:Boolean, Name:TryParse, Parameter:String, Int32&
Type:Boolean, Name:TryParse, Parameter:String, NumberStyles, IFormatProvider, Int32&
Type:TypeCode, Name:GetTypeCode, Parameter:
Type:Type, Name:GetType, Parameter:

--------Constructors-------


이와 같이 int 형식의 각종 정보에 대해 정보를 얻어올 수 있습니다.
그럼 이런 리플렉션으로 대체 무얼 할 수 있는 것일까요?


리플렉션을 이용한 객체 생성

리플렉션을 이용하면 런타임에 특정 형식의 인스턴스를 만들 수 있습니다. 
System.Activator 클래스를 사용하면 그것이 가능해 지는데요.

object a = Activator.CreateInstance(typeof(int));

처럼 하면 int형의 인스턴스를 생성하게 됩니다. 
제너릭도 가능합니다.

List<int> list = Activator.CreateInstance<List<int>>();

와 같은 형식입니다. 프로퍼티의 값 설정이나 메소드의 호출도 동적으로 가능합니다.
PropertyInfo의 SetValue(), GetValue()메소드, MethodInfo의 Invoke()라는 메소드를 이용하면 말이죠.

using System;
using System.Reflection;
namespace DynamicInstance
{    
    class Profile
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public Profile()
        {
            Name = ""; Address = "";
        }
        public Profile(string name, string address)
        {
            Name = name; Address = address;
        }
        public void Print()
        {
            Console.WriteLine("이름:{0}, 주소:{1}", Name, Address);
        }
    }
    class Program
    {       
        static void Main(string[] args)
        {
            Type type = Type.GetType("DynamicInstance.Profile");
            MethodInfo methodInfo = type.GetMethod("Print");
            PropertyInfo NameProperty = type.GetProperty("Name");
            PropertyInfo AddressProperty = type.GetProperty("Address");
            object profile = Activator.CreateInstance(type, "Rachael Mc.Adams""남양주");
            methodInfo.Invoke(profile, null);
            profile = Activator.CreateInstance(type);
            NameProperty.SetValue(profile, "Babara palvin"null);
            AddressProperty.SetValue(profile, "구리시"null);
            Console.WriteLine("{0}, {1}"
                NameProperty.GetValue(profile, null), 
                AddressProperty.GetValue(profile, null));
        }
    }
}
cs

결과)
이름:Rachael Mc.Adams, 주소:남양주
Babara palvin, 구리시


댓글 없음:

댓글 쓰기