
혹시 코드 위에 [Serializable]이나 [HttpGet]처럼 대괄호([])로 감싸인
낯선 코드를 보신 적 있나요? 이게 바로 애트리뷰트(Attribute)인데요,
마치 문서에 '긴급'을 붙이는 것처럼 우리 코드에 특별한 꼬리표를 달아주는 역할을 합니다.
이번 포스팅에서는 코드에 '의미'와 '정보'를 부여하는 애트리뷰트에 대해 알아보려 합니다.
애트리뷰트(Attribute)는 클래스, 메소드, 속성 등 코드의 특정 요소에 대한
추가 정보(메타데이터)를 제공하는 선언적인 태그입니다.
여기서 가장 중요한 포인트는 애트리뷰트(Attribute) 그 자체는
코드의 실행 흐름에 아무런 직접적인 영향을 주지 않는다는 점입니다.
마치 '긴급' 스티커를 붙인다고 해서 서류가 저절로 처리되지는 않는 것과 같죠.
이 스티커(애트리뷰트)를 누군가가 읽고 그에 맞는 행동을 할 때 비로소 처리됩니다.
이 꼬리표를 읽고 행동하는 주체는 크게 세 가지로 나눌 수 있습니다.
1. C# 컴파일러: 컴파일러가 특정 애트리뷰트를 보고 특별한 동작을 합니다.
가장 대표적인 예가 바로 [Obsolete]애트리뷰트입니다.
[Obsolete("이 메소드는 곧 사라질 예정이니 NewMethod()를 사용하세요.")]
public void OldMethod()
{
// ...
}
이 코드를 사용하면 컴파일러가 "곧 없어지는 메소드야!"라며 경고 메시지를 띄워줍니다.
애트리뷰트 하나로 개발자에게 중요한 정보를 전달하는 것이죠.
2. .NET 프레임워크 및 라이브러리: 우리가 가장 흔하게 애트리뷰트를 만나는 곳입니다.
[ApiController], [HttpGet]등 라우팅 규칙을 정의합니다.[HttpGet]이 붙어있으니3. 우리가 직접 만든 코드: 우리도 직접 애트리뷰트를 만들고, 리플렉션을 이용해
특정 애트리뷰트가 붙은 코드를 찾아내어 원하는 작업을 수행할 수 있습니다.
개발자의 작업 이력을 남기는 간단한 History애트리뷰트를 직접 만들어 보겠습니다.
1단계: 애트리뷰트 클래스 정의
애트리뷰트는 System.Attribute를 상속받는 클래스입니다.
클래스 이름 끝에 Attribute를 붙이는 것이 규칙입니다.
// AttributeUsage: 이 애트리뷰트를 어디에 붙일 수 있는지 정의
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class HistoryAttribute : Attribute
{
private readonly string _programmer;
public string Version { get; set; }
public string Description { get; set; }
public HistoryAttribute(string programmer)
{
_programmer = programmer;
Version = "1.0";
Description = "Initial version";
}
public string GetProgrammer() => _programmer;
}
[AttributeUsage(...)]: 이 애트리뷰트의 사용 규칙을 정의하는 또 다른 애트리뷰트입니다!
AttributeTargets: 애트리뷰트를 적용할 수 있는 대상을 지정합니다. (예: Class, Method)
AllowMultiple: 한 요소에 이 애트리뷰트를 여러 번 적용할 수 있는지 설정합니다.
2단계: 애트리뷰트 사용하기
이제 우리가 만든 History 애트리뷰트를 클래스와 메소드에 적용해 봅시다.
사용할 때는 이름에서 Attribute 접미사를 생략할 수 있습니다.
[History("Alice", Version = "1.0", Description = "클래스 생성")]
[History("Bob", Version = "1.1", Description = "기능 개선")]
public class MyImportantLogic
{
[History("Alice", Description = "메소드 생성")]
public void PerformTask()
{
Console.WriteLine("중요한 작업을 수행합니다.");
}
}
3단계: 리플렉션으로 애트리뷰트 정보 읽기
애트리뷰트는 붙이는 것만으로는 의미가 없죠. 리플렉션으로 정보를 읽어와야 합니다.
class Program
{
static void Main()
{
Type type = typeof(MyImportantLogic);
Console.WriteLine($"--- [{type.Name}] 클래스 히스토리 ---");
// 클래스에 적용된 모든 HistoryAttribute 가져오기
var classAttributes = type.GetCustomAttributes<HistoryAttribute>();
foreach (var attr in classAttributes)
{
Console.WriteLine(
$"버전: {attr.Version}, " +
$"작성자: {attr.GetProgrammer()}, " +
$"설명: {attr.Description}");
}
Console.WriteLine($"\n--- 메소드 히스토리 ---");
MethodInfo method = type.GetMethod("PerformTask");
// 메소드에 적용된 HistoryAttribute 가져오기
var methodAttributes = method.GetCustomAttributes<HistoryAttribute>();
foreach (var attr in methodAttributes)
{
Console.WriteLine(
$"[{method.Name}] 버전: {attr.Version}, " +
$"작성자: {attr.GetProgrammer()}, " +
$"설명: {attr.Description}");
}
}
}
[실행 결과]
--- [MyImportantLogic] 클래스 히스토리 ---
버전: 1.0, 작성자: 김철수, 설명: 클래스 생성
버전: 1.1, 작성자: 이영희, 설명: 기능 개선
--- 메소드 히스토리 ---
[PerformTask] 버전: 1.0, 작성자: 김철수, 설명: 메소드 생성
코드에 의미를 더하는 꼬리표, 애트리뷰트(Attribute)에 대해 알아보았습니다.
애트리뷰트는 그 자체로는 아무것도 하지 않지만,
컴파일러나 프레임워크, 혹은 우리의 리플렉션 코드가 그 의미를 해석하고
특별한 작업을 수행하게 만드는 강력한 메타 프로그래밍 도구입니다.
이를 통해 우리는 코드를 더 선언적이고 유연하게 만들 수 있으며,
반복적인 작업을 자동화하는 멋진 프레임워크를 설계할 수도 있습니다.