Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# delegates, reference resolution time

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.

like image 691
Ekin Koc Avatar asked Feb 16 '10 22:02

Ekin Koc


2 Answers

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?

like image 177
Eric Lippert Avatar answered Oct 15 '22 17:10

Eric Lippert


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.

like image 26
Mark Byers Avatar answered Oct 15 '22 16:10

Mark Byers