-1

I came across some code that confused me a little and I'd like to know what's going on behind the scenes to make this possible.

The code in question was a unit test using the FakeItEasy library. ReturnsLazily() and Invokes() methods were being used, both of which accept lambda functions as parameters. Both the lambda functions were either referencing/changing a local variable. When these lambdas are invoked is it not possible the local variables they reference have been garbage collected?

I've tried to illustrate what I mean with a simple similar example below:

class Program
{
    static void Main(string[] args)
    {
        var (PrintLocals, ModifyLocals) = GenerateFunctions();

        // Here I am able to print and modify the local variables declared in GenerateFunctions
        PrintLocals();
        ModifyLocals();
        PrintLocals();
        ModifyLocals();
        PrintLocals();
    }

    public static (Action, Action) GenerateFunctions()
    {
        var someObject = new object();
        var someNumber = 1;

        void PrintLocals()
        {
            Console.WriteLine(someObject.GetHashCode());
            Console.WriteLine(someNumber);
        }

        void ModifyLocals()
        {
            someObject = new object();
            someNumber += 1;
        }

        return (PrintLocals, ModifyLocals);
    }
}

The output when run is:

58225482

1

54267293

2

18643596

3

Clearly the local variables I declared in the GenerateFunctions() method still exists somewhere as I am able to modify/print them. Where are these local variables being stored and when can the be GC?

RealR55
  • 1
  • 1
  • The GC running is expensive. If it only has to run once when the application closes - *that* is the ideal scenario. Unless you call it (which you should not do in Production code anyway), there is plenty of reason to wait. – Christopher Aug 14 '21 at 11:51
  • 3
    Also as long as you have a function that *can* print them, they are in use. And the GC will *NEVER* collect what is still in use. – Christopher Aug 14 '21 at 11:51

2 Answers2

1

Local variables “captured” by a lambda aren’t really locals. They are stored in the state machine the compiler creates for the lambda. If you decompile the code you can see the details.

“Lambdas can refer to outer variables. These are the variables that are in scope in the method that defines the lambda expression, or in scope in the type that contains the lambda expression. Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected. “ https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-expressions

David Browne - Microsoft
  • 80,331
  • 6
  • 39
  • 67
1

You do seem to have some massive missconception about what the GC does and does not do.

What it does

If a variable is no longer in use (it has no unbroken chain of strong references to any of the application roots) the GC will collect it eventually. Nobody can say for sure when as it depends on too many factors, but it will happen.

As running the GC is expensive and pauses all threads for a human noticeable time, it will try to avoid running. The ideal case is that it only runs once - when the programm is closed - as then it is the least disruptive and has to do the least work.

Given that you have a function that can still access those variables and that is capable of running, you do still have a unbroken chain of strong references to those variables by definition. Or you are doing memory shenanigans using naked pointers (see following point).

What it does not do

It will not overwrite the memory positions of those variables when it drops them. The compiler makes sure you never forget to initialize a variable before you use it, so that is no problem. But the data still stays in RAM until it is overwritten by someone elses code or the RAM loosing power.

If you want data cleaned from the RAM as soon as it is no longer in use, that goes into the design pattern of SecureString. Which uses the Disposeable pattern to make sure the memory of the String is cleaned up ASAP after use.

If you do not want to block Collection but also want to maybe still access it, look at WeakReference.

Christopher
  • 9,634
  • 2
  • 17
  • 31
  • Thank you for your detailed response and explanation - all makes sense. I'm a junior dev so I'm sure I have many misconceptions about the GC, are there any resources on the topic you'd recommend? – RealR55 Aug 14 '21 at 14:15
  • @RealR55 I think I had a link once, but I can not find it anymore. The other common issue usually revolves around finalize and dispose functions/patterns where I do still have my reference: https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose | Note that the cases where you write a finalizer are very rare - most classes that need them already exist and you just use them. Meanwhile the need to cascade Dispose calls is pretty common. – Christopher Aug 14 '21 at 14:40