Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Archives
Today
Total
관리 메뉴

nomad-programmer

[Programming/C#] 어트리뷰트 (Attribute) 본문

Programming/C#

[Programming/C#] 어트리뷰트 (Attribute)

scii 2020. 9. 26. 00:24

어트리뷰트(Attribute)는 코드에 대한 부가 정보를 기록하고 읽을 수 있는 기능이다.

어트리뷰트가 주석과 다른 점은 주석이 사람이 읽고 쓰는 정보라면, 어트리뷰트는 사람이 작성하고 컴퓨터가 읽는다는 것이다.
어트리뷰트를 이용해서 클래스나 구조체, 메소드, 프로퍼티 등에 데이터를 기록해두면 이 정보를 C# 컴파일러나 C#으로 작성된 프로그램이 이 정보를 읽어 사용할 수 있다. (예를 들어 컴파일러가 경고를 내도록 만들 수도 있음)

메타데이터 (Metadata)

메타데이터란 데이터의 데이터를 말한다. 가령 C# 코드도 데이터지만, 이 코드에 대한 정보, 데이터가 있을 수 있다. 이를 메타데이터라고 하는 것이다.
어트리뷰트나 리플렉션을 통해 얻는 정보들도 C# 코드의 메타데이터라고 할 수 있다.

어트리뷰트 사용하는 방법

어트리뷰트를 사용할 때는 설명을 하고자 하는 코드 요소 앞에 [ ] 괄호 쌍을 붙이고 그 안에 어트리뷰트의 이름을 넣으면 된다.

[ 어트리뷰트_이름(어트리뷰트 매개 변수) ]
public void MyMethod()
{
    // ...
}

아래는 컴파일러가 경고 메시지를 내도록 하는 예제 코드이다. .NET 프레임워크에서 기본적으로 제공하는 Obsolete 어트리뷰트를 이용

class MyClass
{
    [Obsolete("OldMethod는 오래되었습니다. NewMethod()를 이용하십시오.")]
    public void OldMethod()
    {
        Console.WriteLine("old version...");
    }
    
    public void NewMethod()
    {
        Console.WriteLine("new version...");
    }
}

OldMethod()를 사용하는 코드를 그대로 둔 채 컴파일을 하게 된다면 "OldMethod는 오래되었습니다. NewMethod()를 이용하십시오."라는 경고 메시지를 보게 될 것이다.

using System;

namespace test
{
    class MyClass
    {
        [Obsolete("OldMethod는 오래되었습니다. NewMethod()를 이용하십시오.")]
        public void OldMethod()
        {
            Console.WriteLine("old version...");
        }

        public void NewMethod()
        {
            Console.WriteLine("new version...");
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            obj.OldMethod();
            obj.NewMethod();
        }
    }
}


/* 결과

old version...
new version...

*/

/* 경고

Program.cs(24, 13): [CS0618] 'MyClass.OldMethod()' is obsolete: 'OldMethod는 오래되었습니다. NewMethod()를 이용하십시오.'

*/

정상적으로 실행하지만, 컴파일을 할 때 오류 목록창을 확인하면 경고 메시지가 나타나는 것을 볼 수 있다.


호출자 정보 어트리뷰트

C/C++ 언어의 __FILENAME__, __LINE__, __FUNCTION__ 이다.

C# 5.0 버전부터 호출자 정보(Caller Information)가 도입됐다. 호출자 정보는 메소드의 매개 변수에 사용되며 메소드의 호출자 이름, 호출자 메소드가 정의되어 있는 소스 파일 경로, 소스 파일 내의 행(Line) 번호를 알 수 있다.
이것을 이용해서 응용 프로그램의 이벤트를 로그 파일이나 화면에 출력하면 그 이벤트가 어떤 코드에서 일어났는지를 알 수 있다.

어트리뷰트 설명
CallerMemberNameAttribute 현재 메소드를 호출한 메소드 또는 프로퍼티의 이름을 나타낸다.
CallerFilePathAttribute 현재 메소드가 호출된 소스 파일 경로를 나타낸다. 이 때 경로는 소스 코드를 컴파일할 때의 전체 경로를 나타낸다.
CallerLineNumberAttribute 현재 메소드가 호출된 소스 파일 내의 행(Line) 번호를 나타낸다.

호출자 정보 어트리뷰트 사용 예제

using System;
using System.Runtime.CompilerServices;

namespace test
{
    public static class Trace
    {
        public static void WriteLine(string message,
            [CallerFilePath] string file = "",
            [CallerLineNumber] int line = 0,
            [CallerMemberName] string member = "")
        {
            Console.WriteLine($"{file}(Line: {line}) {member}: {message}");
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            Trace.WriteLine("C#은 정말 재밌다!");
        }
    }
}


/* 결과

/home/test/workspace/c#/test/Program.cs(Line: 21) Main: C#은 정말 재밌다!

*/

Trace.WriteLine()의 선언부를 보면 [CallerFilePath], [CallerLineNumer], [CallerMemberName] 이 선택적 매개 변수로 사용되고 있다. 이렇게 하면 Trace.WriteLine() 메소드를 호출할 때 호출자 정보 어트리뷰트로 수식한 매개 변수는 프로그래머가 별도로 입력하지 않아도 된다.


사용자 정의 어트리뷰트

.NET 프레임워크가 제공하는 어트리뷰트는 Obsolete 말고도 그 종류가 상당히 많다.

  • [DLLImport] : C/C++ 언어로 작성된 네이티브 DLL(Dynamic Link Library) 에 존재하는 함수를 호출할 때 사용한다.
  • [Conditional] : 조건부 메소드 실행을 지정할 때 사용한다.

어트리뷰트도 하나의 클래스일 뿐이다. 모든 어트리뷰트는 예를 들어 다음과 같이 System.Attribute 클래스로부터 상속을 받아 만들어진다.

Class History : System.Attribute
{
    // ...
}

System.Attribute를 상속하는 것만으로도 어트리뷰트 하나를 만든 셈이 된다. 이렇게 선언된 어트리뷰트는 Obsolete 어트리뷰트처럼 [ ] 괄호 안에 어트리뷰트 이름을 넣어 사용하면 된다.

[HIstory]
class MyClass
{
    // ...
}

위의 History는 어트리뷰트이긴 하지만 아무것도 설명하는 것이 없다. History 어트리뷰트가 구체적으로 설명할 내용과 이것을 "어떻게" 설명하게 할 것인가를 나타내도록 코드를 추가해보자.

History가 클래스의 변경 이력을 나타내도록 할 것이다. History 클래스에 설명할 클래스의 작성자(즉, 프로그래머), 버전, 그리고 변경 내용 등을 나태낼 수 있도록 필드 및 프로퍼티를 추가할 것이다.

class History : System.Attribute
{
    private string programmer;
    
    public double Version{ get; set; }
    public string Changes{ get; set; }
    
    // 생성자
    public History(string programmer)
    {
        this.programmer = programmer;
        Version = 1.0;
        Changes = "First release";
    }
    
    public string Programmer => programmer;
}

History 클래스는 System.Attribute로부터 상속받았을 뿐이다. 여느 클래스와 다를 바가 없다. History 클래스를 사용해보자.

[History("Test1", Version = 0.1 Changes = "2020-05-05 Created class stub")]
class MyClass
{
    public void Func()
    {
        Console.WriteLine("Func()");
    }
}

이와 같이 MyClass를 History 어트리뷰트로 설명해 놓으면 리플렉션을 이용해서 손쉽게 Release 노트를 만들 수 있다.

하지만 한 가지 문제가 존재한다. 훗날 다른 프로그래머가 MyClass를 수정한 뒤 History 어트리뷰트를 추가하고 싶어도 더 추가할 수 없다.
지금의 History 어트리뷰트는 단 한번밖에 사용할 수 없기 때문이다. 가령 다음과 같이 어트리뷰트를 사용하려 들면 컴파일러가 에러 메시지를 보여줄 것이다.

[History("Test1", Version = 0.1, Changes = "2020-05-05 Created class stub")]
// 현재로서는 어트리뷰트를 이렇게 겹쳐 사용할 수 없다.
[History("Test2", Version = 0.2, changes = "2020-07-05 Added Func() Method")]
class MyClass
{
    public void Func() { }
}   

이 문제를 해결하려면 System.AttributeUsage 라는 어트리뷰트의 도움을 받아야만 한다.

System.AttributeUsage 는 어트리뷰트의 어트리뷰트다. 어트리뷰트가 어떤 대상을 설명할지, 이 어트리뷰트를 중복해서 사용할 수 있는지의 여부 등을 설명한다. System.AttributeUsage는 다음과 같이 어트리뷰트 정의 부분에 사용하면 된다.

[System.AttributeUsage(System.AttributeTargets.Class, AllowMultiple=true)]
class History : System.Attribute
{
    // ...
}

System.AttributeUsage의 첫 번째 매개 변수는 지금 선언하고 있는 어트리뷰트의 설명 대상이 무엇인지를 나타낸다. 이것을 Attribute Target이라고 하는데, 어트리뷰트의 설명 대상이 될 수 있는 코드 요소는 다음과 같다.

Attribute Target 설명
All 이 표의 나머지 모든 요소
Assembly 어셈블리
Module 모듈
Interface 인터페이스
Class 클래스
Struct 구조체
ClassMembers 클래스 안에 선언된 클래스나 구조체를 포함한 클래스의 모든 멤버
Constructor 생성자
Delegate 대리자
Enum 열거형
Event 이벤트
Field 필드
Property 프로퍼티
Method 메소드
Parameter 메소드의 매개 변수
ReturnValue 메소드의 반환 값

위의 Attribute Target은 논리합 연산자를 이용해서 결합할 수도 있다. 가령 클래스와 메소드를 대상으로 하고 싶다면 AttributeTargets.Class | AttributeTargets.Method를 System.AttributeUsage의 어트리뷰트 매개 변수로 넘기면 된다.

[System.AttributeUsage(
    System.AttributeTargets.Class | System.AttributeTargets.Method,
    AllowMultiple=true)]
class History : System.Attribute
{
    // ...
}

AllowMultiple 이 매개변수는 History 어트리뷰트를 여러 번 사용할 수 있도록 하기 위한 매개변수이다. true를 대입하면 중복해서 사용할 수 있다.

사용자 정의 Attribute 사용 예제

using System;

namespace test
{
    [System.AttributeUsage(
        System.AttributeTargets.Class, AllowMultiple = true)]
    class History : System.Attribute
    {
        private string programmer;

        public double Version { get; set; }
        public string Changes { get; set; }

        // 생성자
        public History(string programmer)
        {
            this.programmer = programmer;
            Version = 1.0;
            Changes = "First release";
        }

        public string Programmer => programmer;
    }

    [History("Jeon", Version = 0.1, Changes = "2020-05-05 Created class stub")]
    [History("Ethan", Version = 0.2, Changes = "2020-07-05 Added Func() Method")]
    class MyClass
    {
        public void Func()
        {
            Console.WriteLine("Func()");
        }
    }

    internal class Program
    {
        public static void Main(string[] args)
        {
            Type type = typeof(MyClass);
            Attribute[] attributes = Attribute.GetCustomAttributes(type);

            Console.WriteLine("MyClass change history...");

            foreach (Attribute attr in attributes)
            {
                History hist = attr as History;
                if (hist != null)
                {
                    Console.WriteLine("Ver: {0}, Programmer: {1}, Changes: {2}",
                        hist.Version, hist.Programmer, hist.Changes);
                }
            }
        }
    }
}


/* 결과

MyClass change history...
Ver: 0.1, Programmer: Jeon, Changes: 2020-05-05 Created class stub
Ver: 0.2, Programmer: Ethan, Changes: 2020-07-05 Added Func() Method

*/
Comments