【问题标题】:Do anonymous methods passed to actions obtain data by value or reference?传递给操作的匿名方法是通过值还是引用获取数据?
【发布时间】:2016-12-03 07:36:53
【问题描述】:

我正在创建一个匿名方法并将其传递给稍后要调用的操作。我想将一些数字数据 (int) 传递到我的匿名方法中。为了按值传递数据,我需要创建副本吗?还是按值传递数据?

如果我必须创建副本,这就是我认为实现的样子:

private void CreateAction()
{
    int bus = 4;
    CustomObject[] data = new object[16];
    int length = 1500;

    this.doWorkLater = new Action(() =>
    {
        var busCopy = bus;
        var dataCopy = data;
        var lengthCopy = length;

        this.WorkMethod(busCopy, dataCopy, lengthCopy);
    });
}

这(上面的代码)是否需要按值获取lengthbus

在这种情况下,CustomObject[] data我创建的某个类)是通过引用传递还是通过值传递?

【问题讨论】:

标签: c# anonymous-methods


【解决方案1】:

您传递的不是按值复制。

如果您不打算在执行操作之前修改值,那么您无需担心如何传递值。但不,它们不是按值传递的。即使您从另一种方法返回操作和Invoke。这些值保存在编译器生成的类中。不用担心这个

如果您希望数据在执行操作之前发生变化,那么您做错了。您正在使用的方法(将值类型复制到局部变量)应该在操作之外而不是在操作内部完成。至于引用类型(数组),即使复制到局部变量,它复制的引用也会反映复制局部变量的任何变化。

private void CreateAction()
{
    int bus = 4;
    CustomObject[] data = new object[16];
    int length = 1500;

    var busCopy = bus; // a copy of bus
    var dataCopy = data; // reference copy
    var lengthCopy = length; // a copy of length

    this.doWorkLater = new Action(() =>
    {
        this.WorkMethod(busCopy, dataCopy, lengthCopy);
    });

    bus = 10; // No effect on the action
    length = 1700; // No effect on the action

    this.doWorkLater();
}

这看起来毫无意义,但有时您可能需要将一个局部变量复制到另一个局部变量,然后再将其传递给匿名方法。检查此Valid Example,它修复了报告的意外行为!

【讨论】:

  • 复制我刚刚创建的对象似乎很荒谬......不是吗?我认为这有问题。
  • @StevieV 不,这并不荒谬。在某些有效的情况下,您需要这样做。如果您想将其按值传递给匿名方法。检查这个有效的例子:stackoverflow.com/a/38309084/3185569
  • 这不像我需要在循环内关闭。所以你在做什么并没有什么不同。
  • @StevieV 如果您不打算在关闭变量后对其进行变异,则不需要复制。如果方法要在它们被关闭后对其进行变异,并且匿名方法不想观察这些变化,那么您需要创建第二个变量来关闭。跨度>
  • 好的,在那个人的例子中,他正在修改通过后的值。我没有那样做。
【解决方案2】:

闭包通过引用可观察地捕获值* - 请注意,您拥有的代码不能解决一般情况下的问题,如果CreateAction 的整个点是创建一个单独的操作,它将起作用。

private void CreateAction()
{
    int bus = 4;

    this.doWorkLater = new Action(() =>
    {
        var busCopy = bus;

        this.WorkMethod(busCopy);
    });
    // if you change local `bus` before call to `doWorkLater` it will not work:
    bus = 42;
    doWorkLater(); // busCopy is 42. 
}

* 它实际上收集编译器创建的类中的所有变量,并使用对它的引用来访问方法和闭包中的变量。因此,即使值类型看起来也像通过引用传递一样。

【讨论】:

  • 假设我在创建闭包后立即离开该方法,并且 bus 永远不会改变。我会因为bus 不再存在而出现某种错误吗?
  • @StevieV 运行代码并找出答案。你已经写好了。
【解决方案3】:

这可能会帮助您弄清楚发生了什么。

如果你从这个稍微简化的类开始:

public class Example
{
    private void CreateAction()
    {
        int bus = 4;
        object[] data = new object[16];
        int length = 1500;

        Action doWorkLater = () =>
        {
            var busCopy = bus;
            var dataCopy = data;
            var lengthCopy = length;

            this.WorkMethod(busCopy, dataCopy, lengthCopy);
        };

        doWorkLater.Invoke();
    }

    public void WorkMethod(int bus, object[] data, int length)
    {
    }
}

...然后编译器基本上会生成:

public class Example
{
    private void CreateAction()
    {
        Example.GeneratedClass closure = new Example.GeneratedClass();
        closure.parent = this;
        closure.bus = 4;
        closure.data = new object[16];
        closure.length = 1500;

        // ISSUE: method pointer
        IntPtr method = __methodptr(closure.CreateAction);
        new Action((object)closure, method)();
    }

    public void WorkMethod(int bus, object[] data, int length)
    {
    }

    [CompilerGenerated]
    private sealed class GeneratedClass
    {
        public int bus;
        public object[] data;
        public int length;
        public Example parent;

        internal void CreateAction()
        {
            this.parent.WorkMethod(this.bus, this.data, this.length);
        }
    }
}

被困在闭包中的局部变量不再是方法的局部变量,而是成为生成类中的公共字段。

现在你所知道的关于 C# 和类的一切都适用了。

【讨论】:

    猜你喜欢
    • 2012-02-03
    • 2020-04-01
    • 2014-12-26
    • 1970-01-01
    • 2016-12-12
    • 2014-06-22
    • 2011-05-06
    • 2014-07-26
    • 2015-02-24
    相关资源
    最近更新 更多