2009-04-12 6 views
3

Je suis curieux de connaître l'implémentation réelle de .NET et la décision qui la sous-tend.Comment la valeur capturée dans les méthodes anonymes est implémentée dans .NET

Par exemple, en Java, toutes les valeurs capturées utilisées dans une classe anonyme doivent être finales. Cette exigence semble être supprimée dans .NET.

De même, existe-t-il une différence dans la mise en œuvre des valeurs capturées pour les types de valeur par opposition aux types de référence?

Merci

Répondre

8

La meilleure façon de savoir comment il est mis en œuvre est de l'essayer. Écrivez du code qui utilise une variable capturée, compilez-la, puis regardez-la en Reflector. Notez que c'est la variable qui est capturée, pas la valeur . C'est l'une des grandes différences entre Java et C# dans ce domaine.

L'idée de base est que chaque niveau de portée contenant au moins une variable capturée donne une nouvelle classe avec des champs pour les variables qui ont été capturées. S'il y a plus d'un niveau, alors une portée interne a aussi un champ pour la prochaine portée, et ainsi de suite. Les variables locales authentiques sur la pile finissent par être des références aux instances des classes autogénérées.

Voici un exemple:

using System; 
using System.Collections.Generic; 

class Program 
{ 
    static void Main() 
    { 
     List<Action> actions = new List<Action>(); 

     for (int i=0; i < 5; i++) 
     { 
      int copyOfI = i; 

      for (int j=0; j < 5; j++) 
      { 
       int copyOfJ = j; 

       actions.Add(delegate 
       { 
        Console.WriteLine("{0} {1}", copyOfI, copyOfJ); 
       }); 
      } 
     } 

     foreach (Action action in actions) 
     { 
      action(); 
     }   
    } 
} 

(Vous obtenez des résultats différents si vous ne prenez pas une copie bien sûr - expérience!) Ceci est compilé en code comme ceci:

using System; 
using System.Collections.Generic; 

class Program 
{ 
    static void Main() 
    { 
     List<Action> actions = new List<Action>(); 

     for (int i=0; i < 5; i++) 
     { 
      OuterScope outer = new OuterScope(); 
      outer.copyOfI = i; 

      for (int j=0; j < 5; j++) 
      { 
       InnerScope inner = new InnerScope(); 
       inner.outer = outer; 
       inner.copyOfJ = j; 

       actions.Add(inner.Action); 
      } 
     } 

     foreach (Action action in actions) 
     { 
      action(); 
     }   
    } 

    class OuterScope 
    { 
     public int copyOfI; 
    } 

    class InnerScope 
    { 
     public int copyOfJ; 
     public OuterScope outer; 

     public void Action() 
     { 
      Console.WriteLine("{0} {1}", outer.copyOfI, copyOfJ); 
     } 
    } 
} 

Chaque référence à la variable capturée finit par passer par l'instance de la classe générée, donc ce n'est pas seulement une copie unique. (Bon, dans ce cas, rien d'autre dans le code n'utilise les variables capturées, mais vous pouvez facilement imaginer qu'il le pourrait.) Notez que pour une itération de la boucle externe, les cinq nouvelles instances partagent toutes une instance de OuterScope. Vous pouvez essayer de jouer avec du code supplémentaire dans le délégué pour voir comment cela affecte les choses - si le délégué change copyofI, cette modification apparaîtra dans le prochain délégué; les modifications à copyOfJ ne seront pas visibles car le délégué suivant utilisera une instance distincte de InnerScope.

+0

Salut, très belle explication. Vous pouvez également montrer ce que le compilateur produirait sans copier la variable locale. –