I was reading https://blog.jetbrains.com/dotnet/2019/01/23/c-classes-memory-snapshots/ and refering to using "method group instead of a lambda" it correctly states:
We want to know if this approach will generate memory traffic. A quick look at the decompiled code says that yes, unfortunately, it will.
But, unfortunately, they do not explain, why.
I created my own example in https://sharplab.io/:
using System;
using System.Collections.Generic;
using System.Linq;
public class ClassWithAllocation {
private string selector(string y){
return y + this.GetHashCode(); //random example to demonstrate use of `this`
}
public void Method() {
var source= new List<string>();
source.Select(selector); //compiled to: Enumerable.Select(source, new Func<string, string>(selector));
}
}
As noted in the comment, selector
is compiled to create a new Func
on every call.
When lambdas without closures are used, C# caches a Func
instance that can be used for every call. (And: with closures this is not possible, because a different context must be "preserved" for every call.)
But why can't the above code compile to something like the following?
public class ClassWithoutAllocation {
private readonly Func<string,string> _selector;
public ClassWithoutAllocation(){
_selector = new Func<string, string>(selector);
}
private string selector(string y){
return y + this.GetHashCode(); //random example to demonstrate use of `this`
}
public void Method() {
var source= new List<string>();
source.Select(_selector); //compiled to: Enumerable.Select(source, _selector);
}
}
SharpLab shows the code is basically compiled as is.
Follow up question: It being what it is, is the above pattern (storing a Func
to use instead of a method group) a good pattern for avoiding heap allocations?
EDIT
This question was closed as a supposed duplicate of Why are there memory allocations when calling a func. But, as @JonSkeet says in his answer:
Unfortunately, the C# 5 specification currently requires that to create a new delegate instance each time it's run.
All of this makes me sad, and in the latest edition of the ECMA C# standard, the compiler will be permitted to cache the result of method group conversions. I don't know when/whether it will do so though.
So, I'm still wondering why would the compiler not cache a reference as in my example above (which is simpler than the one in the linked question)? What are the benefits of not doing so? What are the drawbacks of doing it?