【问题标题】:C# delegates, reference resolution timeC#委托,参考解决时间
【发布时间】:2010-02-16 22:42:09
【问题描述】:

我有一个关于 .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,这将在this 对象的安全线程中由m_TaskQueue 完成。

【问题讨论】:

    标签: c# .net multithreading delegates naming-conventions


    【解决方案1】:

    让我通过描述我们为此实际生成的代码来回答您的问题。我将重命名您命名混乱的其他 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
      } 
    }
    

    这能回答你的问题吗?

    【讨论】:

      【解决方案2】:

      委托存储对变量的引用,而不是变量的值。如果你想保留当前值(假设它是一个值类型),你需要制作它的本地副本:

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

      如果它是可变引用类型,您可以进行本地克隆/深拷贝。

      【讨论】:

      【解决方案3】:

      真正的关键是要记住作用域是词法的;这是编译器负责的事情。所以它捕获变量,而不是它们的。这些值是值类型还是引用类型完全是另一回事。

      也许改变代表行为的稍微极端的例子会有所帮助:

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

      打印“完全不同的东西”。从这个角度来看,包装、保存或移动函数的次数并不重要。它仍然指代它所包含的变量。因此,简而言之,重要的是实际执行委托时封闭变量的值。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-10
        • 2013-01-25
        • 1970-01-01
        • 1970-01-01
        • 2019-04-05
        • 1970-01-01
        相关资源
        最近更新 更多