I recently ran across an issue where I would like to queue up code to run when a certain condition is met. Now, you could approach this in several ways. However, in this case, what immediately started coming out of my fingers onto the keyboard was to have a Queue<Action>, that is, a queue which stores delegates, or function pointers.
The concept would be that I would enqueue function pointers. Later on when the specified condition was met, I would dequeue the function pointers and execute them. I’m not sure why I gravitated to this particular approach, but for this particular problem, this seemed like a logical conclusion.
For example, you might enqueue arbitrary code like this:
In other words, there is not a function pointer stored in that queue. It contains the arbitrary code from above. It’s important to understand that this code hasn’t run yet, but the pointer to it has been stored.
After an arbitrary amount of time, you could later dequeue these function pointers/delegates/anonymous functions and execute that code with something like this (calling .Invoke() executes the code):
This might seem underwhelming and this is only useful in some scenarios. But hold on, it gets a little more complicated.
What if code inside of your anonymous method used variables from an outer scope. What if that anonymous method uses arguments which were passed into that method or local variables from that method. Later on (it could be days later), what happens when I go to Invoke/execute that anonymous function? That “context” is no longer there – so…. what happens?
This concept is called “closures”. As I understand it, the run-time creates a in-memory class basically to house the code to execute and the context that it needs. So, if you used a local variable in your anonymous method, the run-time gets a copy of it and stores it along with your anonymous method. It does this for any context that you use, inside of that delegate.
To test this, I created some sample code:
You might notice that I even have this setup code executing in it’s own method (RunThoseThings). We know that when we leave that method, it is taken off the stack, and those variables are lost. I wanted to be sure that queued-up code would run when the original context was definitely long-gone. Sure enough, this works as you might expect:
I say works as you might expect, but there is a lot going on in the background. The run-time saw that my arbitrary code was referencing arguments that were in the outer scope and via this “closures” technique, it accounted for it.
This is one of those things that is just a “tool in your toolbox”. It may not have an everyday use, but the one time when you do need it – it’s really the perfect tool for the job!