Получают ли анонимные методы, переданные действиям, данные по значению или по ссылке?

Я создаю анонимный метод и передаю его в действие, которое будет вызвано позже. Я хотел бы передать некоторые числовые данные (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);
    });
}

Это (приведенный выше код) необходимо для того, чтобы получить length а также bus по значению?

В этом случае будет CustomObject[] data (какой-то класс, который я создал) будет передан по ссылке или по значению?

3 ответа

Решение

То, что вы передаете, не является копией по стоимости.

Если вы не собираетесь изменять значения перед выполнением действия, вам не нужно беспокоиться о том, как вы передаете значения. Но нет, они не передаются по значению. Даже если вы вернете действие и Invoke это из другого метода. значения сохраняются в классе, сгенерированном компилятором. Не нужно беспокоиться об этом

Если вы ожидаете, что данные изменятся до выполнения действия, то вы делаете это неправильно. Подход, который вы используете (Скопировать Value-Type в локальную переменную), должен выполняться вне действия, а не внутри него. Что касается ссылочного типа (Array), даже если вы копируете его в локальную переменную, ссылка копируется, поэтому отражаются любые изменения в локальной переменной копирования.

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();
}

Это выглядит бессмысленно, но иногда вам может потребоваться скопировать локальную переменную в другую локальную переменную, прежде чем передавать ее анонимному методу. Проверьте этот действительный пример, который исправляет непредвиденное поведение!

Замыкания фиксируют значения, наблюдаемые по ссылке* - обратите внимание, что ваш код не решает проблему в общем случае, даже если весь смысл 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. 
}

* Он фактически собирает все переменные в созданном компилятором классе и использует ссылку на него для доступа к переменным в методе и замыкании. Таким образом, даже типы значений выглядят так, как если бы они передавались по ссылке.

Это может помочь вам понять, что происходит.

Если вы начнете с этого немного упрощенного класса:

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# и классах, применимо.

Другие вопросы по тегам