C#代理,引用解析时间

7

我有一个关于 .net 委托的简单问题。假设我有下面这样的代码:

    public void Invoke(Action<T> action)
    {
        Invoke(() => action(this.Value));
    }

    public void Invoke(Action action)
    {
        m_TaskQueue.Enqueue(action);
    }

第一个函数包含对 this.Value 的引用。在运行时,当调用带有泛型参数的第一个方法时,它将以某种方式向第二个方法提供 this.Value,但是如何提供呢?我想到了以下几点:
  • 按值传递(结构体) - 传递 this.Value 的当前值,因此如果 m_TaskQueue 在5分钟后执行它,则其值将不会处于最新状态,而是处于首次引用时的状态。
  • 按引用传递(引用类型) - 然后在执行操作期间将引用到 Value 的最新状态,但如果在执行操作之前将 this.Value 更改为另一个引用,则仍将指向旧引用
  • 按名称传递(两者皆可) - 在调用操作时将评估 this.Value。我相信实际实现将持有对 this 的引用,然后在委托的实际执行期间在其中评估 Value,因为没有按名称调用。

我认为它应该是按名称调用风格,但找不到任何文档,所以想知道它是否是一种明确定义的行为。这个类有点像Scala或Erlang中的Actor,因此我需要它是线程安全的。我不希望 Invoke 函数立即取消引用 Value,这将由 m_TaskQueuethis 对象在一个安全的线程中完成。

3个回答

19

让我描述一下我们为此生成的代码来回答你的问题。我会将你那个令人困惑的Invoke方法重新命名;这并不影响理解这里发生了什么。

假设你说

class C<T>
{
  public T Value;
  public void Invoke(Action<T> action) 
  { 
      Frob(() => action(this.Value)); 
  } 
  public void Frob(Action action) 
  {  // whatever
  } 
}

编译器生成的代码就像你实际编写了以下内容一样:
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
  } 
}

这回答你的问题了吗?

8
委托存储的是变量的引用,而不是其值。如果您想保留当前值(假设它是值类型),则需要制作一个本地副本:
public void Invoke(Action<T> action)
{
    var localValue = this.Value;
    Invoke(() => action(localValue));
}

如果它是可变引用类型,您可以制作本地克隆/深度副本。


1
强制性的Eric Lippert插件:http://blogs.msdn.com/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx - Michael Stum

4

关键是要记住作用域是词法作用域,这是编译器负责处理的。因此,它捕获变量而不是它们的值。无论这些值是值类型还是引用类型都是完全不同的问题。

也许更极端的修改委托行为的示例会有所帮助:

var myVariable = "something";
Action a = () => Console.WriteLine(myVariable);
myVariable = "something else entirely"
a();

输出“完全不同的东西”。在这种情况下,无论您如何包装、保存或移动函数,它仍然引用了它所包含的变量。因此,简而言之,当委托实际执行时,所封闭变量的值是重要的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接