Skip to content
Draft
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: 76 additions & 5 deletions standard/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@
- `System.Diagnostics.ConditionalAttribute` ([§23.5.3](attributes.md#2353-the-conditional-attribute)), is a multi-use attribute class which is used to define conditional methods and conditional attribute classes. This attribute indicates a condition by testing a conditional compilation symbol.
- `System.ObsoleteAttribute` ([§23.5.4](attributes.md#2354-the-obsolete-attribute)), which is used to mark a member as obsolete.
- `System.Runtime.CompilerServices.AsyncMethodBuilderAttribute` ([§23.5.5](attributes.md#2355-the-asyncmethodbuilder-attribute)), which is used to establish a task builder for an async method.
- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§23.5.6.3](attributes.md#23563-the-callerfilepath-attribute)), and `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§23.5.6.4](attributes.md#23564-the-callermembername-attribute)), which are used to supply information about the calling context to optional parameters.
- `System.Runtime.CompilerServices.CallerLineNumberAttribute` ([§23.5.6.2](attributes.md#23562-the-callerlinenumber-attribute)), `System.Runtime.CompilerServices.CallerFilePathAttribute` ([§23.5.6.3](attributes.md#23563-the-callerfilepath-attribute)), `System.Runtime.CompilerServices.CallerMemberNameAttribute` ([§23.5.6.4](attributes.md#23564-the-callermembername-attribute)), and `System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` (§callargexpattr), which are used to supply information about the calling context to optional parameters.
- `System.Runtime.CompilerServices.EnumeratorCancellationAttribute` ([§23.5.8](attributes.md#2358-the-enumeratorcancellation-attribute)), which is used to specify parameter for the cancellation token in an asynchronous iterator.
- `System.Runtime.CompilerServices.ModuleInitializer` ([§23.5.9](attributes.md#2359-the-moduleinitializer-attribute)), which is used to mark a method as a module initializer.

Expand Down Expand Up @@ -797,13 +797,13 @@

One exception is query expressions. These are considered syntactic expansions, and if the calls they expand to omit optional parameters with caller-info attributes, caller information will be substituted. The location used is the location of the query clause which the call was generated from.

If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumber`, `CallerFilePath`, `CallerMemberName`. Consider the following parameter declaration:
If more than one caller-info attribute is specified on a given parameter, they are recognized in the following order: `CallerLineNumber`, `CallerFilePath`, `CallerMemberName`, `CallerArgumentExpression`. Consider the following parameter declaration:

```csharp
[CallerMemberName, CallerFilePath, CallerLineNumber] object p = ...
```

`CallerLineNumber` takes precedence, and the other two attributes are ignored. If `CallerLineNumber` were omitted, `CallerFilePath` would take precedence, and `CallerMemberName` would be ignored. The lexical ordering of these attributes is irrelevant.
`CallerLineNumber` takes precedence, and the other three attributes are ignored. If `CallerLineNumber` were omitted, `CallerFilePath` would take precedence, and `CallerMemberName` and `CallerArgumentExpression` would be ignored. The lexical ordering of these attributes is irrelevant.

#### 23.5.6.2 The CallerLineNumber attribute

Expand Down Expand Up @@ -845,6 +845,79 @@

For an invocation that occurs within a local function, the name of the method that calls that local function is used. Consider the following: if method `M` calls local function `F1`, which in turn calls local function `F2`, and `F2` has a parameter marked with this attribute, the method name passed to `F2` is `M`, because a local function is *not* a function member!

#### §callargexpattr The CallerArgumentExpression attribute

The attribute `System.Runtime.CompilerServices.CallerArgumentExpressionAttribute` is applied to a *target parameter*, and can result in the capture of the source-code text of a sibling parameter’s argument as a string, referred to here as the *captured string*.

Except when it is the first parameter in an extension method, the target parameter shall have a *default_argument*. When applied to the first parameter of an extension method, the captured string is the source text of the receiver expression in an extension method invocation. If the method is invoked using static method syntax, the captured string is the argument corresponding to the first parameter.

Consider the following method declaration:

<!-- Example: {template:"standalone-lib-without-using", name:"CallerArgumentAttr1"} -->
```csharp
using System;
using System.Runtime.CompilerServices;
#nullable enable
class Test
{

Check warning on line 862 in standard/attributes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/attributes.md#L862

MDC032::Line length 92 > maximum 81
public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null)
{
Console.WriteLine($"val = {val}, text = <{text}>");
}
}
```

in which the target parameter is `text` and the sibling parameter is `val`, whose corresponding argument’s source-code text can be captured in `text` when `M` is called.

The attribute constructor takes an argument of type `string`. That string

- Shall contain the name of a sibling parameter; otherwise, the attribute is ignored.
- Shall omit the leading `@` from a parameter name having that prefix.

A *parameter_list* may contain multiple target parameters.

The type of the target parameter shall have a standard conversion from `string`.

> *Note:* This means no user-defined conversions from `string` are allowed, and in practice means the type of such a parameter must be `string`, `object`, or an interface implemented by `string`. *end note*
Copy link
Contributor

Choose a reason for hiding this comment

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

Or dynamic.

Copy link
Contributor

Choose a reason for hiding this comment

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

The "First-class Span Types" proposal will also add a standard implicit conversion from string to ReadOnlySpan<char>.


If an explicit argument is passed for the target parameter, no string is captured, and that parameter takes on that argument’s value. Otherwise, the text for the argument corresponding to the sibling parameter is converted to a captured string, according to the following rules:
Copy link
Contributor

Choose a reason for hiding this comment

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

Apparently, "the text for the argument" means the expression or variable_reference part of argument_value, or the attribute_argument_expression. It would be good to have an example that shows the in/out/ref part of argument_value is not included in the text.


- Leading and trailing white space is removed both before and after any outermost grouping parentheses are removed.
- All outermost grouping parentheses are removed both before and after any leading and trailing white space is removed.
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose "grouping parentheses" here means that the parentheses are part of the same parenthesized_expression, rather than a tuple_expression:

using System.Runtime.CompilerServices;
#nullable enable
class Test
{
    public static void M(
        (int, int) val,
        [CallerArgumentExpression("val")] string? text = null)
    {
    }
    
    static void N()
    {
        // text: "(1, 2)" rather than "1, 2"
        M(((1, 2)));
    }
}

(In the grammar, a deconstruction_tuple likewise has parentheses, but that cannot be an argument_value.)

- All other *input_element*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers).
Copy link
Contributor

Choose a reason for hiding this comment

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

Roslyn deletes some comments here:

using System;
using System.Runtime.CompilerServices;
#nullable enable
class Test
{
    public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null)
    {
        Console.WriteLine($"val = {val}, text = <{text}>");
        M(/*a*/ ( /*b*/ 1 /*c*/ + /*d*/ 2 /*e*/ ) /*f*/);
    }
}

lowered to:

      M(3, "1 /*c*/ + /*d*/ 2");

Copy link
Contributor Author

@RexJaeschke RexJaeschke Mar 19, 2026

Choose a reason for hiding this comment

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

@KalleOlaviNiemitalo At a glance, this suggests that comments before the first token (redundant parens excluded) and after the last token are treated as whitespace---so are removed---while those between tokens are preserved. This follows the rule I wrote at Line 887 above.

Copy link
Contributor

Choose a reason for hiding this comment

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

White space is defined in 6.3.4 and does not include comments.

Copy link
Contributor

@KalleOlaviNiemitalo KalleOlaviNiemitalo Mar 19, 2026

Choose a reason for hiding this comment

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

input_element does not include PP_Directive but AFAICT those are also retained, if they occur within the argument expression.

Suggested change
- All other *input_element*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers).
- All other *input_element*s and *PP_Directive*s are retained verbatim (including white space, comments, *Unicode_Escape_Sequence*s, and `@` prefixes on identifiers).


The captured string is then passed as the argument corresponding to the target parameter. However, if the argument for the sibling parameter is omitted, the target parameter takes on its *default_argument* value.

> *Example*: Given the declaration of `M` above, consider the following calls to `M`:
>
> <!-- Example: {template:"standalone-console", name:"CallerArgumentAttr2", inferOutput:true, additionalFiles:["CallerArgumentAttrM.cs"]} -->
> ```csharp
> Test.M();
> Test.M(123);
> Test.M(123, null);
> Test.M(123, "xyz");
> Test.M( 1 + 2 );
> Test.M(( ( (123) + 0) ) );
> int local = 10;
> Test.M(l\u006fcal /*...*/ + // xxx
> 5);
> ```
>
> the output produced is
>
> ```console
> val = 0, text = <>
> val = 123, text = <123>
> val = 123, text = <>
> val = 123, text = <xyz>
> val = 3, text = <1 + 2>
> val = 123, text = <(123) + 0>
> val = 15, text = <l\u006fcal /*...*/ + // xxx
> 5>
> ```
>
> *end example*

### 23.5.7 Code analysis attributes

#### 23.5.7.1 General
Expand Down Expand Up @@ -967,7 +1040,7 @@
> ```csharp
> #nullable enable
> public class X
> {

Check warning on line 1043 in standard/attributes.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/attributes.md#L1043

MDC032::Line length 86 > maximum 81
> private void ThrowIfNull([DoesNotReturnIf(true)] bool isNull, string argumentName)
> {
> if (isNull)
Expand Down Expand Up @@ -1192,5 +1265,3 @@
> Now, the indexer’s name is `TheItem`.
>
> *end example*


8 changes: 8 additions & 0 deletions standard/standard-library.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@
public class NullReferenceException : Exception
{
public NullReferenceException();
public NullReferenceException(string? message);

Check warning on line 193 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L193

MDC032::Line length 82 > maximum 81
public NullReferenceException(string? message, Exception? innerException);
}

Expand Down Expand Up @@ -238,7 +238,7 @@
public sealed class StackOverflowException : Exception
{
public StackOverflowException();
public StackOverflowException(string? message);

Check warning on line 241 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L241

MDC032::Line length 82 > maximum 81
public StackOverflowException(string? message, Exception? innerException);
}

Expand All @@ -247,7 +247,7 @@
public int Length { get; }
public char this [int index] { get; }
public static string Format(string format, params object?[] args);
System.Collections.IEnumerator IEnumerable.GetEnumerator();

Check warning on line 250 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L250

MDC032::Line length 87 > maximum 81
System.Collections.Generic.IEnumerator<char> IEnumerable<char>.GetEnumerator();
}

Expand Down Expand Up @@ -407,7 +407,7 @@
public class OperationCanceledException : Exception
{
public OperationCanceledException();
public OperationCanceledException(string? message);

Check warning on line 410 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L410

MDC032::Line length 86 > maximum 81
public OperationCanceledException(string? message, Exception? innerException);
}

Expand Down Expand Up @@ -675,7 +675,7 @@
}

public interface IAsyncEnumerable<out T>
{

Check warning on line 678 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L678

MDC032::Line length 82 > maximum 81
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken token = default);
}

Expand Down Expand Up @@ -742,7 +742,7 @@
System.AttributeTargets.Property, AllowMultiple=true, Inherited=false)]
public sealed class MemberNotNullWhenAttribute : Attribute
{
public MemberNotNullWhenAttribute(bool returnValue, string member) {}

Check warning on line 745 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L745

MDC032::Line length 87 > maximum 81
public MemberNotNullWhenAttribute(bool returnValue, params string[] members) {}
}

Expand Down Expand Up @@ -789,6 +789,13 @@
public Type BuilderType { get; }
}

[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false,
Inherited = false)]
public sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute(string parameterName);
}

[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
public sealed class CallerFilePathAttribute : Attribute
{
Expand Down Expand Up @@ -898,7 +905,7 @@
}

public class Task<TResult> : Task
{

Check warning on line 908 in standard/standard-library.md

View workflow job for this annotation

GitHub Actions / Markdown to Word Converter

standard/standard-library.md#L908

MDC032::Line length 85 > maximum 81
public new System.Runtime.CompilerServices.TaskAwaiter<TResult> GetAwaiter();
}

Expand Down Expand Up @@ -1389,6 +1396,7 @@
- `global::System.Linq.Expressions.Expression<TDelegate>`
- `global::System.Reflection.MemberInfo`
- `global::System.Runtime.CompilerServices.AsyncMethodBuilderAttribute`
- `global::System.Runtime.CompilerServices.CallerArgumentExpressionAttribute`
- `global::System.Runtime.CompilerServices.CallerFilePathAttribute`
- `global::System.Runtime.CompilerServices.CallerLineNumberAttribute`
- `global::System.Runtime.CompilerServices.CallerMemberNameAttribute`
Expand Down
10 changes: 10 additions & 0 deletions tools/example-templates/additional-files/CallerArgumentAttrM.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Runtime.CompilerServices;
#nullable enable
class Test
{
public static void M(int val = 0, [CallerArgumentExpression("val")] string? text = null)
{
Console.WriteLine($"val = {val}, text = <{text}>");
}
}
Loading