Skip to content

Attributes on local functions C# 9.0new scenarios

Apply attributes to local functions and their parameters.

Local functions were introduced in C# 7.0 but could not have attributes applied to them. This meant you could not use compiler-recognized attributes like [NotNullWhen], [DoesNotReturn] or [Conditional] on local functions, so a helper that the compiler needed to understand had to be a regular method.

C# 9.0 allows attributes on local functions and their parameters, bringing them to parity with regular methods.

Code ​

csharp
static void Validate(string name)
{
    [DoesNotReturn]
    static void ThrowHelperError(string paramName)
    {
        throw new ArgumentNullException(paramName);
    }

    if (name is null)
        ThrowHelperError(nameof(name));
}

static bool TryParse(string input, [NotNullWhen(true)] out MyType? result)
{
    [return: NotNullIfNotNull("s")]
    static MyType? ParseCore(string? s) => s is null ? null : new MyType(s);

    result = input is not null ? ParseCore(input) : null;
    return result is not null;
}
csharp
static void Validate(string name)
{
    // Could not mark local functions with [DoesNotReturn]
    // so they had to be promoted to regular private methods
    if (name is null)
        ThrowHelperError(nameof(name));
}

[DoesNotReturn]
private static void ThrowHelperError(string paramName)
{
    throw new ArgumentNullException(paramName);
}

Notes ​

  • NotNullIfNotNull takes the name of the local function's own parameter. Use a string here because nameof cannot see that parameter until extended nameof scope C# 11.0
  • The attribute is emitted onto the generated method even though a local function has no metadata-visible name, so the effect is compile-time only and cannot be read back with reflection
  • [Conditional] works here too: a call to a local function marked [Conditional("DEBUG")] is omitted entirely when the symbol is not defined, which is a useful way to keep a diagnostic helper out of release builds
  • The flow analysis attributes are the other main reason to want this - [DoesNotReturn], [NotNull], [NotNullWhen], [MaybeNullWhen] and [NotNullIfNotNull] change what the compiler knows after the call
  • The attributes can be applied to the local function itself or to its parameters, as with a normal method
  • Before C# 9 the workaround was to promote the helper to a private static method, which cost you the enclosing method's scope and made the helper reachable from the whole class

More information ​

Powered by Intracia