Static anonymous functions

Home DailyDrop

Daily Knowledge Drop

When using anonymous functions (lambda functions) - in certain use cases static anonymous functions can be used instead to improve performance of the application.


Anonymous function

Code

In the below sample, a function OutputDatetime is called to output the current datetime.
However the formatting of the output is determined by a Func (an anonymous function) in conjunction with either the formattedTime string or slimTime string (or any other string format which can be specified).

string formattedTime = "The current time is: {0}";
string slimTime = "{0}";

OutputDatetime(inputText => string.Format(formattedTime, inputText));
OutputDatetime(inputText => string.Format(slimTime, inputText));

void OutputDatetime(Func<string, string> func)
{
    Console.WriteLine(func(DateTime.Now.ToString()));
}

When the Console.WriteLine is executed on line 9, the OutputDatetime method doesn't have visibility of either formattedTime or slimTime, which are used by func - they are not within the scope of OutputDatetime.

The compiler gets around this by creating a closure - more information can be found here


Lowered

When the code is lowered y the compiler, a class is created to encapsulate the local values needed by the function, in this case formattedTime and slimTime.

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public string formattedTime;

    public string slimTime;

    internal string <<Main>$>b__0(string inputText)
    {
        return string.Format(formattedTime, inputText);
    }

    internal string <<Main>$>b__1(string inputText)
    {
        return string.Format(slimTime, inputText);
    }
}

private static void <Main>$(string[] args)
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.formattedTime = "The current time is: {0}";
    <>c__DisplayClass0_.slimTime = "{0}";
    <<Main>$>g__OutputDatetime|0_2(new Func<string, string>(<>c__DisplayClass0_.<<Main>$>b__0));
    <<Main>$>g__OutputDatetime|0_2(new Func<string, string>(<>c__DisplayClass0_.<<Main>$>b__1));
}

[CompilerGenerated]
internal static void <<Main>$>g__OutputDatetime|0_2(Func<string, string> func)
{
    Console.WriteLine(func(DateTime.Now.ToString()));
}

The important parts to note are:

  • A private class is created which contains the two anonymous methods (Func) as methods
  • The values required by the methods are declared are values on the class
  • The values on the class are set to the required values in the <Main>$(string[] args) method

The anonymous functions now have access to the values it requires, which are outside its scope.


Issue at hand

The problem with the above is that the two strings used in the anonymous function need to be captured and stored in the private class. This results in additional allocations which are potentially not required.

C#9 introduced the ability to be able to set an anonymous function as static - however static functions are unable to capture state from the local (declaring) function, so any variables the static function uses must be declared as const.

If the use case allows for the making the use local variables constant, then the anonymous function can also be made static which will reduced unnecessary memory allocations.


Static anonymous function

Code

Let's convert the above example to make use of a static anonymous function:

// The two local variables have been declared as const
const string formattedTime = "The current time is: {0}";
const string slimTime = "{0}";

// The anonymous functions passed in has been declared as static
OutputDatetime(static inputText => string.Format(formattedTime, inputText));
OutputDatetime(static inputText => string.Format(slimTime, inputText));

void OutputDatetime(Func<string, string> func)
{
    Console.WriteLine(func(DateTime.Now.ToString()));
}

Lowered

Taking a look at the lowered code, the benefits of the static anonymous method can be seen:

[Serializable]
[CompilerGenerated]
private sealed class <>c
{
    public static readonly <>c <>9 = new <>c();

    public static Func<string, string> <>9__0_0;

    public static Func<string, string> <>9__0_1;

    internal string <<Main>$>b__0_0(string inputText)
    {
        return string.Format("The current time is: {0}", inputText);
    }

    internal string <<Main>$>b__0_1(string inputText)
    {
        return string.Format("{0}", inputText);
    }
}

private static void <Main>$(string[] args)
{
    <<Main>$>g__OutputDatetime|0_2(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Func<string, string>(<>c.<>9.<<Main>$>b__0_0)));
    <<Main>$>g__OutputDatetime|0_2(<>c.<>9__0_1 ?? (<>c.<>9__0_1 = new Func<string, string>(<>c.<>9.<<Main>$>b__0_1)));
}

[CompilerGenerated]
internal static void <<Main>$>g__OutputDatetime|0_2(Func<string, string> func)
{
    Console.WriteLine(func(DateTime.Now.ToString()));
}

This version of the private class, doesn't contain the two string variables - thats two less allocations compared to the non-static version.


Notes

Wherever possible, static anonymous functions should be used over non-static anonymous functions to avoid the unnecessary memory allocations.


References

Static anonymous functions: New with C# 9

Daily Drop 46: 06-04-2022

At the start of 2022 I set myself the goal of learning one new coding related piece of knowledge a day.
It could be anything - some.NET / C# functionality I wasn't aware of, a design practice, a cool new coding technique, or just something I find interesting. It could be something I knew at one point but had forgotten, or something completely new, which I may or may never actually use.

The Daily Drop is a record of these pieces of knowledge - writing about and summarizing them helps re-enforce the information for myself, as well as potentially helps others learn something new as well.
c# .net anonymous function static