[C#] 31. 어트리뷰트(Attribute)를 사용하는 방법


Study/C#  2021. 9. 17. 15:49

안녕하세요. 명월입니다.


이 글은 C#에서 어트리뷰트(Attribute)를 사용하는 방법에 대한 글입니다.


C#에서의 어트리뷰트(Attribute)란 클래스나 메소드에 메타데이터를 기록하는 데이터입니다.

여기서 메타데이터란 예를 들면 우리가 이미지 파일을 보면 이미지에 대한 바이너리 데이터로 이루어져 있습니다. 물론 이 바이너리 데이터에도 이미지에 대한 정보가 있습니다만, 파일 이름이나 확장자 이름 등에 대한 데이터는 없습니다.

즉, 이런 내용은 윈도우 OS에서 파일에 대한 메타데이터 정보입니다.


다시 설명하면, 클래스나 메소드에 대한 구분을 하기 위한 표시 데이터라고 생각하면 됩니다.

using System;

namespace Example
{
  // Obsolete 어트리뷰트 사용, 파라미터는 에러 시의 메시지와 경고를 할 것인지 에러를 발생할 것인지에 대한 true, false
  [Obsolete("Not used", true)]
  class Example
  {

  }
  class Program
  {
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var e = new Example();
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 Obsolete의 어트리뷰트는 더 이상 함수나 클래스를 사용하지 않을 때, 에러를 발생하는 것입니다.

첫번째 파라미터에서는 에러 시의 메시지입니다. 위 결과 이미지를 보면 에러 메시지가 Not used라고 나옵니다.

그 다음에는 true를 하면 빌드 자체의 에러를 발생하는 것이고 false를 하면 함수나 클래스를 사용할 수는 있으나 경고 메시지가 발생합니다.


어트리뷰트는 위 예제처럼 실제 프로그램 실행에 영향이 가는 것은 아니고 프로그램의 빌드나 디버깅할 때의 메시지, 리플렉션(Reflection)에서 클래스나 메소드의 일괄 관리하는 용도로 사용됩니다.

리플렉션(Reflection)에서 어트리뷰트를 사용하는 방법은 리플렉션을 소개할 때 자세하게 설명하겠습니다.


먼저 C# .Net 프레임워크에서 기본적으로 제공하는 어트리뷰트는 아래와 같습니다.

어트리뷰트 설명
CLSCompliant 어셈블리의 모든 형을 CLS에 맞춰 사용
Obsolete 사용할 수 없는 요소라는 것을 나타냄
Conditional 전처리기 식별자에 의해 실행 여부를 결정
DllImport 비관리 코드 형태로 실행되는 메서드를 나타냄
Dispid COM의 DISPID ID(디스패치 식별자)를 나타냄
Serializable 클래스 또는 구조체가 직렬화할 수 있음을 나타냄
Transaction 트랜잭션이 무시되거나 지원 가능한가를 나타냄

저의 경우도 위의 어트리뷰트를 전부 사용해 본적은 없습니다.

그러나 가장 많이 사용되는 어트리뷰트 중에는 Serializable, 클래스를 바이너리로 변환하는 직렬화할 때 사용됩니다.

링크 - [C#] 직렬화 (Serialization)


DllImport의 어트리뷰트의 경우는 .Net에서 만들어진 라이브러리가 아닌 C/C++에서 만들어진 외부 라이브러리를 C#에서 사용할 때의 어트리뷰트입니다.

링크 - [C++] C++ Dll 만들어서 C#에서 사용하기 (마샬링)

링크 - [C#] ini 환경 설정 파일을 다루는 방법

using System;
using System.Runtime.InteropServices;

namespace Example
{
  class Program
  {
    // C/C++에서 사용되는 User32.dll를 Import한다.
    [DllImport("User32.dll")]
    // 그 중 MessageBox를 가져옵니다.
    public static extern int MessageBox(int hWnd, string lpText, string lpCaption, int uType);

    static void Main(string[] args)
    {
      // C/C++의 MessageBox 박스를 사용한다.
      MessageBox(0, "Hello world", "nowonbun", 0);
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

Obsolete의 경우도 많이 사용되는 어트리뷰트입니다. Visual studio에서 클래스와 함수의 사용 경고에 대한 어트리뷰트입니다.

보통의 프로그램 개발에서 보다는 프로그램을 업그레이드나 성능, 유지보수(Maintenance) 할 때 사용하는데, 기존에 사용하지 않는 클래스나 함수를 그냥 삭제해 버리기 보다는 어트리뷰트를 작성하고 사용 못하게 남기는 방법도 있습니다.

이건 개발 성향에 다르지만... 참고로 소스 지저분해지는 게 싫어서 Obsolete 어트리뷰트를 사용하기 보단 그냥 지웁니다.


.Net framework에서 기본으로 제공되는 어트리뷰트도 자주 사용하지만, 대체적으로 사용자가 어트리뷰트를 만들어서 사용합니다.

using System;
using System.Reflection;
using System.Linq;

namespace Example
{
  // 어트리뷰트 생성
  // AttributeUsage의 어트리뷰트는 어디에서 사용할 지에 대한 정의.
  // 해당 어트리뷰트는 함수에서만 사용된다.
  [AttributeUsage(AttributeTargets.Method)]
  class ExampleAttribute : Attribute
  {
    // 프로퍼티
    public String Name { get; set; }
    public String Message { get; set; }
    // 어트리뷰트의 생성자는 사용할 때, 필수 항목이 된다.
    public ExampleAttribute(string name)
    {
      // Name 프로퍼티 설정
      this.Name = name;
    }
  }
  class Program
  {
    // 예제 함수, Name은 생성자에서 필수 입력이라 넣어야 한다.
    [ExampleAttribute("Test")]
    private void Print1()
    {
      // 콘솔 출력
      Console.WriteLine("Print1");
    }
    // 예제 함수, Message는 필수 항목은 아니기 때문에 Message 프로퍼티에 넣는다.
    [ExampleAttribute("Test", Message = "hello world")]
    private void Print2()
    {
      // 콘솔 출력
      Console.WriteLine("Print2");
    }
    // 예제 함수, Name은 생성자에서 필수 입력이라 넣어야 한다.
    [ExampleAttribute("Test1")]
    private void Print3()
    {
      // 콘솔 출력
      Console.WriteLine("Print3");
    }
    // 예제 함수
    private void Print4()
    {
      // 콘솔 출력
      Console.WriteLine("Print4");
    }
    // 실행 함수
    static void Main(string[] args)
    {
      // 인스턴스 생성
      var p = new Program();
      // 리플렉션(Reflection), Program 클래스에서 public, private 관계없이 메소드 추출
      var methods = typeof(Program).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
      // 반복문으로 데이터 추출
      foreach (var method in methods)
      {
        // 메소드에서 ExampleAttribute의 어트리뷰트를 취득
        var attribute = method.GetCustomAttribute(typeof(ExampleAttribute)) as ExampleAttribute;
        // 어트리뷰트가 있으면 - 즉, Print4는 제외
        if (attribute != null)
        {
          // 어트리뷰트의 Name 프로퍼티의 값이 Test의 경우 Print1과 Print2 함수가 대상
          if (string.Equals(attribute.Name, "Test"))
          {
            // 함수 실행
            method.Invoke(p, null);
          }
        }
      }
      // 아무 키나 누르시면 종료합니다.
      Console.WriteLine("Press any key...");
      Console.ReadLine();
    }
  }
}

위 예제에서는 ExampleAttribute의 어트리뷰트를 생성했습니다.

ExampleAttribute의 어트리뷰트에 AttributeUsage의 어트리뷰트가 설정이 되어있는데, 이 설정을 안하면 함수나 클래스, 변수에 관계없이 모든 곳에서 설정이 가능합니다.

즉, AttributeUsage를 설정하게 되면 특정 클래스나 함수에서만 설정이 가능합니다. 위 예제는 제가 함수에서만 사용 가능하게 설정하였습니다.


그리고 어트리뷰트는 Attribute의 클래스를 상속을 받아야 합니다. 이는 예전 예외처리 Exception구조와 비슷합니다.

생성자의 작성하는 것은 필수는 아닙니다만, 생성자를 작성하면 해당 파라미터가 필수가 됩니다.


Program 클래스에서 네개의 함수를 작성했습니다.

그중 세개는 ExampleAttribute의 어트리뷰트를 설정하고 Print4 함수는 설정하지 않았습니다.


Main 함수에서 리플렉션으로 Program의 대한 함수를 추출하고 어트리뷰트의 Name이 Test로 설정된 함수를 추출했습니다.

결과적으로 Print1 함수와 Print2의 함수가 실행되었습니다.


사실 이 Attribute는 리플렉션(Reflection) 기능과 아주 밀접한 관계가 있습니다.

자세한 부분은 리플렉션(Reflection)에서 설명하겠습니다.


여기까지 C#에서 어트리뷰트(Attribute)를 사용하는 방법에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.