Сборка мусора в методах урожайности - PullRequest
1 голос
/ 30 января 2009

Скажем, у меня есть такой метод (украден из предыдущего SO-ответа Джона Скита):

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

В этом методе у меня есть HashSet, который используется для хранения видимых ключей. Если я использую этот метод в чем-то вроде этого.

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

Это будет перечислять только первые 2 элемента в списке строк. Но как сборщик мусора собирает хэш-набор seenKeys. Поскольку yield просто приостанавливает выполнение метода, если метод дорогой, как я могу убедиться, что я правильно распорядился?

Ответы [ 2 ]

2 голосов
/ 30 января 2009

Компилятор генерирует скрытый класс для реализации этого кода. У него суперсекретное имя: "d__0`2". Ваши переменные seenKeys и source становятся полями этого класса, гарантируя, что они не смогут собирать мусор, пока не будет собран объект класса.

Класс реализует интерфейс IEnumerator <>, клиентский код, который использует итератор, использует этот интерфейс для вызова метода MoveNext (). Это та ссылка интерфейса, которая поддерживает объект класса. Который поддерживает свои поля живыми. Как только клиентский код завершает цикл foreach, ссылка на интерфейс исчезает, что позволяет ГХ очистить все.

Используйте Ildasm.exe или Reflector, чтобы убедиться в этом. Это даст вам некоторое представление о скрытой стоимости синтаксического сахара. Итераторы недешевы.

1 голос
/ 30 января 2009

Ну, сборщик мусора не собирает его сразу . Это не может, очевидно.

Внутренне, когда вы делаете что-то вроде foreach над своим методом, он вызывает GetEnumerator (), а затем MoveNext () много раз для получения каждой вещи. Перечислители являются одноразовыми, и когда перечислитель удаляется - foreach располагает его для вас в конце цикла - сборщик мусора может свободно очищать любые объекты, находящиеся в вашем итераторе.

Итак, если у вас много дорогостоящих состояний в итераторе, и вы долго его повторяете, то вы, вероятно, захотите либо не использовать yield return, либо сразу оценить все перечисление, вызвав что-то вроде ToArray (), а затем смотреть на это.

РЕДАКТИРОВАТЬ : Итак, в ответ на ваш последний вопрос - как вы можете убедиться, что он утилизируется - вам не нужно делать ничего особенного, если вы используете для этого LINQ или конструкции foreach потому что они сами позаботятся об этом с помощью своей обычной магии. Если вы вручную получаете перечислитель, убедитесь, что вы вызвали Dispose () для него, когда закончите, или поместите его в блок using.

...