Note: this is not just some random useless code, this is an attempt to reproduce an issue with lambda expressions and memory leaks in C#.
Examine the following program in C#. It's a console application that simply:
I run this program using JetBrains DotMemory, and I take two memory snapshots: one after the object was initialized, and another after its been collected. I compare the snapshots and get what I expect: one dead object of type Test.
But here's the quandary: I then create a local lambda expression inside the object's constructor and I DO NOT USE IT ANYWHERE. It's just a local constructor variable. I run the same procedure in DotMemory, and suddenly, I get an object of type Test+<>, which survives garbage collection.
See the attached retention path report from DotMemory: The lambda expression has a pointer to the Test+<> object, which is expected. But who has a pointer to the lambda expression, and why is it kept in memory?
Also, this Test+<> object - I assume it is just temporary object to hold the lambda method, and has nothing to do with the original Test object, am I right?
public class Test
{
public Test()
{
// this line causes a leak
Func<object, bool> t = _ => true;
}
public void WriteFirstLine()
{
Console.WriteLine("Object allocated...");
}
public void WriteSecondLine()
{
Console.WriteLine("Object deallocated. Press any button to exit.");
}
}
class Program
{
static void Main(string[] args)
{
var t = new Test();
t.WriteFirstLine();
Console.ReadLine();
t.WriteSecondLine();
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Console.ReadLine();
}
}
In C++11 and later, a lambda expression—often called a lambda—is a convenient way of defining an anonymous function object (a closure) right at the location where it's invoked or passed as an argument to a function.
No, C doesn't have lambda expressions (or any other way to create closures). This is likely so because C is a low-level language that avoids features that might have bad performance and/or make the language or run-time system more complex.
A lambda expression can implement a functional interface by defining an anonymous function that can be passed as an argument to some method.
One of the new features introduced in Modern C++ starting from C++11 is Lambda Expression. It is a convenient way to define an anonymous function object or functor. It is convenient because we can define it locally where we want to call it or pass it to a function as an argument.
If you decompile your code with something (like dotpeek), you will see that compiler generated something like this:
public class Test {
public Test() {
if (Test.ChildGeneratedClass.DelegateInstance != null)
return;
Test.ChildGeneratedClass.DelegateInstance =
Test.ChildGeneratedClass.Instance.DelegateFunc;
}
public void WriteFirstLine() {
Console.WriteLine("Object allocated...");
}
public void WriteSecondLine() {
Console.WriteLine("Object deallocated. Press any button to exit.");
}
[CompilerGenerated]
[Serializable]
private sealed class ChildGeneratedClass {
// this is what's called Test.<c> <>9 in your snapshot
public static readonly Test.ChildGeneratedClass Instance;
// this is Test.<c> <>9__0_0
public static Func<object, bool> DelegateInstance;
static ChildGeneratedClass() {
Test.ChildGeneratedClass.Instance = new Test.ChildGeneratedClass();
}
internal bool DelegateFunc(object _) {
return true;
}
}
}
So it created child class, put your function as a instance method of that class, created singleton instance of that class in a static field and finally created static field with your Func<object,bool
referencing method DelegateFunc
. So no surprise that those static members generated by compiler cannot be collected by GC. Of course those objects are not created for each Test
object you create, only once, so I cannot really call that a "leak".
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