101 votes

Accès à fermeture modifiée (2)

C'est une extension de la question de l'Accès à l'Modifié Fermeture. Je veux juste vérifier si celui-ci est en fait assez sûr pour une utilisation en production.

List<string> lists = new List<string>();
//Code to retrieve lists from DB	
foreach (string list in lists)
{
	Button btn = new Button();
	btn.Click += new EventHandler(delegate { MessageBox.Show(list); });
}

J'ai seulement exécuter le dessus après chaque démarrage. Pour l'instant, il semble fonctionner très bien. Comme Jon l'a mentionné au sujet de la contre-intuitif résultat dans certains cas. Donc que dois-je regarder? Ça va être ok si la liste est gérée par plus d'une fois?

159voto

Marc Gravell Points 482669

Avant C# 5, vous avez besoin de re-déclarer une variable à l'intérieur de la boucle foreach - sinon, il est partagé, et tous vos gestionnaires d'utiliser la dernière chaîne:

foreach (string list in lists)
{
    string tmp = list;
    Button btn = new Button();
    btn.Click += new EventHandler(delegate { MessageBox.Show(tmp); });
}

De manière significative, note qu'à partir de C# à partir de 5, cela a changé, et spécifiquement dans le cas de l' foreach, vous n'avez pas besoin de faire cela une fois de plus: le code en question ne fonctionnent pas comme prévu.

Pour montrer cela ne fonctionne pas sans ce changement, considérez les points suivants:

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

Exécuter le dessus avant C# 5, et bien que chaque bouton indique un nom différent, en cliquant sur les boutons de la montre "Wilma" quatre fois.

C'est parce que la langue spec (ECMA 334 v4, 15.8.4) (avant C# 5) définit:

foreach (V v in x) embedded-statement est ensuite étendu à:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        V v;
         while (e.MoveNext()) {
            v = (V)(T)e.Current;
             embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

Notez que la variable v (ce qui est ton list) est déclarée en dehors de la boucle. Par les règles de la capture de variables, toutes les itérations de la liste de partager la capture de variable titulaire.

À partir de C# à partir de 5, c'est changé: la variable d'itération (v) est étendue à l'intérieur de la boucle. Je n'ai pas de spécification de référence, mais fondamentalement, ça devient:

{
    E e = ((C)(x)).GetEnumerator();
    try {
        while (e.MoveNext()) {
            V v = (V)(T)e.Current;
            embedded-statement
        }
    }
    finally {
        … // Dispose e
    }
}

Re désabonnement; si vous souhaitez vous désabonner d'un anonyme du gestionnaire, l'astuce est de capturer le gestionnaire lui-même:

EventHandler foo = delegate {...code...};
obj.SomeEvent += foo;
...
obj.SomeEvent -= foo;

De même, si vous voulez un seul gestionnaire d'événements (comme la Charge, etc):

EventHandler bar = null; // necessary for "definite assignment"
bar = delegate {
  // ... code
  obj.SomeEvent -= bar;
};
obj.SomeEvent += bar;

C'est maintenant l'auto-désabonnement ;-p

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X