특성은 메타데이터에 원하는 기록을 남기거나, 타입의 기능을 확장할 때 사용된다.
일반적으로 개발자는 주석을 사용해 기록을 남기지만 주석은 컴파일 과정에서 제외되기 때문에 메타데이터에 포함되지 않는다.
하지만 의도적으로 메타데이터에 기록을 포함하고 싶은 경우(ex. 저작권) 특성을 활용할 수 있다.
특성은 클래스와 같다.
클래스로 정의하며 'System.Attribute'를 상속받는다.
class TestAttribute : System.Attribute // ※관례적으로 특성을 정의할 때 식별자를 "~Attribute"로 정한다
{
}
[TestAttribute]
class TestClass1
{
}
[Test] // 사용할 때 Attribute 접미사는 생략해도 된다.
class TestClass2
{
}
[Test()] // 클래스이기 때문에 생성자로 표현할 수 있다.
class TestClass3
{
}
class TestClass4
{
TestAttribute test = new TestAttribute(); // 클래스이기 때문에 new 키워드를 사용하여 인스턴스를 생성하는 것 또한 가능하다.
}
특성의 생성자에 매개변수를 추가하여 확장할 수 있다.
class TestAttribute : System.Attribute
{
string name;
public TestAttribute(string name)
{
this.name = name;
}
}
[Test("Jerod")]
class TestClass
{
}
생성자의 매개변수가 아니라 프로퍼티를 사용하여 확장할 수도 있다.
class TestAttribute : System.Attribute
{
string name;
public int Age { get; set; }
public TestAttribute(string name)
{
this.name = name;
}
}
[Test("Jerod", Age = 27)]
class TestClass
{
}
생성자의 뒤에 나열하며 반드시 필드명을 명시해야 된다.
특성을 정의할 때 특성이 적용될 대상을 지정할 수 있다.
이 용도로 닷넷은 'System.AttributeUsageAttribute'를 제공한다.
namespace System
{
/* By default, attributes are inherited and multiple attributes are not allowed */
[AttributeUsage(AttributeTargets.Class, Inherited = true)]
public sealed class AttributeUsageAttribute : Attribute
{
private readonly AttributeTargets _attributeTarget;
private bool _allowMultiple;
private bool _inherited;
internal static readonly AttributeUsageAttribute Default = new AttributeUsageAttribute(AttributeTargets.All);
public AttributeUsageAttribute(AttributeTargets validOn)
{
_attributeTarget = validOn;
_inherited = true;
}
internal AttributeUsageAttribute(AttributeTargets validOn, bool allowMultiple, bool inherited)
{
_attributeTarget = validOn;
_allowMultiple = allowMultiple;
_inherited = inherited;
}
public AttributeTargets ValidOn => _attributeTarget;
public bool AllowMultiple
{
get => _allowMultiple;
set => _allowMultiple = value;
}
public bool Inherited
{
get => _inherited;
set => _inherited = value;
}
}
}
AttributeTargets: enum 타입이며, 특성이 적용되는 대상을 지정한다. '|' 비트 연산으로 복수대상을 지정할 수 있다.
AllowMultiple: 동일한 특성을 중복으로 부여할 수 있다. 디폴트값은 false이다.
Inherited: 특성을 지정한 대상을 상속받는 타입도 자동으로 특성을 물려받는다. 디폴트값은 true이다.
예제
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field)]
class TestAttribute : System.Attribute
{
}
[Test] // OK
class TestClass1
{
[Test]
[Test] // 동일한 특성이 중복으로 사용되어 컴파일 에러 발생
int testField;
[Test] // 지정되지 않는 대상에 사용되어 컴파일 에러 발생
public void TestMethod()
{
}
}
[type : Test] // AttributeTarget이 여러개일 경우 구분을 위해 이러한 구문도 지원한다.
class TestClass2
{
}
비주얼 스튜디오에서 프로젝트를 빌드하면 AssemblyInfo.cs 파일이 생성된다.
해당 파일은 어셈블리 수준에서 적용되는 특성만을 모아두는 용도이다.
// 생략
[assembly: System.Reflection.AssemblyCompanyAttribute("TestProject")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
[assembly: System.Reflection.AssemblyProductAttribute("TestProject")]
[assembly: System.Reflection.AssemblyTitleAttribute("TestProject")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// 생략
Attribute를 상속받은 클래스나 메서드에 사용할 수 있으며 해당 클래스나 메서드에 전처리를 해준다.
인자로 넘긴 string과 동일한 전처리 상수가 정의되지 않은 경우, 컴파일러는 해당 클래스나 메서드를 무시한다.
public class Program
{
[Conditional("DEBUG")] // DEBUG 전처리 상수는 디버그 빌드에서 자동으로 관리된다.
class TestAttribute : Attribute
{
}
public static void Main()
{
TestMethod();
}
[Conditional("DEBUG"), Conditional("TEST")] // DEBUG or TEST
[Test]
static void TestMethod()
{
Console.WriteLine("Test");
}
}
// 출력:
// Test
위 예시를 통해 알 수 있는 Conditional의 특징은
1. 중복 부여가 가능하다. (중복 부여시 하나의 전처리 상수만 만족해도 된다.)
2. Conditional을 부여한 메서드는 반드시 void 반환형이어야 한다. (컴파일 에러 발생. 그 이유는 조금만 생각해도 알 수 있다.)
참고 자료
시작하세요! C# 10 프로그래밍 - 정성태
Microsoft 공식 문서 - AttributeTargets 열거형