2010-11-09 2 views
5

Рассмотрим этот фрагмент кода и попытаться угадать, что 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- двойной тип значения и поэтому значение, возвращенное методом расширения является копия из основной 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? Каким-то образом вопрос о том, кто владеет какой информацией (данными) становится все менее и более ясным, когда методы расширения объединяются с делегатами.

ответ

3

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

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

В противоположность этому, fg связывается с переменнойx на этой линии:

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

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

+0

Поклонись этому ответу из-за краткости + прозрачности + формата. – ja72

+0

Meh ... Это a: не привязывает к * значению * ничего (кроме компилятора, генерирует экземпляр класса захвата) и b: не привязывает строго к «x» * вообще * - более четкое да, но я думаю, что, возможно, немного обманчиво из-за этой простоты. (простота хороша) –

+0

@Marc - это подбросить. Я решил сосредоточиться на том, как он кажется пользователю, а не погружаться в способ его реализации. – Bevan

2

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

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

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

Итак, когда функция() называется, она использует любое значение i в то время (вне цикла)? Я думал, что объем 'i' заканчивается, когда цикл заканчивается ... – ja72

7

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

См: Outer Variable Trap

+0

Строго говоря, он фиксирует значение * аргумента *; значение которого первоначально является значением x и никогда не изменяется. Тонкое различие ... –

3

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

4

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

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

В основном добавление вызова метода изменило захват бедра.

0

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

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