C# 컴파일러에서 해석하는 기타 특성
Conditional
, Obsolete
, AttributeUsage
, AsyncMethodBuilder
, InterpolatedStringHandler
, ModuleInitializer
특성은 코드의 요소에 적용할 수 있습니다. 해당 요소에 의미 체계 의미를 추가합니다. 컴파일러는 해당 의미 체계 의미를 사용하여 출력을 변경하고 코드를 사용하여 개발자의 가능한 실수를 보고합니다.
Conditional
특성
Conditional
특성을 사용하면 메서드 실행이 전처리 식별자에 따라 달라집니다. Conditional
특성은 ConditionalAttribute의 별칭이고 메서드 또는 특성 클래스에 적용할 수 있습니다.
다음 예제에서 Conditional
은 프로그램 관련 진단 정보 표시를 사용하거나 사용하지 않도록 설정하는 메서드에 적용됩니다.
#define TRACE_ON
using System.Diagnostics;
namespace AttributeExamples;
public class Trace
{
[Conditional("TRACE_ON")]
public static void Msg(string msg)
{
Console.WriteLine(msg);
}
}
public class TraceExample
{
public static void Main()
{
Trace.Msg("Now in Main...");
Console.WriteLine("Done.");
}
}
TRACE_ON
식별자가 정의되지 않으면 추적 출력이 표시되지 않습니다. 대화형 창에서 직접 살펴보세요.
Conditional
특성은 보통 DEBUG
식별자와 함께 사용하여 다음 예제에 표시된 대로 릴리스 빌드가 아닌 디버그 빌드의 추적 및 로깅 기능을 사용하도록 설정합니다.
[Conditional("DEBUG")]
static void DebugMethod()
{
}
조건부로 표시된 메서드를 호출하면 지정된 전처리 기호가 있는지 여부에 따라 컴파일러가 메서드 호출을 포함할지 여부가 결정됩니다. 기호가 정의되면 호출이 포함되고, 정의되지 않으면 호출이 생략됩니다. 조건부 메서드는 클래스 또는 구조체 선언의 메서드여야 하며 void
반환 형식을 포함해야 합니다. 메서드를 #if…#endif
블록 내부에 포함하는 것보다 Conditional
을 사용하는 것이 더 분명하고 더 정교하며 오류 가능성이 더 작습니다.
메서드에 여러 Conditional
특성이 있는 경우 하나 이상의 조건부 기호가 정의되어 있으면 컴파일러가 메서드 호출을 포함합니다(기호는 OR 연산자를 사용하여 논리적으로 함께 연결됨). 다음 예제에서는 A
또는 B
가 있으면 메서드 호출이 발생합니다.
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}
특성 클래스와 함께 Conditional
사용
Conditional
특성을 특성 클래스 정의에 적용할 수도 있습니다. 다음 예제에서 사용자 지정 특성 Documentation
은 DEBUG
가 정의된 경우에만 메타데이터에 정보를 추가합니다.
[Conditional("DEBUG")]
public class DocumentationAttribute : System.Attribute
{
string text;
public DocumentationAttribute(string text)
{
this.text = text;
}
}
class SampleClass
{
// This attribute will only be included if DEBUG is defined.
[Documentation("This method displays an integer.")]
static void DoWork(int i)
{
System.Console.WriteLine(i.ToString());
}
}
Obsolete
특성
Obsolete
특성은 코드 요소를 더 이상 사용이 권장되지 않는 항목으로 표시합니다. 사용되지 않음으로 표시된 엔터티를 사용하면 경고나 오류가 생성됩니다. Obsolete
특성은 단일 사용 특성이고 특성을 허용하는 모든 엔터티에 적용할 수 있습니다. Obsolete
는 ObsoleteAttribute의 별칭입니다.
다음 예제에서는 Obsolete
특성이 A
클래스 및 B.OldMethod
메서드에 적용됩니다. B.OldMethod
에 적용된 특성 생성자의 두 번째 인수가 true
로 설정되므로 이 메서드는 컴파일러 오류를 일으키지만, A
클래스를 사용하면 경고가 생성됩니다. 그러나 B.NewMethod
를 호출하면 경고나 오류가 생성되지 않습니다. 예를 들어 이전 정의와 함께 사용할 경우 다음 코드에서는 두 개의 경고 및 하나의 오류가 생성됩니다.
namespace AttributeExamples
{
[Obsolete("use class B")]
public class A
{
public void Method() { }
}
public class B
{
[Obsolete("use NewMethod", true)]
public void OldMethod() { }
public void NewMethod() { }
}
public static class ObsoleteProgram
{
public static void Main()
{
// Generates 2 warnings:
A a = new A();
// Generate no errors or warnings:
B b = new B();
b.NewMethod();
// Generates an error, compilation fails.
// b.OldMethod();
}
}
}
특성 생성자에 첫 번째 인수로 제공된 문자열은 경고 또는 오류의 일부로 표시됩니다. A
클래스에 대한 두 개의 경고가 각각 클래스 참조 선언 및 클래스 생성자에 대해 생성됩니다. Obsolete
특성은 인수 없이 사용할 수 있지만 대신 사용이 권장되는 항목에 대한 설명을 포함합니다.
C# 10에서는 상수 문자열 보간 및 nameof
연산자를 사용하여 이름이 일치하는지 확인할 수 있습니다.
public class B
{
[Obsolete($"use {nameof(NewMethod)} instead", true)]
public void OldMethod() { }
public void NewMethod() { }
}
SetsRequiredMembers
특성
특성은 SetsRequiredMembers
생성자가 해당 클래스 또는 구조체의 모든 required
멤버를 설정한다는 것을 컴파일러에 알릴 수 있습니다. 컴파일러는 특성이 있는 모든 생성자가 모든 required
멤버를 System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute 초기화하는 것으로 가정합니다. 이러한 생성자를 호출하는 모든 코드는 필요한 멤버를 설정하는 데 개체 이니셜라이저가 필요하지 않습니다. 이는 위치 레코드 및 기본 생성자에 주로 유용합니다.
AttributeUsage
특성
AttributeUsage
특성은 사용자 지정 특성 클래스를 사용하는 방법을 결정합니다. AttributeUsageAttribute는 사용자 지정 특성 정의에 적용되는 특성입니다. AttributeUsage
특성을 사용하면 다음을 제어할 수 있습니다.
- 적용할 수 있는 프로그램 요소 특성 사용을 제한하지 않는 한, 다음과 같은 프로그램 요소 중 하나에 특성을 적용할 수 있습니다.
- 어셈블리
- 모듈
- 필드
- 이벤트
- 메서드
- 매개 변수
- 속성
- 반환 값
- 형식
- 특성을 단일 프로그램 요소에 여러 번 적용할 수 있는지 여부
- 특성이 파생 클래스에게 상속되는지 여부
기본 설정을 명시적으로 적용할 경우 다음 예제와 같이 작성합니다.
[AttributeUsage(AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : Attribute { }
이 예제에서 NewAttribute
클래스는 모든 지원되는 프로그램 요소에 적용할 수 있습니다. 하지만 각 엔터티에 한 번만 적용할 수 있습니다. 이 특성은 기본 클래스에 적용될 때 파생 클래스에게 상속됩니다.
AllowMultiple 및 Inherited 인수는 선택 사항이므로 다음 코드는 동일한 효과를 가집니다.
[AttributeUsage(AttributeTargets.All)]
class NewAttribute : Attribute { }
첫 번째 AttributeUsageAttribute 인수는 AttributeTargets 열거형의 요소가 하나 이상이어야 합니다. 다음 예제와 같이 OR 연산자를 사용하여 여러 대상 형식을 함께 연결할 수 있습니다.
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
특성은 자동 구현 속성의 속성 또는 지원 필드에 적용할 수 있습니다. 특성에 field
지정자를 지정하지 않는 한 특성이 속성에 적용됩니다. 두 경우 모두 다음 예제에서 표시됩니다.
class MyClass
{
// Attribute attached to property:
[NewPropertyOrField]
public string Name { get; set; } = string.Empty;
// Attribute attached to backing field:
[field: NewPropertyOrField]
public string Description { get; set; } = string.Empty;
}
AllowMultiple 인수가 true
인 경우 다음 예제와 같이 결과 특성을 단일 엔터티에 두 번 이상 적용할 수 있습니다.
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
class MultiUse : Attribute { }
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
이 경우 AllowMultiple
이 true
로 설정되므로 MultiUseAttribute
를 반복적으로 적용할 수 있습니다. 여러 특성을 적용하기 위해 표시된 두 형식이 모두 유효합니다.
Inherited이 false
인 경우 특성은 특성 클래스에서 파생된 클래스에서 상속하지 않습니다. 예를 들어:
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
class NonInheritedAttribute : Attribute { }
[NonInherited]
class BClass { }
class DClass : BClass { }
이 경우에 NonInheritedAttribute
은 상속을 통해 DClass
에 적용되지 않습니다.
이 키워드를 사용하여 특성을 적용할 위치를 지정할 수도 있습니다. 예를 들어 field:
지정자를 사용하여 자동 구현 속성의 지원 필드에 특성을 추가할 수 있습니다. 아니면 field:
, property:
또는 param:
지정자를 사용하여 위치 레코드에서 생성된 요소에 특성을 적용할 수 있습니다. 예제의 경우 속성 정의에 대한 위치 구문을 참조하세요.
AsyncMethodBuilder
특성
비동기 System.Runtime.CompilerServices.AsyncMethodBuilderAttribute 반환 형식이 될 수 있는 형식에 특성을 추가합니다. 이 특성은 지정된 형식이 비동기 메서드에서 반환될 때 비동기 메서드 구현을 빌드하는 형식을 지정합니다. AsyncMethodBuilder
특성은 다음과 같은 형식에 적용할 수 있습니다.
- 액세스 가능한
GetAwaiter
메서드가 있습니다. GetAwaiter
메서드에서 반환된 개체가 System.Runtime.CompilerServices.ICriticalNotifyCompletion 인터페이스를 구현합니다.
AsyncMethodBuilder
특성에 대한 생성자가 연결된 작성기의 형식을 지정합니다. 작성기는 다음과 같은 액세스 가능한 멤버를 구현해야 합니다.
작성기의 형식을 반환하는 정적
Create()
메서드비동기 반환 형식을 반환하는 읽기 가능한
Task
속성작업에서 오류가 발생할 경우 예외를 설정하는
void SetException(Exception)
메서드작업을 완료됨으로 표시하고 선택적으로 작업의 결과를 설정하는
void SetResult()
또는void SetResult(T result)
메서드다음 API 시그니처를 사용하는
Start
메서드:void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
다음 시그니처를 사용하는
AwaitOnCompleted
메서드:public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
다음 시그니처를 사용하는
AwaitUnsafeOnCompleted
메서드:public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
.NET에서 제공하는 다음 작성기를 검토하여 비동기 메서드 작성기에 대해 알아볼 수 있습니다.
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncTaskMethodBuilder<TResult>
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder
- System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder<TResult>
C# 10 이상에서는 AsyncMethodBuilder
특성을 비동기 메서드에 적용하여 해당 형식에 대한 작성기를 재정의할 수 있습니다.
InterpolatedStringHandler
및 InterpolatedStringHandlerArguments
특성
C# 10부터 이러한 특성을 사용하여 형식을 ‘보간된 문자열 처리기’임을 나타냅니다. .NET 6 라이브러리에는 보간된 문자열을 string
매개 변수의 인수로 사용하는 시나리오에 대한 System.Runtime.CompilerServices.DefaultInterpolatedStringHandler가 이미 포함되어 있습니다. 보간된 문자열 처리 방법을 제어하려는 다른 인스턴스가 있을 수 있습니다. 처리기를 구현하는 형식에 System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute를 적용합니다. 해당 형식 생성자의 매개 변수에 System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute를 적용합니다.
보간된 문자열 향상된 기능에 대한 C# 10 기능 사양에서 보간된 문자열 처리기를 빌드하는 방법에 관해 자세히 알아볼 수 있습니다.
ModuleInitializer
특성
C# 9부터 특성은 ModuleInitializer
어셈블리가 로드할 때 런타임이 호출하는 메서드를 표시합니다. ModuleInitializer
는 ModuleInitializerAttribute의 별칭입니다.
ModuleInitializer
특성은 다음과 같은 메서드에만 적용할 수 있습니다.
- 정적입니다.
- 매개 변수가 없습니다.
void
를 반환합니다.- 포함하는 모듈(
internal
또는public
)에서 액세스할 수 있습니다. - 제네릭 메서드가 아닙니다.
- 제네릭 클래스에 포함되지 않습니다.
- 로컬 함수가 아닙니다.
ModuleInitializer
특성은 여러 메서드에 적용할 수 있습니다. 이 경우 런타임이 호출하는 순서는 결정적이지만 지정되지 않습니다.
다음 예제에서는 여러 모듈 이니셜라이저 메서드를 사용하는 방법을 보여 줍니다. Init1
및 Init2
메서드는 Main
앞에 실행되며 각 메서드는 Text
속성에 문자열을 추가합니다. 따라서 Main
이 실행될 때 Text
속성에는 양쪽 이니셜라이저 메서드의 문자열이 이미 포함되어 있습니다.
using System;
internal class ModuleInitializerExampleMain
{
public static void Main()
{
Console.WriteLine(ModuleInitializerExampleModule.Text);
//output: Hello from Init1! Hello from Init2!
}
}
using System.Runtime.CompilerServices;
internal class ModuleInitializerExampleModule
{
public static string? Text { get; set; }
[ModuleInitializer]
public static void Init1()
{
Text += "Hello from Init1! ";
}
[ModuleInitializer]
public static void Init2()
{
Text += "Hello from Init2! ";
}
}
경우에 따라 소스 코드 생성기는 초기화 코드를 생성해야 합니다. 모듈 이니셜라이저는 해당 코드의 표준 위치를 제공합니다. 다른 경우에는 대부분 모듈 이니셜라이저 대신 정적 생성자를 작성해야 합니다.
SkipLocalsInit
특성
C# 9부터 특성은 SkipLocalsInit
메타데이터로 내보낸 경우 컴파일러가 .locals init
플래그를 설정하지 못하도록 합니다. SkipLocalsInit
특성은 단일 사용 특성이며 메서드, 속성, 클래스, 구조체, 인터페이스 또는 모듈에 적용하지만 어셈블리에는 적용할 수 없습니다. SkipLocalsInit
는 SkipLocalsInitAttribute의 별칭입니다.
.locals init
플래그를 사용하면 CLR이 메서드에 선언된 모든 지역 변수를 기본값으로 초기화합니다. 컴파일러는 변수에 일부 값을 할당하기 전에 변수를 사용하지 않도록 하므로 .locals init
는 일반적으로 필요하지 않습니다. 그러나 stackalloc를 사용하여 스택에서 배열을 할당하는 경우와 같이 일부 시나리오에서는 추가 0 초기화가 성능에 크게 영향을 줄 수 있습니다. 이 경우 SkipLocalsInit
특성을 추가할 수 있습니다. 메서드에 직접 적용되는 경우 특성은 해당 메서드 및 람다, 지역 함수를 포함한 모든 중첩 함수에 영향을 줍니다. 형식 또는 모듈에 적용되는 경우 내부에 중첩된 모든 메서드에 영향을 줍니다. 해당 특성은 추상 메서드에는 영향을 주지 않지만 구현을 위해 생성된 코드에는 영향을 줍니다.
해당 특성에는 AllowUnsafeBlocks 컴파일러 옵션이 필요합니다. 이 요구 사항은 일부 경우에 코드가 할당되지 않은 메모리를 읽을 수 있음을 나타냅니다(예: 초기화되지 않은 스택 할당 메모리에서 읽기).
다음 예제에서는 stackalloc
를 사용하는 메서드에 대한 SkipLocalsInit
특성의 영향을 보여 줍니다. 해당 메서드는 정수 배열이 할당될 때 메모리에 있던 모든 항목을 표시합니다.
[SkipLocalsInit]
static void ReadUninitializedMemory()
{
Span<int> numbers = stackalloc int[120];
for (int i = 0; i < 120; i++)
{
Console.WriteLine(numbers[i]);
}
}
// output depends on initial contents of memory, for example:
//0
//0
//0
//168
//0
//-1271631451
//32767
//38
//0
//0
//0
//38
// Remaining rows omitted for brevity.
이 코드를 직접 사용해 보려면 .csproj 파일에서 AllowUnsafeBlocks
컴파일러 옵션을 설정합니다.
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>