Home [C#] Attribute
Post
Cancel

[C#] Attribute

1. Attribute란?

Attribute는 코드에 추가적인 정보를 연결하는 기능이다.

코드에 특정 정보를 연결할 수 있기 때문에 Attribute로 재사용이 가능한 기능을 구현할 수 있다.

C#에서 기본적으로 제공해 주는 몇 가지 Attribute를 보면서 Attribute에 대해 이해해 보자!

1-1. [ObsoleteAttribute]

Obsolete의 뜻은 “구식”이다.

뜻 그대로 더 이상 사용하지 않는 코드에 ObsoleteAttribute를 추가하여 사용하지 않음을 알려줄 수 있다.

다시 말해,

ObsoleteAttribute를 통해 “더 이상 사용하지 않는 기능”이라는 정보를 코드에 연결한 것이다.

아래 예시 코드를 확인해 보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static void Main(string[] args)
{
    OldFunc();
    NewFunc();
}

[Obsolete("현재 사용하지 않는 메소드입니다. NewFunc() 을 사용해주세요")]
static void OldFunc()
{

}

static void NewFunc()
{

}

[Obsolete] 코드를 보면 class ObsoleteAttribute로 되어 있다.

C#에서는 Attribute를 구현할 때 “이름 Attribute”라고 작성하면 코드에서는 Attribute를 뺀 [이름]으로 사용할 수 있게 하고 있다.
물론 [이름Attribute]으로도 사용 가능하다.

OldFunc()에 ObsoleteAttribute를 사용하고 생성자를 통해 message를 전달했다.

1
2
3
4
5
6
public sealed class ObsoleteAttribute : Attribute
{
    public ObsoleteAttribute();
    public ObsoleteAttribute(string? message);
    public ObsoleteAttribute(string? message, bool error);
}

그리고 OldFunc()를 호출하면 message가 경고로 노출되는 것을 볼 수 있다.

cs_06
cs_07

2. Custom Attribute 구현

ObsoleteAttribute 정의를 보면 아래와 같이 정의되어 있다.

1
2
3
4
public sealed class ObsoleteAttribute : Attribute
{
	...	// 필드 및 메소드 생략
}

여기서 주의 깊게 보아야 할 것은 ObsoleteAttribute는 System.Attribute를 상속받고 있는 것이다.

즉, Custom Attribute를 구현하려면 새롭게 만든 class가 System.Attribute를 상속받으면 된다는 것을 알 수 있다.

이제 만들어보자!

2-1. HistoryAttribute

Custom Attribute 구현 예시로 가장 많이 사용되는 HistoryAttribute를 구현해 보자

HistoryAttribute는 코드를 누가, 어떤 버전에서 작업했는지 기록하기 위한 Attribute이다.

전체 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
using System;

class HistoryAttribute : Attribute
{
    public string _author;
    public double _version;
    public HistoryAttribute(string author)
    {
        _author = author;
    }
}
1
2
3
4
5
6
// 생성자를 통해서 값을 전달할 수 있지만, public 변수에 직접적으로 값을 전달할 수 있다.
[History("Kim",_version = 1.0)]
class Test
{

}

이렇게 코드를 작성하면 [History(“Kim”,_version = 1.0)] 를 통해 “Kim” 이 1.0 version에서 작업을 했음을 HistoryAttribute로 전달할 수 있다.

여기서 아래와 같이 새로운 [History(“Park”,_version = 1.1)] 추가했다고 가정하자.

1
2
3
4
5
6
7
 // 생성자를 통해서 값을 전달할 수 있지만, public 변수에 직접적으로 값을 전달할 수 있다.
[History("Kim",_version = 1.0)]
[History("Park",_version = 1.1)]
class Test
{

}

추가하게 되면 아래와 같이 에러가 발생한 것을 볼 수 있다.

cs_08

그리고 History는 class 나 struct에서만 작성하게 하고 싶은데 변수에도 작성이 된다.

cs_09

어떻게 하면 해결할 수 있을까??

2-2. System.AttributeUsage

System.AttributeUsage 란 Attirbute의 사용 방법을 지정하는 Attribute이다.

필드로는 3가지 값이 존재하는데 각각의 아래와 같다.

  • ValidOn(AttributeTargets): Attirbute를 사용할 수 있는 코드 요소를 특정합니다.(Ex. Class, Struct)
  • AllowMultiple(boolean): Attribute를 복수로 사용할 수 있는지 여부를 설정합니다.
  • Inherited(boolean): Attribute가 파생 클래스 및 재정의 멤버에 의해 상속될 수 있는지 여부를 설정합니다.

그럼  System.AttributeUsage를 사용해서 위에 두 가지 문제점을 해결해 보자.

1
2
3
4
5
6
7
8
9
10
11
// Class 와 Struct에서만 사용 / 복수로 사용 가능
[System.AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct,AllowMultiple = true)]
class HistoryAttribute : Attribute
{
    public string _author;
    public double _version;
    public HistoryAttribute(string author)
    {
        _author = author;
    }
}

cs_10

HistoryAttribute에 System.AttributeUsage를 설정함으로써

복수 사용이 가능하게 되었으며, Class 나 Struct 외에 사용하지 못하게 하였다.

3. Custom Attribute 가져와서 사용하기

위에서 HistoryAttribute를 구현했다. 이제 Attribute로 전달된 내용을 출력하려고 한다.

어떻게 Attribute를 참조해서 사용할 수 있을까?

이것을 해결하기 위해 GetCustomAttributes 메서드를 사용할 것이다.

사용 방법은 간단해서 예제 코드를 통해 알아보자

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[System.AttributeUsage(AttributeTargets.Class,AllowMultiple = true)]
public class HistoryAttribute : Attribute
{
    public string _author;
    public double _version;
    public HistoryAttribute(string author)
    {
        _author = author;
    }

    public void Print()
    {
        Console.WriteLine($"저자: {_author} / 버전: {_version}");
    }
}

[History("Kim",_version = 1.0)]
[History("Park",_version = 1.1)]
class Test
{ }

[History("Lee", _version = 1.0)]
[History("Choi", _version = 1.1)]
class Test2
{ }

class Program
{
    static void Main(string[] args)
    {
        PrintHistory(new Test());
        PrintHistory(new Test2());
    }

    static void PrintHistory<T>(T t) where T: class
    {
        HistoryAttribute[] historyAttributes =
            (HistoryAttribute[])Attribute.GetCustomAttributes(t.GetType(), typeof(HistoryAttribute));

        foreach (var attr in historyAttributes)
        {
            attr.Print();
        }
    }
}

코드 실행 시 [History]로 전달된 내용들이 출력되는 것을 볼 수 있다.

cs_11

4. 추가 내용

Attribute.GetCustomAttributes(t.GetType(), typeof(HistoryAttribute)); 에서

첫번 째 파라미터는 System.Reflection.MemberInfo element 이다.

1
public static Attribute[] GetCustomAttributes(MemberInfo element, Type type);

하지만 위 코드에서는 자료형이 Type인 t.GetType()을 첫번 째 파라미터로 전달하였다.

이게 어떻게 가능한 일인가??

이건 class Type 정의부를 보면 바로 확인할 수 있는데

class Typeclass MemberInfo상속받고 있기 때문에 가능한 것임을 알 수 있다.

코딩을 하다 보면 “왜? 이게 되는 거지?” 하는 부분들이 있다.
이 때 MSDN을 살펴봐도 되지만 정의로 이동(VS 기준 F12)해서 메타 데이터 코드를 확인해보면 더욱 빨리 알 수 있다.

그러므로, 모르는 게 있을 때는 정의부를 꼭 한번 확인하는 습관을 들이자!!!

1
2
3
4
public abstract class Type : MemberInfo, IReflect
{
	// 이하 생략
}

참고 자료


자습서: 특성 사용 - C#

Retrieving Information Stored in Attributes

특성에 저장된 정보 검색

This post is licensed under CC BY 4.0 by the author.

[C++] explicit

[C++ 프로그래머스] 옹알이(1)