I'm trying to understand an edge-case of compiler powered delegate caching to avoid memory allocation.
For example, to my understanding, this delegate gets cached to a single instance and reused, because it doesn't close over any local variable:
int[] set = new [] { 1, 2, 3, 4, 5, 6 };
var subset = set.Where(x => x % 2 == 0);
Now I have some cases where my generated code might want to invoke a delegate directly, so an anonymous method is not valid C#, like so:
var result = (x => x % 2 == 0).Invoke(5); // Invalid
To circumvent this, I see two options:
var result = (new Func<int, bool>(x => x % 2 == 0)).Invoke(5);
var result = ((Func<int, bool>)(x => x % 2 == 0)).Invoke(5);
I'm assuming the compiler will not cache the delegate in option #1, but I'm not sure if it will in #2.
Is this documented anywhere?
I'm assuming the compiler will not cache the delegate in option #1, but I'm not sure if it will in #2.
In fact it can in both cases, and they're tied together.
From the ECMA C# 5 spec, section 7.6.10.5:
The binding-time processing of a delegate-creation-expression of the form new D(E), where D is a delegate-type and E is an expression, consists of the following steps:
- ...
- If E is an anonymous function, the delegate creation expression is processed in the same way as an anonymous function conversion (§6.5) from E to D.
- ...
So basically the two are handled in the same way. And in both cases it can be cached. And yes, it's very odd that "new doesn't necessarily mean new".
To show this, let's write a pretty trivial program:
using System;
public class Program
{
public static void Main()
{
var func = new Func<int, bool>(x => x % 2 == 0);
}
}
Here's the IL for the Main
method on my machine (admittedly building with the C# 8 preview compiler, but I'd expect the same for a while):
.method public hidebysig static void Main() cil managed
{
.entrypoint
// Code size 29 (0x1d)
.maxstack 8
IL_0000: ldsfld class [mscorlib]System.Func`2<int32,bool> Program/'<>c'::'<>9__0_0'
IL_0005: brtrue.s IL_001c
IL_0007: ldsfld class Program/'<>c' Program/'<>c'::'<>9'
IL_000c: ldftn instance bool Program/'<>c'::'<Main>b__0_0'(int32)
IL_0012: newobj instance void class [mscorlib]System.Func`2<int32,bool>::.ctor(object,
native int)
IL_0017: stsfld class [mscorlib]System.Func`2<int32,bool> Program/'<>c'::'<>9__0_0'
IL_001c: ret
} // end of method Program::Main
That's effectively:
Func<int, bool> func;
func = cache;
if (func == null)
{
func = new Func<int, bool>(GeneratedPrivateMethod);
cache = func;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With