C# Reflection & Attribute

선비Sunbei·2023년 1월 10일
0

C#

목록 보기
10/18
post-thumbnail

Reflection

System.Reflection 네임스페이스와 System.Type을 함께 사용하여 로드된 어셈블리 및 해당 어셈블리 내에 정의된 형식(클래스, 인터페이스, 값 형식 등등)에 대한 정보를 불러 올 수 있고, 리플렉션을 사용하여 런타임에 인스턴스를 호출 및 액세스가 가능하다.

리플렉션은 형식, 멤버, 매개 변수, 기타 코드 엔터티를 나타내는 Type 및 MethidInfo와 같은 클래스를 제공한다. 하지만 리플렉션을 사용할 때 이들 클래스를 직접 사용하지 않는다.
이들 클래스는 대부분 추상클래스이다. 따라서 CLR에서 제공된 형식을 사용한다.

예를 들어서 C#의 typeof 연산자를 사용하여 Type 개체를 가져오면 개체가 실제로 RuntimeType이다.(혹은 객체.GetType() 메서드 이용) RuntimeType은 Type에서 파생된 것으로 모든 추상 메서드의 구현을 제공한다.

정리하면, 우리는 Runtime에 typeof 연산자를 통해 가져온 것을 통해서 정보를 불러오거나 런타임에 인스턴스를 호출 및 액서스 할 수 있다.

using System;
using System.Reflection;
// MethodInfo,PropertyInfo 클래스 사용을 위해(Reflection) unsing namespace를 해야 한다.

namespace Study19
{
    class ClassName
    {

        public int value { get; private set; } = 10;

        public void Act1()
        {
            Console.WriteLine("function : Act1");
        }
        private void Act2()
        {
            Console.WriteLine("function : Act2");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ClassName cn = new ClassName();

            // Method 호출
            MethodInfo[] mInfos = cn.GetType().GetMethods();
            foreach(MethodInfo mInfo in mInfos)
            {
                Console.WriteLine(mInfo.Name);
                if(mInfo.Name == "Act1" || mInfo.Name == "Act2")
                    mInfo.Invoke(cn, null);
            }

            Console.WriteLine("");

            // 프로퍼티 수정
            Console.WriteLine("value : {0}",cn.value);
            PropertyInfo[] pInfos = cn.GetType().GetProperties();
            foreach(PropertyInfo pInfo in pInfos)
            {
                Console.WriteLine(pInfo.Name);
                if (pInfo.Name == "value")
                    pInfo.SetValue(cn, 20);
            }
            Console.WriteLine("value : {0}", cn.value);

        }
    }
}
/*
get_value
Act1
function : Act1
GetType
ToString
Equals
GetHashCode

value : 10
value
value : 20
*/

cn 인스턴스의 모든 메서드를 확인할 수 있고 실행할 수 있다.
주의할 점은 'set_value'가 없다는 것인데, value의 set프로퍼티를 private으로 해서 검색이 안되기 때문이다.
이에 반해서 프로퍼티 수정의 경우 'set_value'함수를 통해서 값을 수정하는 것이 아니기 때문에 privte이라도 수정할 수 있다.

Attribute

애트리뷰트는 클래스에 메타데이터를 추가할 수 있도록 제공하는 것이다. 클래스부터 시작해서 메서드, 구조체, 생성자, 프로퍼티, 필드, 이벤트, 인터페이스 등 여러가지 요소에 애트르부트를 사용할 수 있다. 사용방법은 다음과 같다.
컴파일러에 각 데이터 별로 데이터에 대한 데이터를 넣을 수 있다고 생각하면 편하다.

[attribute명(인자~)]

C#에 기본적으로 정의되어있는 어트리뷰트가 존재하며, 사용자 정의도 가능하다.

[Conditional("정의")]

#define TEST // 전처리문은 가장 맨 위에 적어야 한다.

using System;
using System.Diagnostics;
// Conditional을 쓰기위해 꼭 써야 한다.

namespace Study20
{
    class Program
    {
        [Conditional("TEST")]
        static void Test()
        {
            Console.WriteLine("Function On");
        }

        static void Main(string[] args)
        {
            Test();
        }
    }
}

애트리뷰트 [Conditional("이름")]은 #define으로 정의되어 있어야만 호출이 된다.
#define Test를 지워도 호출자체는 가능하지만(에러 발생x) 함수 내용은 실행이 되지 않는다.

[Obsolete("내용")]

using System;


namespace Study20
{
    class Program
    {
        [Obsolete("OldFunction은 사용하지 않으니, NewFunction을 사용하세요.")]
        static void OldFunction()
        {
            Console.WriteLine("OldFunc");
        }
        static void NewFunction()
        {
            Console.WriteLine("NewFunc");
        }

        static void Main(string[] args)
        {
            OldFunction();
            NewFunction();

        }
    }
}

실행시켜보면 문제없지 작동은 한다. 하지만 해당 [Obsolete] 어트리뷰트를 쓰면 경고를 띄워준다.
만약 강제적으로 사용하지 못하게 하려면 [Obsolete("문자열",true)]로 변경하여 에러를 발생시킬 수 있다.

[DllImporter("xxx.dll)]

using System.Runtime.InteropServices;
// DLLimport를 사용하기 위해 꼭 써야 한다.

namespace Study20
{
    class Program
    {
        
        [DllImport("User32.dll")]
        public static extern int MessageBox(int hParent, string Messgae, string Caption, int Type);

        static void Main(string[] args)
        {
            MessageBox(0, "DLL_IMPORT","Attribute", 3);
        }
    }
}

extern 키워드는 프로그램 외부를 의미한다. 즉, 외부 DLL에 정의되어있는 함수를 사용할 때 DLLImport와 extern키워드로 얻어올 수 있다.

사용자 정의 Attribute

using System;

namespace Study21
{
    class Program
    {

        class CustomAttribute : System.Attribute
        {
            public string Name { get; private set; }
            public CustomAttribute(string name)
            {
                this.Name = name;
            }
        }

        [CustomAttribute("이름")]
        class TmpClass { }

        static void Main(string[] args)
        {
            TmpClass tmpClass = new TmpClass();

            foreach (Object attr in tmpClass.GetType().GetCustomAttributes(true))
            {
                if(attr.GetType() == typeof(CustomAttribute))
                {
                    CustomAttribute customAttribute = attr as CustomAttribute;
                    Console.WriteLine(customAttribute.Name);
                }
            }
            
        }
    }
}

클래스를 생성 후에 System.Attribute를 상속하게 될 경우 사용자 정의 Attribute를 생성할 수 있다.이렇게 생성할 경우 다른 Atrribute를 쓰듯이 [Attribute(인자)]를 쓰면 된다. 이것을 응용하는 방법으로 Reflection을 사용하여 특정 클래스의 정보를 얻어와서 런타임에 수정할 수 있다.

실제 유니티 엔진의 스크립트에서 private의 경우 엔진에 뜨지 않는다. 하지만 [SerializeField] Attribute를 사용할 경우 Inspecter 창에 뜬다.

0개의 댓글