Antes do C # 5, você precisa declarar novamente uma variável dentro do foreach - caso contrário, ela será compartilhada e todos os seus manipuladores usarão a última string:
foreach (string list in lists)
{
string tmp = list;
Button btn = new Button();
btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}
De forma significativa, observe que, a partir do C # 5, isso mudou e, especificamente no caso deforeach
, você não precisa mais fazer isso: o código na questão funcionaria conforme o esperado.
Para mostrar que isso não está funcionando sem essa mudança, considere o seguinte:
string[] names = { "Fred", "Barney", "Betty", "Wilma" };
using (Form form = new Form())
{
foreach (string name in names)
{
Button btn = new Button();
btn.Text = name;
btn.Click += delegate
{
MessageBox.Show(form, name);
};
btn.Dock = DockStyle.Top;
form.Controls.Add(btn);
}
Application.Run(form);
}
Execute o procedimento acima antes de C # 5 e, embora cada botão mostre um nome diferente, clicar nos botões mostra "Wilma" quatro vezes.
Isso ocorre porque a especificação de idioma (ECMA 334 v4, 15.8.4) (antes de C # 5) define:
foreach (V v in x)
embedded-statement
é então expandido para:
{
E e = ((C)(x)).GetEnumerator();
try {
V v;
while (e.MoveNext()) {
v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Observe que a variável v
(que é sua list
) é declarada fora do loop. Portanto, pelas regras das variáveis capturadas, todas as iterações da lista compartilharão o portador da variável capturada.
Do C # 5 em diante, isso mudou: a variável de iteração ( v
) tem o escopo dentro do loop. Não tenho uma referência de especificação, mas basicamente se torna:
{
E e = ((C)(x)).GetEnumerator();
try {
while (e.MoveNext()) {
V v = (V)(T)e.Current;
embedded-statement
}
}
finally {
… // Dispose e
}
}
Cancelar a inscrição novamente; se você deseja cancelar a inscrição de um manipulador anônimo, o truque é capturar o próprio manipulador:
EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;
Da mesma forma, se você quiser um manipulador de eventos apenas uma vez (como Carregar etc):
EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
// ... code
obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;
Isso agora está cancelando automaticamente ;-p