title | ms.date | description |
---|---|---|
Attributes interpreted by the compiler: Miscellaneous |
07/26/2024 |
Learn about attributes that affect code generated by the compiler: the Conditional, Obsolete, AttributeUsage, ModuleInitializer, and SkipLocalsInit attributes. |
There are several attributes that can be applied to elements in your code that add semantic meaning to those elements:
Conditional
: Make execution of a method dependent on a preprocessor identifier.Obsolete
: Mark a type or member for (potential) future removal.AttributeUsage
: Declare the language elements where an attribute can be applied.AsyncMethodBuilder
: Declare an async method builder type.InterpolatedStringHandler
: Define an interpolated string builder for a known scenario.ModuleInitializer
: Declare a method that initializes a module.SkipLocalsInit
: Elide the code that initializes local variable storage to 0.UnscopedRef
: Declare that aref
variable normally interpreted asscoped
should be treated as unscoped.OverloadResolutionPriority
: Add a tiebreaker attribute to influence overload resolution for possibly ambiguous overloads.Experimental
: Mark a type or member as experimental.
The compiler uses those semantic meanings to alter its output and report possible mistakes by developers using your code.
The Conditional
attribute makes the execution of a method dependent on a preprocessing identifier. The Conditional
attribute is an alias for xref:System.Diagnostics.ConditionalAttribute, and can be applied to a method or an attribute class.
In the following example, Conditional
is applied to a method to enable or disable the display of program-specific diagnostic information:
:::code language="csharp" source="snippets/trace.cs" interactive="try-dotnet" :::
If the TRACE_ON
identifier isn't defined, the trace output isn't displayed. Explore for yourself in the interactive window.
The Conditional
attribute is often used with the DEBUG
identifier to enable trace and logging features for debug builds but not in release builds, as shown in the following example:
:::code language="csharp" source="snippets/ConditionalExamples.cs" ID="SnippetConditional" :::
When a method marked conditional is called, the presence or absence of the specified preprocessing symbol determines whether the compiler includes or omits calls to the method. If the symbol is defined, the call is included; otherwise, the call is omitted. A conditional method must be a method in a class or struct declaration and must have a void
return type. Using Conditional
is cleaner, more elegant, and less error-prone than enclosing methods inside #if…#endif
blocks.
If a method has multiple Conditional
attributes, compiler includes calls to the method if one or more conditional symbols are defined (the symbols are logically linked together by using the OR operator). In the following example, the presence of either A
or B
results in a method call:
:::code language="csharp" source="snippets/ConditionalExamples.cs" ID="SnippetMultipleConditions" :::
The Conditional
attribute can also be applied to an attribute class definition. In the following example, the custom attribute Documentation
adds information to the metadata if DEBUG
is defined.
:::code language="csharp" source="snippets/ConditionalExamples.cs" ID="SnippetConditionalConditionalAttribute" :::
The Obsolete
attribute marks a code element as no longer recommended for use. Use of an entity marked obsolete generates a warning or an error. The Obsolete
attribute is a single-use attribute and can be applied to any entity that allows attributes. Obsolete
is an alias for xref:System.ObsoleteAttribute.
In the following example, the Obsolete
attribute is applied to class A
and to method B.OldMethod
. Because the second argument of the attribute constructor applied to B.OldMethod
is set to true
, this method causes a compiler error, whereas using class A
produces a warning. Calling B.NewMethod
, however, produces no warning or error. For example, when you use it with the previous definitions, the following code generates two warnings and one error:
:::code language="csharp" source="snippets/ObsoleteExample.cs" ID="Snippet1" interactive="try-dotnet" :::
The string provided as the first argument to the attribute constructor is displayed as part of the warning or error. Two warnings for class A
are generated: one for the declaration of the class reference, and one for the class constructor. The Obsolete
attribute can be used without arguments, but including an explanation what to use instead is recommended.
In C# 10, you can use constant string interpolation and the nameof
operator to ensure the names match:
:::code language="csharp" source="snippets/ObsoleteExample.cs" id="Snippet2" :::
Beginning in C# 12, types, methods, and assemblies can be marked with the xref:System.Diagnostics.CodeAnalysis.ExperimentalAttribute?displayProperty=nameWithType to indicate an experimental feature. The compiler issues a warning if you access a method or type annotated with the xref:System.Diagnostics.CodeAnalysis.ExperimentalAttribute. All types declared in an assembly or module marked with the Experimental
attribute are experimental. The compiler issues a warning if you access any of them. You can disable these warnings to pilot an experimental feature.
Warning
Experimental features are subject to changes. The APIs may change, or they may be removed in future updates. Including experimental features is a way for library authors to get feedback on ideas and concepts for future development. Use extreme caution when using any feature marked as experimental.
You can read more details about the Experimental
attribute in the feature specification.
The SetsRequiredMembers
attribute informs the compiler that a constructor sets all required
members in that class or struct. The compiler assumes any constructor with the xref:System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute?displayProperty=fullName attribute initializes all required
members. Any code that invokes such a constructor doesn't need object initializers to set required members. Adding the SetsRequiredMembers
attribute is primarily useful for positional records and primary constructors.
The AttributeUsage
attribute determines how a custom attribute class can be used. xref:System.AttributeUsageAttribute is an attribute you apply to custom attribute definitions. The AttributeUsage
attribute enables you to control:
- Which program elements the attribute can be applied to. Unless you restrict its usage, an attribute can be applied to any of the following program elements:
- Assembly
- Module
- Field
- Event
- Method
- Param
- Property
- Return
- Type
- Whether an attribute can be applied to a single program element multiple times.
- Whether derived classes inherit attributes.
The default settings look like the following example when applied explicitly:
:::code language="csharp" source="snippets/NewAttribute.cs" ID="SnippetUsageFirst" :::
In this example, the NewAttribute
class can be applied to any supported program element. But it can be applied only once to each entity. Derived classes inherit the attribute applied to a base class.
The xref:System.AttributeUsageAttribute.AllowMultiple and xref:System.AttributeUsageAttribute.Inherited arguments are optional, so the following code has the same effect:
:::code language="csharp" source="snippets/NewAttribute.cs" ID="SnippetUsageSecond" :::
The first xref:System.AttributeUsageAttribute argument must be one or more elements of the xref:System.AttributeTargets enumeration. Multiple target types can be linked together with the OR operator, like the following example shows:
:::code language="csharp" source="snippets/NewPropertyOrFieldAttribute.cs" ID="SnippetDefinePropertyAttribute" :::
Attributes can be applied to either the property or the backing field for an autoimplemented property. The attribute applies to the property, unless you specify the field
specifier on the attribute. Both are shown in the following example:
:::code language="csharp" source="snippets/NewPropertyOrFieldAttribute.cs" ID="SnippetUsePropertyAttribute" :::
If the xref:System.AttributeUsageAttribute.AllowMultiple argument is true
, then the resulting attribute can be applied more than once to a single entity, as shown in the following example:
:::code language="csharp" source="snippets/MultiUseAttribute.cs" ID="SnippetMultiUse" :::
In this case, MultiUseAttribute
can be applied repeatedly because AllowMultiple
is set to true
. Both formats shown for applying multiple attributes are valid.
If xref:System.AttributeUsageAttribute.Inherited is false
, then derived classes don't inherit the attribute from an attributed base class. For example:
:::code language="csharp" source="snippets/NonInheritedAttribute.cs" ID="SnippetNonInherited" :::
In this case, NonInheritedAttribute
isn't applied to DClass
via inheritance.
You can also use these keywords to specify where an attribute should be applied. For example, you can use the field:
specifier to add an attribute to the backing field of an autoimplemented property. Or you can use the field:
, property:
or param:
specifier to apply an attribute to any of the elements generated from a positional record. For an example, see Positional syntax for property definition.
You add the xref:System.Runtime.CompilerServices.AsyncMethodBuilderAttribute?displayProperty=nameWithType attribute to a type that can be an async return type. The attribute specifies the type that builds the async method implementation when the specified type is returned from an async method. The AsyncMethodBuilder
attribute can be applied to a type that:
- Has an accessible
GetAwaiter
method. - The object returned by the
GetAwaiter
method implements the xref:System.Runtime.CompilerServices.ICriticalNotifyCompletion?displayProperty=nameWithType interface.
The constructor to the AsyncMethodBuilder
attribute specifies the type of the associated builder. The builder must implement the following accessible members:
-
A static
Create()
method that returns the type of the builder. -
A readable
Task
property that returns the async return type. -
A
void SetException(Exception)
method that sets the exception when a task faults. -
A
void SetResult()
orvoid SetResult(T result)
method that marks the task as completed and optionally sets the task's result -
A
Start
method with the following API signature:void Start<TStateMachine>(ref TStateMachine stateMachine) where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
-
An
AwaitOnCompleted
method with the following signature:public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.INotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
-
An
AwaitUnsafeOnCompleted
method with the following signature:public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : System.Runtime.CompilerServices.ICriticalNotifyCompletion where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
You can learn about async method builders by reading about the following builders supplied by .NET:
- xref:System.Runtime.CompilerServices.AsyncTaskMethodBuilder?displayProperty=fullName
- xref:System.Runtime.CompilerServices.AsyncTaskMethodBuilder%601?displayProperty=fullName
- xref:System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder?displayProperty=fullName
- xref:System.Runtime.CompilerServices.AsyncValueTaskMethodBuilder%601?displayProperty=fullName
In C# 10 and later, the AsyncMethodBuilder
attribute can be applied to an async method to override the builder for that type.
Starting with C# 10, you use these attributes to specify that a type is an interpolated string handler. The .NET 6 library already includes xref:System.Runtime.CompilerServices.DefaultInterpolatedStringHandler?displayProperty=nameWithType for scenarios where you use an interpolated string as the argument for a string
parameter. You might have other instances where you want to control how interpolated strings are processed. You apply the xref:System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute?displayProperty=nameWithType to the type that implements your handler. You apply the xref:System.Runtime.CompilerServices.InterpolatedStringHandlerArgumentAttribute?displayProperty=nameWithType to parameters of that type's constructor.
You can learn more about building an interpolated string handler in the C# 10 feature specification for interpolated string improvements.
The ModuleInitializer
attribute marks a method that the runtime calls when the assembly loads. ModuleInitializer
is an alias for xref:System.Runtime.CompilerServices.ModuleInitializerAttribute.
The ModuleInitializer
attribute can only be applied to a method that:
- Is static.
- Is parameterless.
- Returns
void
. - Is accessible from the containing module, that is,
internal
orpublic
. - Isn't a generic method.
- Isn't contained in a generic class.
- Isn't a local function.
The ModuleInitializer
attribute can be applied to multiple methods. In that case, the order in which the runtime calls them is deterministic but not specified.
The following example illustrates use of multiple module initializer methods. The Init1
and Init2
methods run before Main
, and each adds a string to the Text
property. So when Main
runs, the Text
property already has strings from both initializer methods.
:::code language="csharp" source="snippets/ModuleInitializerExampleMain.cs" :::
:::code language="csharp" source="snippets/ModuleInitializerExampleModule.cs" :::
Source code generators sometimes need to generate initialization code. Module initializers provide a standard place for that code. In most other cases, you should write a static constructor instead of a module initializer.
The SkipLocalsInit
attribute prevents the compiler from setting the .locals init
flag when emitting to metadata. The SkipLocalsInit
attribute is a single-use attribute and can be applied to a method, a property, a class, a struct, an interface, or a module, but not to an assembly. SkipLocalsInit
is an alias for xref:System.Runtime.CompilerServices.SkipLocalsInitAttribute.
The .locals init
flag causes the CLR to initialize all of the local variables declared in a method to their default values. Since the compiler also makes sure that you never use a variable before assigning some value to it, .locals init
is typically not necessary. However, the extra zero-initialization might have measurable performance impact in some scenarios, such as when you use stackalloc to allocate an array on the stack. In those cases, you can add the SkipLocalsInit
attribute. If applied to a method directly, the attribute affects that method and all its nested functions, including lambdas and local functions. If applied to a type or module, it affects all methods nested inside. This attribute doesn't affect abstract methods, but it does affect code generated for the implementation.
This attribute requires the AllowUnsafeBlocks compiler option. This requirement signals that in some cases code could view unassigned memory (for example, reading from uninitialized stack-allocated memory).
The following example illustrates the effect of SkipLocalsInit
attribute on a method that uses stackalloc
. The method displays whatever was in memory when the array of integers was allocated.
:::code language="csharp" source="snippets/SkipLocalsInitExample.cs" ID="ReadUninitializedMemory":::
To try this code yourself, set the AllowUnsafeBlocks
compiler option in your .csproj file:
<PropertyGroup>
...
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
The UnscopedRef
attribute marks a variable declaration as unscoped, meaning the reference is allowed to escape.
You add this attribute where the compiler treats a ref
as implicitly scoped
:
- The
this
parameter forstruct
instance methods. ref
parameters that refer toref struct
types.out
parameters.
Applying the xref:System.Diagnostics.CodeAnalysis.UnscopedRefAttribute?displayProperty=nameWithType marks the element as unscoped.
The xref:System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute enables library authors to prefer one overload over another when two overloads can be ambiguous. Its primary use case is for library authors to write better performing overloads while still supporting existing code without breaks.
For example, you might add a new overload that uses xref:System.ReadOnlySpan%601 to reduce memory allocations:
:::code language="csharp" source="snippets/OrpaSnippets.cs" ID="SnippetOverloadExample":::
Overload resolution considers the two methods equally good for some argument types. For an argument of int[]
, it prefers the first overload. To get the compiler to prefer the ReadOnlySpan
version, you can increase the priority of that overload. The following example shows the effect of adding the attribute:
:::code language="csharp" source="snippets/OrpaSnippets.cs" ID="SnippetOrpaExample":::
All overloads with a lower priority than the highest overload priority are removed from the set of applicable methods. Methods without this attribute have the overload priority set to the default of zero. Library authors should use this attribute as a last resort when adding a new and better method overload. Library authors should have a deep understanding of how Overload resolution impacts choosing the better method. Otherwise, unexpected errors can result.
- xref:System.Attribute
- xref:System.Reflection
- Attributes
- Reflection