2014-01-21 3 views
1

Код:Подписавшись на событие в затворе, а переборе результатов сбора в последнем пункте подписавшись на событие

using System; 
using System.Collections.Generic; 

namespace so { 

    public abstract class Feature { 
     public void doIt() { 
     Console.WriteLine(GetType().FullName); 
     } 
    } 

    class A : Feature { } 
    class B : Feature { } 
    class C : Feature { } 

    public class SSCCE { 

     event EventHandler Click; 

     static void Main(string[] args) { 
     SSCCE sscce = new SSCCE(); 
     List<Feature> features = new List<Feature>(); 
     features.Add(new A()); 
     features.Add(new B()); 
     features.Add(new C()); 
     foreach (Feature feature in features) { 
      sscce.Click += (object sender, EventArgs e) => { feature.doIt(); }; 
     } 
     sscce.Click.Invoke(null, null); 
     } 
    } 
} 

Ожидаемый результат:

so.A 
so.B 
so.C 

Обнаруженное:

so.C 
so.C 
so.C 

в Яве final ключевое слово перед Feature в foreach Петля допускает значение feature, которое должно использоваться в корпусе действия лямбда, до .doIt().

Какой хороший синтаксис в C#?

+0

Какую версию C# вы используете? –

+0

Я использую Microsoft Visual C# 2010, где компилятор? Что это за имя? – Aerospace

+0

У Jon Skeet есть полезная страница. Короче говоря, вы, вероятно, используете C# 4.0 или ранее: http://csharpindepth.com/Articles/Chapter1/Versions.aspx Это исправление не было введено до тех пор, пока C# 5. –

ответ

5

Последняя функция захвачена вашей лямбдой (ее закрытием). Вы должны создать локальную переменную для захвата функции на каждой итерации:

foreach (Feature feature in features) { 
    Feature current = feature; 
    sscce.Click += (object sender, EventArgs e) => { current.doIt(); }; 
} 

Я предлагаю вам прочитать Closing over the loop variable статью в блоге Эрика Липперта.

ПРИМЕЧАНИЕ: Это было зафиксировано в последней версии C#


Чтобы понять, что происходит, давайте посмотрим, что код генерируется в вашем случае (до C# 5). Таким образом, ваш лямбда использует локальную переменную, это не достаточно, чтобы сгенерировать метод - компилятор генерирует частный класс, который улавливает локальные переменные, используемые в лямбда:

private sealed class AnonymousClass 
{ 
    public Feature feature; 

    public void AnonymousMethod(object sender, EventArgs e) 
    { 
     this.feature.doIt(); 
    } 
} 

И ваш код изменен так, что он использует экземпляр этого AnonymousClass и выписывает это AnonymousMethod чтобы Нажмите событие:

using(var enumerator = ((IEnumerable<Feature>)features).GetEnumerator()) 
{ 
    AnonymousClass x = new AnonymousClass(); 

    while(enumerator.MoveNext()) 
    { 
    x.feature = (Feature)enumerator.Current; 
    sscce.Click += new EventHandler(x.AnonymousMethod); 
    } 
} 

Как вы можете видеть, вы подписались AnonymousMethod того же экземпляра AnonymousClass несколько раз. И этот экземпляр будет иметь функцию, равную последней назначенной функции. Теперь то, что изменяется при копировании текущей функции в локальной переменной:

using(var enumerator = ((IEnumerable<Feature>)features).GetEnumerator()) 
{ 
    while(enumerator.MoveNext()) 
    { 
    AnonymousClass x = new AnonymousClass(); 
    x.current = (Feature)enumerator.Current; // field has local variable name 
    sscce.Click += new EventHandler(x.AnonymousMethod); 
    } 
} 

В этом случае AnonymousClass экземпляр, созданный на каждой итерации, таким образом, AnonymousMethods различных экземпляров класса (каждый с собственной функцией захваченном) будет обрабатывать событие Click. Почему код отличается - потому что, как говорит Эрик, закрытие (т. Е. Анонимный класс) закрывается над переменными. Чтобы быть закрытым по локальной переменной в теле цикла, во втором случае экземпляр анонимного класса должен быть создан внутри цикла.

+0

@GrantWinney yep, уже добавлял эту ссылку:) –

+1

А, удалил! Рад, что они исправили это. Вы никогда не догадались, что это происходит под обложками. –

+0

Возможно, вы можете добавить ссылку на примечания к версии Microsoft, объясняющие эту ошибку? – Aerospace

Смежные вопросы