C#고급 주제

Attribute 기본

내장 어트리뷰트([Obsolete], [Flags], [CallerMemberName])의 동작 원리, 커스텀 어트리뷰트 선언과 리플렉션으로 읽는 패턴, AttributeTargets 제한 방법을 정리합니다.

마지막 수정 2026년 3월 26일

기본 패턴

csharp
// 내장 어트리뷰트
[Obsolete("V2 API를 사용하세요", error: false)]
public void OldMethod() { }

// 커스텀 어트리뷰트 선언
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AuditAttribute : Attribute
{
    public string Reason { get; }
    public AuditAttribute(string reason) => Reason = reason;
}

// 적용
[Audit("결제 처리 — 감사 로그 필수")]
public void ProcessPayment() { }

// 리플렉션으로 읽기
var attr = typeof(MyClass)
    .GetMethod("ProcessPayment")
    ?.GetCustomAttribute<AuditAttribute>();
Console.WriteLine(attr?.Reason);

설명

어트리뷰트란

어트리뷰트는 클래스, 메서드, 프로퍼티 등에 메타데이터를 부착하는 방법입니다. 컴파일러, 런타임, 외부 도구(직렬화, ORM, DI 등) 가 이 메타데이터를 읽어 동작을 결정합니다. 코드 자체는 바꾸지 않고 의미를 추가할 수 있습니다.

주요 내장 어트리뷰트

[Obsolete] 는 사용 중단을 알리는 가장 흔한 어트리뷰트입니다. error: true로 설정하면 컴파일 오류로 격상됩니다.

csharp
[Obsolete("GetUser() 대신 GetUserAsync()를 사용하세요")]
public User GetUser(int id) { ... }

[Obsolete("제거 예정 — 즉시 마이그레이션 필요", error: true)]
public void LegacyLogin() { }

[CallerMemberName] 은 호출자 이름을 컴파일 시점에 자동 주입합니다. 로깅과 INotifyPropertyChanged 구현에 유용합니다.

csharp
void Log(string message,
    [CallerMemberName] string caller = "",
    [CallerLineNumber] int line = 0)
{
    Console.WriteLine($"[{caller}:{line}] {message}");
}

// 호출
Log("시작");  // [ProcessPayment:42] 시작

커스텀 어트리뷰트 선언

Attribute를 상속하고 [AttributeUsage] 로 적용 가능한 대상과 규칙을 제한합니다.

csharp
[AttributeUsage(
    AttributeTargets.Method | AttributeTargets.Class,
    AllowMultiple = false,   // 같은 대상에 중복 적용 여부
    Inherited = true         // 파생 클래스에도 상속 여부
)]
public class RequiresRoleAttribute : Attribute
{
    public string Role { get; }
    public RequiresRoleAttribute(string role) => Role = role;
}

[RequiresRole("Admin")]
public void DeleteUser(int id) { }

리플렉션으로 읽기

런타임에 GetCustomAttribute<T>() 로 어트리뷰트를 읽을 수 있습니다.

csharp
var method = typeof(UserService).GetMethod("DeleteUser");
var attr   = method?.GetCustomAttribute<RequiresRoleAttribute>();

if (attr != null)
    Console.WriteLine($"필요 권한: {attr.Role}");  // "Admin"

// 여러 개 읽기
var attrs = method?.GetCustomAttributes<RequiresRoleAttribute>();

// 존재 여부만 확인
bool has = method?.IsDefined(typeof(RequiresRoleAttribute)) ?? false;

빠른 정리

어트리뷰트용도
[Obsolete]사용 중단 경고/오류
[Flags]enum 비트 플래그 조합 허용
[Serializable]직렬화 허용 표시
[CallerMemberName]호출자 이름 컴파일 시 주입
[CallerLineNumber]호출 줄 번호 주입
[JsonPropertyName]JSON 직렬화 키 이름 지정
[Required]모델 유효성 검사
[AttributeUsage]커스텀 어트리뷰트 적용 규칙 정의

주의할 점

어트리뷰트는 런타임에 리플렉션으로 읽으므로 반복 호출 시 성능 비용이 있습니다. 요청마다 GetCustomAttribute()를 호출하는 대신 결과를 ConcurrentDictionary 등에 캐싱해 재사용하세요.

어트리뷰트 생성자에는 컴파일 시점에 알 수 있는 상수값(string, int, Type, enum 등)만 사용할 수 있습니다. 런타임에 결정되는 값은 전달할 수 없습니다.

참고 링크

2 sources