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.