Quick Reference
Attribute는 코드에 붙이는 메타데이터이며, 그 자체로 validation·serialization·권한 검사를 실행하지 않습니다. 컴파일러, reflection 코드, serializer, source generator처럼 읽는 주체가 있을 때만 동작이 생깁니다. 커스텀 attribute는 AttributeUsage로 적용 대상·중복·상속을 먼저 정합니다.
using System.Reflection;
// 내장 어트리뷰트
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class AuditAttribute : Attribute
{
public string Reason { get; }
public AuditAttribute(string reason) => Reason = reason;
}
public sealed class PaymentService
{
[Obsolete("V2 API를 사용하세요", error: false)]
public void OldMethod() { }
[Audit("결제 처리 - 감사 로그 필수")]
public void ProcessPayment() { }
}
// 리플렉션으로 읽기
var attr = typeof(PaymentService)
.GetMethod("ProcessPayment")
?.GetCustomAttribute<AuditAttribute>();
Console.WriteLine(attr?.Reason);문법
어떤 어트리뷰트를 먼저 떠올리면 되나
| 상황 | 먼저 떠올릴 것 |
|---|---|
| API 사용 중단 알리기 | [Obsolete] |
| 호출자 이름/줄 번호 기록 | [CallerMemberName], [CallerLineNumber] |
| JSON 필드 이름 지정 | [JsonPropertyName] |
| 직접 메타데이터 정의 | 커스텀 attribute + [AttributeUsage] |
어트리뷰트란
어트리뷰트는 클래스, 메서드, 프로퍼티 등에 메타데이터를 부착하는 방법입니다. 소비 주체는 제각각입니다. [Obsolete]와 caller info attribute는 컴파일러가, GetCustomAttribute는 reflection 코드가, [JsonPropertyName]은 JSON serializer가 읽습니다. [Required], [Serializable], 권한 attribute도 해당 validation·serialization·authorization pipeline이 없으면 스스로 실행되지 않습니다.
주요 내장 어트리뷰트
[Obsolete] 는 사용 중단을 알리는 가장 흔한 어트리뷰트입니다. error: true로 설정하면 컴파일 오류로 격상됩니다.
[Obsolete("GetUser() 대신 GetUserAsync()를 사용하세요")]
public User GetUser(int id) { ... }
[Obsolete("제거 예정 — 즉시 마이그레이션 필요", error: true)]
public void LegacyLogin() { }[CallerMemberName] 은 호출자 이름을 컴파일 시점에 자동 주입합니다. 로깅과 INotifyPropertyChanged 구현에 유용합니다.
void Log(string message,
[CallerMemberName] string caller = "",
[CallerLineNumber] int line = 0)
{
Console.WriteLine($"[{caller}:{line}] {message}");
}
// 호출
Log("시작"); // [ProcessPayment:42] 시작커스텀 어트리뷰트 선언
Attribute를 상속하고 [AttributeUsage] 로 적용 가능한 대상과 규칙을 제한합니다. Inherited = true는 상속 가능한 대상에서 reflection이 base attribute를 포함해 조회할 수 있게 하는 설정이지, 모든 도구가 자동으로 정책을 상속한다는 뜻은 아닙니다.
[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>() 로 하나를 읽거나 GetCustomAttributes<T>()로 여러 개를 읽을 수 있습니다. 전자는 없으면 null이고 같은 타입이 여러 개면 AmbiguousMatchException이 될 수 있으므로 AllowMultiple 계약과 맞춰 고릅니다.
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;// ❌ 요청마다 리플렉션으로 반복 조회
bool requiresAudit = method
.GetCustomAttribute<AuditAttribute>() != null;
// ✅ 자주 쓰는 결과는 캐싱 고려
bool requiresAudit = _cache.GetOrAdd(
method,
m => m.GetCustomAttribute<AuditAttribute>() != null);주요 Attribute
| 어트리뷰트 | 용도 |
|---|---|
[Obsolete] | 사용 중단 경고/오류 |
[Flags] | enum 비트 플래그 조합 허용 |
[Serializable] | 직렬화 허용 표시 |
[CallerMemberName] | 호출자 이름 컴파일 시 주입 |
[CallerLineNumber] | 호출 줄 번호 주입 |
[JsonPropertyName] | JSON 직렬화 키 이름 지정 |
[Required] | 모델 유효성 검사 |
[AttributeUsage] | 커스텀 어트리뷰트 적용 규칙 정의 |
주의할 점
모든 attribute가 런타임 reflection으로 읽히는 것은 아닙니다. reflection으로 반복 조회하는 hot path에서만 프로파일링 후 metadata 결과나 CustomAttributeData를 캐싱합니다. compiler·serializer·generator가 소비하는 attribute에는 별도의 reflection cache가 동작을 추가하지 않습니다.
어트리뷰트 생성자와 named argument에는 허용된 attribute argument type의 컴파일 시간 상수, typeof(...), enum, 그 1차원 배열만 넣을 수 있습니다. 임의 객체·DateTime·런타임 계산 결과는 전달할 수 없습니다.
참고 링크
2 sources