Почему эти две функции не возвращают одно и то же значение? - PullRequest
5 голосов
/ 09 ноября 2010

Рассмотрите этот фрагмент кода и попытайтесь угадать, что y1 и y2 оценивают как

static class Extensions
{
    public static Func<T> AsDelegate<T>(this T value)
    {
        return () => value;
    }
}
class Program
{
    static void Main(string[] args)
    {
        new Program();
    }

    Program()
    {
        double x = Math.PI;

        Func<double> ff = x.AsDelegate();
        Func<double> fg = () => x;

        x = -Math.PI;

        double y1 = ff();  // y1 = 3.141..
        double y2 = fg();  // y2 = -3.141..

    }
}

Можно сказать, -Aha- double - это тип значения, поэтому значение, возвращаемое методом расширения копия основной x.Но когда вы изменяете вышеупомянутое на делегатов классов, результаты все еще различны.Пример:

class Foo
{
    public double x;
}
    Program()
    {
        Foo foo = new Foo() { x=1.0 };

        Func<Foo> ff = foo.AsDelegate();
        Func<Foo> fg = () => foo;

        foo = new Foo() { x = -1.0 };

        double y1 = ff().x;    // y1 = 1.0
        double y2 = fg().x;    // y2 = -1.0
    }

Таким образом, две функции должны возвращать два разных экземпляра одного и того же класса.Интересно учитывать, что ff() содержит ссылку на локальную переменную foo, а fg() - нет, и это зависит от того, что находится в области видимости в настоящее время.

Так что же происходит, когда эти два делегатапередаются другим частям кода, которые не видны экземпляру foo?Почему-то вопрос о том, кому принадлежит какая информация (данные) становится все менее и менее понятным, когда методы расширения объединяются с делегатами.

Ответы [ 7 ]

7 голосов
/ 09 ноября 2010

AsDelegate фиксирует переменную value (параметр AsDelegate), а () => x фиксирует переменную x. Поэтому, если вы измените значение x, лямбда-выражение вернет другое значение. Изменение x не меняет value.

См .: Внешняя переменная ловушка

4 голосов
/ 09 ноября 2010

В AsDelegate мы фиксируем аргумент «значение». значение этого аргумента берется как копия значения переменной во время вызова метода и никогда не изменяется - поэтому мы видим исходный объект.

Прямая лямбда-выражение захватывает переменную foo (не значение переменной - самой переменной) - таким образом, мы делаем видим изменения в этом.

По существу, добавление вызова метода изменило захват бедра.

3 голосов
/ 09 ноября 2010

ff захватывает (связывает) значение из x в этой строке:

Func<double> ff = x.AsDelegate();

В отличие от этого fg связывается с переменной x в этой строке:

Func<double> fg = () => x;

Таким образом, когда значение x изменяется, ff не изменяется, но fg изменяется.

3 голосов
/ 09 ноября 2010

В методе расширения AsDelegate используется значение x во время вызова AsDelegate, тогда как лямбда-выражение () => x фиксирует переменную x и, следовательно, значение x берется во времявызывается выражение (а не значение, когда оно было определено).

2 голосов
/ 09 ноября 2010

Вам нужно прочитать о замыканиях в C # .Смотрите также Википедия .

2 голосов
/ 09 ноября 2010

() => x фиксирует значение х.Специальный класс создается компилятором для обработки лямбда-выражений или анонимных делегатов, любая переменная, используемая в лямбда-выражении, захватывается.

Например, если вы запустите следующий код:

    List<Func<int>> list = new List<Func<int>>();

    for (int i = 0; i < 5; i++)
    {
        list.Add(() => i);
    }

    list.ForEach(function => Console.WriteLine(function()));

, вы увидите, что числанапечатаны одинаково.

0 голосов
/ 09 ноября 2010

Первый метод создает новый делегат для данной функции и сохраняет его.Если вы позже перезапишете foo, ваш вновь созданный делегат не будет затронут.

Вторым является выражение lambda , которое поднимает / * захватывает * его контекст, это означаетпеременная foo.Все изменения в переменных, которые были отменены лямбда-выражениями, видны этим лямбда-выражением.

...