✅ Compiler creates closure when it's not necessary?

I have this function here:
public static void Run<T>(Action<T> action, T parameter) => action(parameter);
public static void Run<T>(Action<T> action, T parameter) => action(parameter);
And then I'm using it like this:
MyObject obj = null;
Run(t => t.Item1.DoThing(t.Item2), (this, obj));
MyObject obj = null;
Run(t => t.Item1.DoThing(t.Item2), (this, obj));
But I have the "Heap Allocation Viewer" plugin installed in Rider, and it says it allocates a closure for the variable t? Even when I run my app, and debug the Run function, it seems to create a hidden class. Why does it do this, even though the Action isn't accessing any variables in the outer scope?
22 Replies
HtmlCompiler
HtmlCompiler15mo ago
apart from this?
bighugemassive3
bighugemassive3OP15mo ago
EVen if I don't use a tuple as the parameter, and just pass "this", it still says closure allocation
jcotton42
jcotton4215mo ago
that's being passed as a parameter, it's not used in the lambda
HtmlCompiler
HtmlCompiler15mo ago
should the compiler know?
jcotton42
jcotton4215mo ago
erm, yes? the compiler does capture analysis to know what to include in the closure
bighugemassive3
bighugemassive3OP15mo ago
No description
jcotton42
jcotton4215mo ago
plug that code into sharplab and look at the decompile maybe?
HtmlCompiler
HtmlCompiler15mo ago
but it's passing through a layer which would be action(parameter)
bighugemassive3
bighugemassive3OP15mo ago
Even in release too
jcotton42
jcotton4215mo ago
oh, I think I see what it's doing it's caching look at the invocation line
Run(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action<string>(<>c.<>9.<M>b__0_0)), "ok");
Run(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action<string>(<>c.<>9.<M>b__0_0)), "ok");
basically it's making sure the Action is only ever allocated once per the app's life instead of every time M is called
bighugemassive3
bighugemassive3OP15mo ago
I guess the plugin is giving the warning for the wrong reason then? Even in the IL viewer it doesn't seem to create a closure class... probably should have looked at that first
jcotton42
jcotton4215mo ago
yeah
bighugemassive3
bighugemassive3OP15mo ago
I get the caching of the action though
jcotton42
jcotton4215mo ago
Run(x => Console.WriteLine(s), "ok");
Run(x => Console.WriteLine(x), "ok");
Run(x => Console.WriteLine(x), "ok");
Run(Console.WriteLine, "ok");
Run(x => Console.WriteLine(s), "ok");
Run(x => Console.WriteLine(x), "ok");
Run(x => Console.WriteLine(x), "ok");
Run(Console.WriteLine, "ok");
becomes
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.s = s;
Run(new Action<string>(<>c__DisplayClass0_.<M>b__0), "ok");
Run(<>c.<>9__0_1 ?? (<>c.<>9__0_1 = new Action<string>(<>c.<>9.<M>b__0_1)), "ok");
Run(<>c.<>9__0_2 ?? (<>c.<>9__0_2 = new Action<string>(<>c.<>9.<M>b__0_2)), "ok");
Run(<>O.<0>__WriteLine ?? (<>O.<0>__WriteLine = new Action<string>(Console.WriteLine)), "ok");
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.s = s;
Run(new Action<string>(<>c__DisplayClass0_.<M>b__0), "ok");
Run(<>c.<>9__0_1 ?? (<>c.<>9__0_1 = new Action<string>(<>c.<>9.<M>b__0_1)), "ok");
Run(<>c.<>9__0_2 ?? (<>c.<>9__0_2 = new Action<string>(<>c.<>9.<M>b__0_2)), "ok");
Run(<>O.<0>__WriteLine ?? (<>O.<0>__WriteLine = new Action<string>(Console.WriteLine)), "ok");
only the first one that captures s actually incurs a new allocation on every call of M the rest are a one-time cost
333fred
333fred15mo ago
These types of warnings are notoriously hard to get right
bighugemassive3
bighugemassive3OP15mo ago
Hm in my actual code, it actually does seem to create an instance of one of those DisplayClass things
jcotton42
jcotton4215mo ago
side note, since you're here @333fred, why are they called display classes?
333fred
333fred15mo ago
¯\_(ツ)_/¯
bighugemassive3
bighugemassive3OP15mo ago
Nvm i was misreading the IL code, It's storing that cached action in a display class instance. newobj only gets called twice at the start, then only once afterwards which is the ValueTuple At least my attempts to make this app run faster weren't a total waste
Want results from more Discord servers?
Add your server