본문 바로가기

C#

C# 애트리뷰트 (Attribute) 정의와 활용

애트리뷰트는 타입, 멤버, 매개변수 등에 메타데이터를 부착해 컴파일러, 런타임, 프레임워크가 특별한 처리를 하도록 돕는 수단입니다. 코드 자체의 동작을 바꾸기보다 “의미”를 선언적으로 전달하는 용도에 적합합니다.

1. 애트리뷰트란?

예를 들어 [Obsolete]는 사용 중단 API 경고를 내고, [Serializable]은 직렬화 대상으로 표시합니다. 애트리뷰트는 실행되지 않으며, 이를 읽는 쪽(컴파일러/리플렉션/도구)이 의미를 해석해 동작합니다.

2. 기본 사용 예

using System;
using System.Diagnostics;

public class Basics
{
    [Obsolete("Use NewApi instead", error: false)]
    public void OldApi() { }

    [Conditional("DEBUG")] // DEBUG 심볼이 없으면 호출이 컴파일에서 제거됩니다.
    public static void Log(string message) => Console.WriteLine(message);

    public void Run()
    {
        OldApi(); // 경고 발생
        Log("디버그 전용 로그");
    }
}

[Conditional]은 void 메서드에만 적용하며, 지정한 심볼이 정의되지 않으면 호출이 빌드 산출물에서 제거됩니다.

3. 사용자 정의 애트리뷰트 만들기

using System;

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)]
public sealed class RangeAttribute : Attribute
{
    public int Min { get; }
    public int Max { get; }
    public string? Message { get; set; } // 이름 있는 인자(Named Argument)로 설정 가능

    public RangeAttribute(int min, int max) // 위치 인자(Positional Argument)
    {
        Min = min;
        Max = max;
    }
}

public class Product
{
    [Range(1, 9999, Message = "가격은 1~9999입니다.")]
    public int Price { get; set; }

    public int Quantity { get; private set; }

    public void SetQuantity([Range(1, 100)] int qty)
    {
        Quantity = qty;
    }
}

AttributeUsage로 대상(Targets), AllowMultiple(중복 허용), Inherited(상속 전파) 등을 제한해 오용을 방지합니다.

4. 리플렉션으로 읽어 활용하기

using System;
using System.Collections.Generic;
using System.Reflection;

public static class Validator
{
    public static IEnumerable<string> Validate(object obj)
    {
        var type = obj.GetType();
        foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
        {
            var range = prop.GetCustomAttribute<RangeAttribute>();
            if (range is null) continue;

            var value = prop.GetValue(obj);
            if (value is int i)
            {
                if (i < range.Min || i > range.Max)
                {
                    yield return range.Message ?? $"{prop.Name}는 {range.Min}~{range.Max} 범위여야 합니다.";
                }
            }
            else
            {
                yield return $"{prop.Name}는 int 형식이어야 합니다.";
            }
        }
    }
}

// 사용 예
var p = new Product { Price = 0 };
foreach (var err in Validator.Validate(p))
{
    Console.WriteLine(err);
}

속도를 위해 반복 호출 시 애트리뷰트 조회 결과를 캐시하는 것이 좋습니다. 단순 존재 여부만 필요하면 IsDefined를 고려합니다.

5. 열거형 값에 설명 붙이기

using System;
using System.ComponentModel;
using System.Linq;

public enum Status
{
    [Description("대기")] Pending,
    [Description("진행")] InProgress,
    [Description("완료")] Done
}

public static class EnumExtensions
{
    public static string GetDescription(this Enum value)
    {
        var mem = value.GetType().GetMember(value.ToString()).FirstOrDefault();
        var desc = mem?.GetCustomAttributes(typeof(DescriptionAttribute), false)
                      .Cast<DescriptionAttribute>()
                      .FirstOrDefault();
        return desc?.Description ?? value.ToString();
    }
}

UI 표시나 로깅에서 의미 있는 문자열로 바꿔 출력할 수 있습니다.

6. 컴파일러/런타임 제공 애트리뷰트 활용

[Obsolete]로 단계적 폐기를 안내하고, Caller Info 애트리뷰트로 호출 위치 정보를 자동 수집할 수 있습니다.

using System;
using System.IO;
using System.Runtime.CompilerServices;

public static class LogEx
{
    public static void Info(string message,
        [CallerMemberName] string member = "",
        [CallerFilePath] string file = "",
        [CallerLineNumber] int line = 0)
    {
        Console.WriteLine($"{Path.GetFileName(file)}:{line} {member} - {message}");
    }
}

// 사용 예
LogEx.Info("시작합니다");

이 외에도 Null 상태를 표현하는 [NotNull], 예외 이후 반환하지 않음을 나타내는 [DoesNotReturn] 등 코드 분석을 돕는 애트리뷰트가 있습니다.

7. 팁과 모범 사례

의미를 명확히: AttributeUsage로 적용 대상을 정확히 제한하고, AllowMultiple/Inherited를 의도에 맞게 설정합니다.

인자 사용 규칙: 생성자 인자는 위치 인자, set 가능한 공개 속성은 이름 있는 인자로 전달합니다. 예) [Range(1, 10, Message = "..." )]

어셈블리 수준: 파일 상단에 어셈블리 애트리뷰트를 선언할 수 있습니다.

using System;

[assembly: CLSCompliant(true)]

성능: 반복적인 리플렉션 조회는 캐싱하고, 꼭 필요할 때만 애트리뷰트를 읽습니다. 많은 런타임 비용 없이 컴파일러가 해석하는 애트리뷰트([Conditional], [Obsolete], Caller Info 등)는 적극 활용합니다.

확장성: 애트리뷰트는 프레임워크 훅(예: ASP.NET Core 필터, DataAnnotations)과 함께 쓸 때 힘을 발휘합니다. 팀의 규약을 선언적으로 녹여 재사용 가능한 도구를 만드십시오.

'C#' 카테고리의 다른 글

C# static 키워드 완벽 이해  (1) 2026.04.13
C# 컬렉션 초기화와 Index/Range  (1) 2026.04.13
C# StringBuilder로 문자열 성능 최적화  (0) 2026.04.10
C# Enum 활용법  (0) 2026.04.10
C# var와 타입 추론  (0) 2026.04.09