Skip to content

initial docs on async method builders #24973

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 63 additions & 18 deletions docs/csharp/language-reference/attributes/general.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "C# reserved attributes: Miscellaneous"
ms.date: 03/18/2021
ms.date: 07/02/2021
description: "Learn about attributes that affect code generated by the compiler: the Conditional, Obsolete, AttributeUsage, ModuleInitializer, and SkipLocalsInit attributes."
---
# Reserved attributes: Miscellaneous
Expand All @@ -19,33 +19,33 @@ If the `TRACE_ON` identifier isn't defined, the trace output isn't displayed. Ex

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" :::
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the motivation for changing id to ID? Our guidance in the contributor guide uses lowercase.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdykstra Acrolinx marks down "id" in favor of "ID". Enough samples, and it has a measurable effect on the score.

Should I ping @MonicaRush on that one? (I'd honestly thought the guidance had changed when it kept getting flagged)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this should count as a false positive, Acrolinx shouldn't be applying general text rules to triple-colon statements.

:::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 call is included or omitted. 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.
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, a call to the method is included if at one or more conditional symbols is 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:
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" :::
:::code language="csharp" source="snippets/ConditionalExamples.cs" ID="SnippetMultipleConditions" :::

### Using `Conditional` with attribute classes

The `Conditional` attribute can also be applied to an attribute class definition. In the following example, the custom attribute `Documentation` will only add information to the metadata if `DEBUG` is defined.

:::code language="csharp" source="snippets/ConditionalExamples.cs" id="SnippetConditionalConditionalAttribute" :::
:::code language="csharp" source="snippets/ConditionalExamples.cs" ID="SnippetConditionalConditionalAttribute" :::

## `Obsolete` attribute

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 will cause a compiler error, whereas using class `A` will just produce 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:
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 will cause a compiler error, whereas using class `A` will just produce 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" :::
:::code language="csharp" source="snippets/ObsoleteExample.cs" ID="Snippet1" interactive="try-dotnet" :::

The string provided as the first argument to the attribute constructor will be 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" :::
:::code language="csharp" source="snippets/ObsoleteExample.cs" ID="Snippet2" :::

## `AttributeUsage` attribute

Expand All @@ -66,36 +66,81 @@ The `AttributeUsage` attribute determines how a custom attribute class can be us

The default settings look like the following example when applied explicitly:

:::code language="csharp" source="snippets/NewAttribute.cs" id="SnippetUsageFirst" :::
:::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. The attribute is inherited by derived classes when 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" :::
:::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" :::
:::code language="csharp" source="snippets/NewPropertyOrFieldAttribute.cs" ID="SnippetDefinePropertyAttribute" :::

Beginning in C# 7.3, attributes can be applied to either the property or the backing field for an auto-implemented 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" :::
:::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" :::
:::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 the attribute isn't inherited by classes derived from an attributed class. For example:

:::code language="csharp" source="snippets/NonInheritedAttribute.cs" id="SnippetNonInherited" :::
:::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 [auto-implemented property](../../programming-guide/classes-and-structs/properties.md#auto-implemented-properties). 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](../builtin-types/record.md#positional-syntax-for-property-definition).

## `AsyncMethodBuilder` attribute

Beginning with C# 7, 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()` or `void 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:

```csharp
void Start<TStateMachine>(ref TStateMachine stateMachine)
where TStateMachine : System.Runtime.CompilerServices.IAsyncStateMachine
```

* An `AwaitOnCompleted` method with the following signature:

```csharp
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:

```csharp
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.0 and later, the `AsyncMethodBuilder` attribute can be applied to an async method to override the builder for that type.

## `ModuleInitializer` attribute

Starting with C# 9, the `ModuleInitializer` attribute marks a method that the runtime calls when the assembly loads. `ModuleInitializer` is an alias for <xref:System.Runtime.CompilerServices.ModuleInitializerAttribute>.
Expand All @@ -118,19 +163,19 @@ The following example illustrates use of multiple module initializer methods. Th

:::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 to reside.
Source code generators sometimes need to generate initialization code. Module initializers provide a standard place for that code.

## `SkipLocalsInit` attribute

Starting in C# 9, 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 may have measurable performance impact in some scenarios, such as when you use [stackalloc](../operators/stackalloc.md) 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 does not affect abstract methods, but it does affect code generated for the implementation.
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 may have measurable performance impact in some scenarios, such as when you use [stackalloc](../operators/stackalloc.md) 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-options/language.md#allowunsafeblocks) compiler option. This is to signal 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":::
:::code language="csharp" source="snippets/SkipLocalsInitExample.cs" ID="ReadUninitializedMemory":::

To try this code yourself, set the `AllowUnsafeBlocks` compiler option in your *.csproj* file:

Expand Down
Loading