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