[로봇활용_12주차] C# 애트리뷰트(Attribute)

최윤호·2025년 11월 1일
post-thumbnail

코드에 꼬리표(🏷️)를 달아보자!

혹시 코드 위에 [Serializable]이나 [HttpGet]처럼 대괄호([])로 감싸인
낯선 코드를 보신 적 있나요? 이게 바로 애트리뷰트(Attribute)인데요,
마치 문서에 '긴급'을 붙이는 것처럼 우리 코드에 특별한 꼬리표를 달아주는 역할을 합니다.
이번 포스팅에서는 코드에 '의미''정보'를 부여하는 애트리뷰트에 대해 알아보려 합니다.

1)애트리뷰트(Attribute)란?

애트리뷰트(Attribute)는 클래스, 메소드, 속성 등 코드의 특정 요소에 대한
추가 정보(메타데이터)를 제공하는 선언적인 태그입니다.
여기서 가장 중요한 포인트는 애트리뷰트(Attribute) 그 자체는
코드의 실행 흐름에 아무런 직접적인 영향을 주지 않는다는 점입니다.
마치 '긴급' 스티커를 붙인다고 해서 서류가 저절로 처리되지는 않는 것과 같죠.
이 스티커(애트리뷰트)를 누군가가 읽고 그에 맞는 행동을 할 때 비로소 처리됩니다.

2)애트리뷰트는 누가 사용할까?

이 꼬리표를 읽고 행동하는 주체는 크게 세 가지로 나눌 수 있습니다.

1. C# 컴파일러: 컴파일러가 특정 애트리뷰트를 보고 특별한 동작을 합니다.
가장 대표적인 예가 바로 [Obsolete]애트리뷰트입니다.

[Obsolete("이 메소드는 곧 사라질 예정이니 NewMethod()를 사용하세요.")]
public void OldMethod()
{
    // ...
}

이 코드를 사용하면 컴파일러가 "곧 없어지는 메소드야!"라며 경고 메시지를 띄워줍니다.
애트리뷰트 하나로 개발자에게 중요한 정보를 전달하는 것이죠.

2. .NET 프레임워크 및 라이브러리: 우리가 가장 흔하게 애트리뷰트를 만나는 곳입니다.

  • ASP.NET Core: [ApiController], [HttpGet]등 라우팅 규칙을 정의합니다.
  • Entity Framework Core: 데이터베이스 스키마 정보를 클래스에 매핑합니다.
  • 유닛 테스트 프레임워크 (xUnit, NUnit): 테스트할 메소드를 지정합니다.
    이 프레임워크들은 내부적으로 리플렉션을 사용해 코드에 붙어있는
    애트리뷰트를 스캔하고, "아, [HttpGet]이 붙어있으니
    이 메소드는 GET 요청에 연결해야겠군!"과 같이 약속된 동작을 수행합니다.

3. 우리가 직접 만든 코드: 우리도 직접 애트리뷰트를 만들고, 리플렉션을 이용해
특정 애트리뷰트가 붙은 코드를 찾아내어 원하는 작업을 수행할 수 있습니다.

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, 작성자: 김철수, 설명: 메소드 생성

4)정리하며

코드에 의미를 더하는 꼬리표, 애트리뷰트(Attribute)에 대해 알아보았습니다.
애트리뷰트는 그 자체로는 아무것도 하지 않지만,
컴파일러나 프레임워크, 혹은 우리의 리플렉션 코드가 그 의미를 해석하고
특별한 작업을 수행하게 만드는 강력한 메타 프로그래밍 도구입니다.
이를 통해 우리는 코드를 더 선언적이고 유연하게 만들 수 있으며,
반복적인 작업을 자동화하는 멋진 프레임워크를 설계할 수도 있습니다.

profile
🚀 미래의 엔지니어를 꿈꾸는 훈련생의 기록 📝

0개의 댓글