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가 경고로 노출되는 것을 볼 수 있다.
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
{
}
추가하게 되면 아래와 같이 에러가 발생한 것을 볼 수 있다.
그리고 History는 class 나 struct에서만 작성하게 하고 싶은데 변수에도 작성이 된다.
어떻게 하면 해결할 수 있을까??
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;
}
}
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]로 전달된 내용들이 출력되는 것을 볼 수 있다.
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 Type은 class MemberInfo를 상속받고 있기 때문에 가능한 것임을 알 수 있다.
코딩을 하다 보면 “왜? 이게 되는 거지?” 하는 부분들이 있다.
이 때 MSDN을 살펴봐도 되지만 정의로 이동(VS 기준 F12)해서 메타 데이터 코드를 확인해보면 더욱 빨리 알 수 있다.그러므로, 모르는 게 있을 때는 정의부를 꼭 한번 확인하는 습관을 들이자!!!
1
2
3
4
public abstract class Type : MemberInfo, IReflect
{
// 이하 생략
}