【问题标题】:How to pass a System.Action by reference?如何通过引用传递 System.Action?
【发布时间】:2020-01-01 23:48:50
【问题描述】:

我将 System.Action 作为参数传递给一个方法,该方法执行一些冗长的操作并希望在调用列表中添加更多内容 Action 已传递: p>

class Class1
{
    private System.Action onDoneCallback;
    void StartAsyncOperation(System.Action onDoneCallback)
    {
        this.onDoneCallback = onDoneCallback;
        // do lengthy stuff

    }

    void MuchLater()
    {
         this.onDoneCallBack?.Invoke();
    }
}

class Class2
{
    public System.Action action;

    void DoStuff()
    {
        action += () => print ("a");
        new Class1().StartAsyncOperation(action);
    }

    {
        // ... much later in another place but still before StartAsyncOperation ends
        action += () => print ("b");
    }
}

但是,仅调用在将 Action 作为参数传递之前使用 += 添加的内容。所以,在这个例子中,它只会打印"a",而不是"b"

这让我觉得System.Action 在作为参数传递时是复制的(就像原始类型,例如int 会)。所以,后面做+=的时候,对SomeAsyncOperation里面的action的本地副本没有影响。

我想过用ref 传递System.Action。但是,我需要将其作为成员变量存储在Class1 中,并且我无法将成员变量设置为ref

所以,基本上,问题是:在回调已通过且冗长的操作已在路上但尚未结束之后,如何将更多内容添加到回调的调用列表中。

编辑:

最终替换

new Class1().StartAsyncOperation(action);

new Class1().StartAsyncOperation(() => action?.Invoke());

【问题讨论】:

  • 穴居人解决方案:不要传action,传() => action()
  • 很好,成功了,谢谢!如果您愿意,请将其写为答案,以便我接受。
  • 很高兴。这个问题让我很高兴:我从来不知道你可以像这样“连接”代表。

标签: c# reference delegates action ref


【解决方案1】:

委托类型是不可变的引用类型,就像字符串一样:

s += "\n";

s 现在是对不同对象的引用。如果您将它传递给一个方法,该方法将获得对该对象的引用,而不是对 s 下一个可能引用的任何对象。无论何时调用 lambda 时,s 所指的任何对象都会返回,并将继续返回:

() => s;

这同样适用于a += () => {};a 之后引用了一个不同的对象,但您可以创建一个执行 a 的当前值的 lambda,无论它可能是什么。

因此:

new Class1().StartAsyncOperation(() => action());

在那之后,无论您对action 做什么,您传入的lambda 都会引用action 的当前值。

在家试试:

Action a = () => Console.Write("a");

//  This would print "a" when we call b() at the end
//Action b = a;

//  This prints "a+" when we call b() at the end.
Action b = () => a();

a += () => Console.Write("+");

b();

【讨论】:

  • 这是有效的,因为您在创建 b 时关闭了对 a 的引用,或者通过关系关闭了对 action 的引用,因为您在使用 @ 调用操作时关闭了对 action 的引用987654336@。因此,对参考的修改会在稍后反映出来。
  • 实际上,在代码中对此进行了测试后,我发现可能不仅需要使用() => action(),还需要使用() => action?.Invoke()——如果操作的调用列表为空的话。
  • "什么是委托——除了我们看到的值类型”——委托不是“值类型”。它们是引用类型。它们是不可变的,因此与 不可变 值类型共享一些特性。但是将它们描述为“值类型”是非常错误的。
  • @PeterDuniho 所以 a+= () =>{} 替换了 a?谢谢,我会更新答案。
  • 是的。委托类型就像字符串一样工作。和字符串一样,加法/连接运算符(即+)返回一个全新的实例。而且无论类型是否可变、引用类型或值类型,x += y 这样的表达式中的+= 运算符实际上只是x = x + y 的简写...x 的值是 always 替换为运算符的结果。
【解决方案2】:

一个选项:

class Class2
{
    public List<System.Action> callbacks = new List<System.Action>();

    void DoStuff()
    {

        callbacks.Add(() => print("a"));
        new Class1().StartAsyncOperation(() => {
            forach(var a in callbacks()
            {
                a();
            }
        });
    }

    {
        // ... much later in another place but still before StartAsyncOperation ends
        callbacks.Add(() => print ("b"));
    }
}

您将在列表上获得一个闭包,因此任何更改在回调运行时仍然可用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-12
    • 2014-12-13
    • 2018-06-29
    • 2010-11-02
    • 2015-04-23
    • 2010-10-27
    相关资源
    最近更新 更多