2009-01-30 22 views
0

Dire que j'ai une méthode comme celui-ci (volée à partir d'une précédente réponse SO par Jon Skeet):Collecte des ordures dans le rendement Méthodes

public static IEnumerable<TSource> DuplicatesBy<TSource, TKey> 
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) 
{ 
    HashSet<TKey> seenKeys = new HashSet<TKey>(); 
    foreach (TSource element in source) 
    { 
     // Yield it if the key hasn't actually been added - i.e. it 
     // was already in the set 
     if (!seenKeys.Add(keySelector(element))) 
     { 
      yield return element; 
     } 
    } 
} 

Dans cette méthode, j'ai un HashSet qui est utilisé pour les clés qui ont été vu. Si j'utilise cette méthode dans quelque chose comme ça.

List<string> strings = new List<string> { "1", "1", "2", "3" }; 
List<string> somewhatUniques = strings.DuplicatesBy(s => s).Take(2); 

Ceci énumérera seulement les deux premiers éléments de la liste de chaînes. Mais comment le garbage collection collecte-t-il le hashset seenKeys? Étant donné que le rendement interrompt juste l'exécution de la méthode, si la méthode est coûteuse, comment puis-je m'assurer que je dispose des choses correctement?

Répondre

1

Le compilateur génère une classe masquée pour implémenter ce code. Il a un nom super-secret: "d__0`2". Vos seenKeys et les variables source deviennent des champs de cette classe, ce qui garantit qu'ils ne peuvent pas être collectés à moins que l'objet de classe ne soit collecté.

La classe implémente l'interface IEnumerator <>, le code client qui utilise l'itérateur utilise cette interface pour appeler la méthode MoveNext(). C'est cette référence d'interface qui maintient l'objet de classe en vie. Qui garde ses champs en vie. Dès que le code client termine la boucle foreach, la référence de l'interface disparaît, permettant au GC de tout nettoyer.

Utilisez Ildasm.exe ou Reflector pour voir cela par vous-même. Il vous donnera également un aperçu du coût caché du sucre syntactique. Les itérateurs ne sont pas bon marché.

1

Eh bien, la collecte des ordures ne le collecte pas tout de suite. Ça ne peut pas, évidemment.

En interne, lorsque vous faites quelque chose comme un foreach sur votre méthode, il appelle GetEnumerator(), puis MoveNext() dessus beaucoup de fois pour obtenir chaque chose. Les énumérateurs sont jetables, et quand l'énumérateur est éliminé - foreach le met à votre disposition à la fin de la boucle - la collecte des ordures se sent libre de nettoyer les objets qui se trouvent dans votre itérateur. Donc, si vous avez beaucoup d'états coûteux dans votre itérateur et que vous l'itérez pendant longtemps, alors vous ne voulez probablement pas utiliser le rendement de rendement, ou évaluez tout le dénombrement tout de suite en appelant quelque chose. comme ToArray() et ensuite regarder ça. Donc, en réponse à votre dernière question - comment vous pouvez vous assurer qu'il est éliminé - il n'y a rien de spécial que vous devez faire si vous utilisez LINQ ou foreach construit dessus, parce qu'ils prennent s'en occuper eux-mêmes par leur magie habituelle. Si vous obtenez manuellement l'énumérateur, assurez-vous d'appeler Dispose() lorsque vous avez terminé ou placez-le dans un bloc using.

+0

Je n'arrive pas à croire que le cadre permette au hashset de s'asseoir jusqu'à ce que mon appdomain se ferme. Ce n'est pas que mon itérateur reste assis longtemps, c'est un exemple artificiel pour poser la question. –

+0

Désolé, je n'ai pas été clair. Il ne le laisse pas s'asseoir pour toujours; il le laisse s'asseoir jusqu'à ce que l'enquêteur soit parti. – mquander