2010-07-02 2 views
21

Consultez le code suivant:Utilisation de la variable itératrice de la boucle foreach dans une expression lambda - pourquoi échoue?

public class MyClass 
{ 
    public delegate string PrintHelloType(string greeting); 


    public void Execute() 
    { 

     Type[] types = new Type[] { typeof(string), typeof(float), typeof(int)}; 
     List<PrintHelloType> helloMethods = new List<PrintHelloType>(); 

     foreach (var type in types) 
     { 
      var sayHello = 
       new PrintHelloType(greeting => SayGreetingToType(type, greeting)); 
      helloMethods.Add(sayHello); 
     } 

     foreach (var helloMethod in helloMethods) 
     { 
      Console.WriteLine(helloMethod("Hi")); 
     } 

    } 

    public string SayGreetingToType(Type type, string greetingText) 
    { 
     return greetingText + " " + type.Name; 
    } 

... 

} 

Après avoir appelé myClass.Execute(), le code imprime la réponse inattendue suivante:

 
Hi Int32 
Hi Int32 
Hi Int32 

De toute évidence, je me attends "Hi String", "Hi Single", "Hi Int32", mais apparemment ce n'est pas Cas. Pourquoi le dernier élément du tableau itéré est utilisé dans toutes les 3 méthodes au lieu de la appropriée?

Comment réécrire le code pour atteindre l'objectif souhaité?

+0

Je n'ai même pas lu la question, mais à partir du titre, je sais que la réponse est: http://lorgonblog.spaces.live.com/blog/cns!701679AD17B6D310!689.entry – Brian

+0

La question des variables capturées quotidiennement dresse sa tête laide. – Marc

Répondre

28

Bienvenue dans le monde des fermetures et des variables capturées :)

Eric Lippert a une explication approfondie de ce comportement:

essentiellement, c'est la variable de boucle qui est capturée, pas sa valeur. obtenir ce que vous pensez que vous devriez obtenir, faites ceci:

foreach (var type in types) 
{ 
    var newType = type; 
    var sayHello = 
      new PrintHelloType(greeting => SayGreetingToType(newType, greeting)); 
    helloMethods.Add(sayHello); 
} 
+4

La balise @Eric Lippert a été allumée. –

+3

Il n'y a pas d'autre dieu qu'Anders, et Eric est son prophète :) – SWeko

+0

Je pourrais ajouter que cela peut surprendre même ceux qui sont bien au courant des fermetures - Lua, et probablement d'autres langues, ont le 'type' lexical à l'intérieur de la boucle supports. Donc, dans Lua, vous capturez toujours la variable, mais c'est une nouvelle variable à chaque itération. C'est quelque chose que lorsque vous programmez en Lua, vous utilisez tout le temps - mais dans mes années de programmation en C#, je n'ai pas encore écrit une méthode qui a bénéficié de son champ d'application 'type'-is-outside-of-the-brackets . Est-ce que quelqu'un? – Mania

3

Vous pouvez corriger en introduisant variable supplémentaire:

... 
foreach (var type in types) 
     { 
      var t = type; 
      var sayHello = new PrintHelloType(greeting => SayGreetingToType(t, greeting)); 
      helloMethods.Add(sayHello); 
     } 
.... 
5

Comme une brève explication qui fait allusion aux messages de blog qui SWeko référencés, un lambda capture la variable , pas la valeur . Dans une boucle foreach, la variable n'est pas unique à chaque itération, la même variable est utilisée pour la durée de la boucle (ceci est plus évident lorsque vous voyez l'expansion que le compilateur effectue sur foreach au moment de la compilation). Par conséquent, vous avez capturé la même variable à chaque itération et la variable (à la dernière itération) fait référence au dernier élément de votre ensemble.

Mise à jour: Dans les versions les plus récentes de la langue (à partir de C# 5), la variable de boucle est considérée comme nouvelle à chaque itération, en fermant de manière plus qu'elle ne produit pas le même problème comme dans les anciennes versions (C# 4 et avant).

Questions connexes