If you look at what’s new in C# 9, you’ll see records, init-only properties, and top-level statements get all the glory. And that’s fine, because they’re great. At the end of the bulleted list, I noticed support for local attributes on local functions.

When it comes to local functions, with C# 9 you can apply attributes to function declarations, parameters, and type parameters.

The most common use case I’ve seen is using the ConditionalAttribute. This attribute is used for checking conditional compilation symbols—instead of using #define DEBUG regions, for example, you can do it here. You can declare multiple attributes, just like for a “normal” method. For example:

static void Main(string[] args)
{
    [Conditional("DEBUG")]
    [Conditional("BETA")]
    static void DoSuperSecretStuff()
    {
        Console.WriteLine("This only is executed in debug or beta mode.");
    }

    DoSuperSecretStuff();
}

If you want to work with the ConditionalAttribute the methods must be both static and void.

Of course, you can work with any kind of attributes. In the GruutBot project (which I showcased here), there’s a local function that has a switch expression to determine Gruut’s emotion:

static string GetReplyText(TextSentiment sentiment) => sentiment switch
{
    TextSentiment.Positive => "I am Gruut.",
    TextSentiment.Negative => "I AM GRUUUUUTTT!!",
    TextSentiment.Neutral => "I am Gruut?",
    _ => "I. AM. GRUUUUUT"
};

Here I can go wild with a few more attributes:

[Obsolete]
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
static string GetReplyText(TextSentiment sentiment) => sentiment switch
{
    TextSentiment.Positive => "I am Gruut.",
    TextSentiment.Negative => "I AM GRUUUUUTTT!!",
    TextSentiment.Neutral => "I am Gruut?",
    _ => "I. AM. GRUUUUUT"
};

The ObsoleteAttribute signifies to callers that the function is no longer use (and will trigger a warning when invoked).

As for the MethodImplAttribute, it allows me to specify AggressiveOptimization. The details are a topic for another post, but from a high level: this method is JIT’ed when first called, and the JIT’ed code is always optimized—making it ineligible for tiered compilation.

This new local function support brings attributes to this scope, which is a welcome addition. These aren’t “new” attributes, but just the ability to do it in local functions now.

Wrap up

In this post, I quickly showed you how to use local function attributes in C# 9. If you have any suggestions, please let me know!