I have a simple question about .net delegates. Say I have something like this:
public void Invoke(Action<T> action)
{
Invoke(() => action(this.Value));
}
public void Invoke(Action action)
{
m_TaskQueue.Enqueue(action);
}
The first function encloses a reference to this.Value
. During runtime, when the first, method with generic parameter gets called, it will provide this.Value
somehow to the second one, but how? These came into my mind:
- Call by value (struct) - the current value of
this.Value
gets passed, so if the m_TaskQueue
executes it 5 minutes later, the value will not be in its recent state, it will be whatever it was when first referencing.
- Call by reference (reference type) - then the most recent state of
Value
will be referenced during execution of action but if I change this.Value
to another reference before execution of action, it will still be pointing to the old reference
- Call by name (both) - where
this.Value
will be evaluated when the action gets called. I believe the actual implementation would be holding a reference to this
then evaluate Value
on that during actual execution of delegate since there is no call by name.
I assume it would be Call of name style but could not find any documentation so wondering if it is a well-defined behavior. This class is something like an Actor in Scala or Erlang so I need it to be thread safe. I do not want Invoke
function to dereference Value
immediately, that will be done in a safe thread for this
object by m_TaskQueue
.
Let me answer your question by describing what code we actually generate for this. I'll rename your confusingly-named other Invoke method; it's not necessary to understanding what's going on here.
Suppose you said
class C<T>
{
public T Value;
public void Invoke(Action<T> action)
{
Frob(() => action(this.Value));
}
public void Frob(Action action)
{ // whatever
}
}
The compiler generates code as though you had actually written:
class C<T>
{
public T Value;
private class CLOSURE
{
public Action<T> ACTION;
public C<T> THIS;
public void METHOD()
{
this.ACTION(this.THIS.Value);
}
}
public void Invoke(Action<T> action)
{
CLOSURE closure = new CLOSURE();
closure.THIS = this;
closure.ACTION = action;
Frob(new Action(closure.METHOD));
}
public void Frob(Action action)
{ // whatever
}
}
Does that answer your question?
The delegate stores a reference to the variable, not the value of it. If you want to keep the current value then (assuming it is a value type) you need to make a local copy of it:
public void Invoke(Action<T> action)
{
var localValue = this.Value;
Invoke(() => action(localValue));
}
If it is a mutable reference type you could make a local clone / deep copy.
The real key is to remember that scope is lexical; it's something the compiler takes care of. So it captures variables, not their values. Whether those values are value types or reference types is another matter completely.
Maybe a slightly more extreme example of altering the behavior of the delegate will help:
var myVariable = "something";
Action a = () => Console.WriteLine(myVariable);
myVariable = "something else entirely"
a();
prints "something else entirely". In that light, it doesn't really matter how many times you wrap, save, or move the function; it still refers to the variable it enclosed. So, in short, what matters is the value of the enclosed variable when the delegate's actually executed.