본문 바로가기
언어(C, C++, C#)

[C#] 리플렉션(Reflection)과 속성(Attribute)

by 적용1 2024. 11. 14.
728x90

리플렉션(Reflection)

리플렉션은 프로그램 실행 중 메타데이터를 검사하고 객체의 정보(형식, 이름, 프로퍼티 목록, 메소드 목록, 필드, 이벤트 목록 등)를 탐색, 조작하는 기능을 제공하는 기법이다.

 

이를 통해 코드가 실행 중에 클래스, 메서드, 프로퍼티, 필드 등의 정보를 조회하고 다룰 수 있으며, 주로 System.Reflection 네임 스페이스에 정의된 클래스를 사용한다.

장단점

장점

  • 유연성 및 확장성 : 코드가 컴파일될 때 타입을 알 필요 없이 런타임에 타입 정보를 가져와 동적으로 조작할 수 있다. 이로 인해 애플리케이션이 런타임에 새로운 타입을 로두하고 사용할 수 있다.
  • 동적 로딩 및 실행 : 리플렉션은 Assembly.Load, Type.GetType 등의 기능을 통해 외부 라이브러리나 플러그인을 런타임에 동적으로 로드하고 실행할 수 있게 한다. 이를 통해 정적 타입으로 코드를 작성하지 않고도 다양한 외부 모듈을 이용할 수 있다.

  • 메타데이터 접근 가능 : C#의 리플렉션은 클래스, 메서드, 필드, 속성 등에 붙어있는 특성(Attribute) 정보를 가져올 수 있어, 코드 자체에 주석처럼 추가된 정보를 런타임에 접근하게 해준다.

단점

  • 성능 저하 : 리플렉션은 런타임에 타입을 해석하고, 메타데이터를 읽어 객체나 메서드에 접근하는 과정에서 문자열 검색과 같은 추가적인 오버헤드가 발생한다. 컴파일 시점에 타입을 결정하는 것보다 많은 리소스가 필요하다.

    또, 리플렉션을 통해 멤버를 호출하는 경우(런타임에 메서드나 생성자, 필드와 같은 클래스 멤버를 직접적으로 실행하는 것), 매개변수들을 object의 배열 형채로 만들고(1), 전달된 매개변수를 호출될 메서드의 매개변수에 맞게 스레드의 스택에 적재한다(2). 이 때 CLR이 런타임에 타입 호환성을 확인하게 되는데(3), 이러한 과정들로 인하여 일반적인 메서드 호출보다 비용이 높다. 따라서 성능에 좋지 않은 영향을 미치게 된다.
     
  • 안정성 저하 : 리플렉션으로 접근하는 메서드나 필드는 컴파일 시점에 존재하지 않는 것으로 간주되므로, 잘못된 메서드 이름이나 필드 접근으로 인해 런타임 오류가 발생할 수 있다. 또, 비공개 멤버에 접근하거나 예기치 않은 방식으로 객체를 수정할 수 있어 코드의 안정성을 해칠 수 있다.

  • 보안 위험 : 리플렉션을 사용하면 비공개 필드나 메서드에 접근할 수 있어, 객체의 내부 상태를 쉽게 변경할 수 있다.

메서드

System.Type은 특정 타입의 정보를 가져오는 데 사용된다. C#의 모든 객체들은 object에서 파생되어 나오기 때문에 object에 정의된 함수들을 사용할 수 있다.

  • GetType()

    object.GetType() 을 이용하여 객체가 가지고 있는 정보를 알 수 있고, 그 정보를 Type 자료형에 담을 수 있다.
    또는, Type.GetType(string name)을 통해 지정한 이름의 타입 정보를 가져올 수 있다.


  • GetMethods()

    클래스에 정의된 모든 메서드의 배열을 반환한다. BindingFlags를 통해 접근 제어자를 설정할 수 있다.

  • GetProperties()

    클래스에 정의된 모든 속성(Property) 정보를 가져온다.
  • GetFields()

    Type이 가지고 있는 멤버들, 즉 필드를 FieldInfo 타입의 배열로 반환한다.
    BindingFlags를 매개변수로 하여 검색 조건을 설정할 수 있다.(ex : System.Reflection.BindingFlags.Public => public인 멤버 추출)

Type 타입

  • 타입 참조를 나타내는 아주 가벼운 객체이다.
  • 만약 타입에 대해 더 알고 싶다면 TypeInfo 객체를 얻어와야하는데, 해당 객체를 가져오려고 시도하면 CLR은 타입을 정의하는 어셈블리가 로드되어 있는지 확인한다. 이 과정은 상당한 고비용의 작업이기 때문에 TypeInfo가 꼭 필요한 경우에만 불러오는 것이 좋다.

FieldInfo 타입

  • 필드의 이름, 속성 등에 대한 정보를 얻을 수 있는 속성과 메서드를 가지고 있다.

Attribute

Attribute(속성)은 클래스, 메서드, 속성, 필드, 매개변수, 어셈블리 등 다양한 요소에 추가적인 메타데이터를 제공하는 기능이다.

프로그램 실행 중 또는 컴파일 타임에 특정 동작을 정의하거나 데이터를 제공하기 위해 사용된다.

주요 특징

1. 메타데이터 제공

  • Attribute는 클래스, 메서드, 프로퍼티 등에 부가 정보를 추가하여 이를 런타임이나 컴파일 타임에 활용할 수 있게 한다.

    예를 들어 [Obsolete] 속성은 특정 메서드나 클래스가 더 이상 사용되지 않음을 나타낸다.

2. Reflective Programming

  • Reflection을 통해 Attribute를 읽어 실행 중에 동적으로 동작을 정의할 수 있다.

    예를 들어, [Serializable] 속성을 사용하면 객체 직렬화를 위한 클래스를 정의할 수 있다.

사용 방법

1. Attribute로 이용할 클래스를 만들고 System.Attribute를 상속받는다.(기본적인 Attribute를 사용할 경우에는 만들 필요 없다.)

2. 멤버 변수나 메서드 바로 위에 [Attribute 이름] 을 추가해준다.

3. 생성자에 매개변수를 추가하여 추가 정보를 제공할 수 있다.

기본 제공 Attribute

[Serializable]
public class MyData
{
	public int Id { get; set; }
    public string Name { get; set; }
}

사용자 정의 Attribute

using System;	// System 네임스페이스

class Important : Attritube
{
	private string message;
    
    public Important(string message)	// 생성자를 이용하여 메세지를 입력한다.
    {
    	this.message = message;
    }
    
    public string Message => message;	// 메세지를 읽을 수 있도록 함
}

/////////////// Custom Attribute 클래스 생성 ///////////////////

class Test
{
	[Important("Very Important")]
	public int hp;
}

class Program
{
	static void Main(string[] args)
    {
    	Type type = typeof(Test);	// Test의 타입 정보를 가져옴(리플렉션)
        
        FieldInfo field = type.GetField("hp");	// 해당 타입의 필드 정보를 가져옴
        
        if(field != null)
        {
        	var attributes = field.GetCustomAttributes(typeof(Important), false);
            // 해당 필드에 붙은 Custom Attribute를 확인
            
            foreach(Important attr in attributes)
            {
            	Console.WriteLine($"Field: {field.Name}, Attribute Message: {attr.Message}");
            }
        }
    }
}

 

이렇게 되면 hp는 Important 라는 정보를 담고 있다는 것을 런타임에 알 수 있게 됐다.

추가적으로 hp는 "Very Important"라는 메세지를 담고 있는 것을 확인할 수 있다.

 

위 코드와 같이 리플렉션을 통해 메타데이터를 읽을 때 사전에 추가해둔 Attribute를 가져와 해당 Attribute에 저장된 메세지를 읽을 수 있다.

Reflection과 Attribute를 함께 사용하는 이유

1. 속성은 메타데이터를 정의한다.

2.리플렉션으로 메타데이터를 읽는다.

3.해당 메타데이터를 기반으로 코드가 어떻게 동작하는지 결정할 수 있다.

 

위와 같은 과정을 통해 강력한 동적 프로그래밍을 할 수 있다.

 

즉, 요약하면

  • Attribute를 통해 런타임 중 참고할 수 있는 메타데이터를 남길 수 있다.(추가할 수 있다.)
  • Reflection을 통해 런타임 중 객체의 모든 정보에 접근할 수 있다.
728x90