Выход с ресурсом IDisposable

Есть ли правильный способ получить доступ к одноразовому ресурсу? Возвращаемые объекты являются IDisposable, но элемент, через который выполняется итерация, является IDisposable.

Вот пример:

public static IEnumerable<T> Fetch(IEnumerable<Guid> ids)
{
    using (var client = new CouchbaseClient())
    {
        yield return ids.Select(s => s.ToString());
    }
}

Прямо сейчас вызов этого не приведет к удалению полученного ресурса using. Я знаю, что могу просто перейти в ToList и вернуть все сразу, но есть ли способ справиться с этим «правильно», или мне нужно следить за ресурсом IDisposable и удалять его вручную, когда я закончу?


person Jess    schedule 22.11.2012    source источник
comment
Разве это не ответственность того, кто собирается перебирать ресурс, чтобы избавиться, когда это будет сделано?   -  person Arthur Nunes    schedule 22.11.2012
comment
stackoverflow.com/q/2274664/34397   -  person SLaks    schedule 22.11.2012
comment
@ArthurNunes Я подозреваю, что да, но мне просто любопытно, предлагает ли язык что-нибудь для этого или мне придется делать это вручную (вероятно, сделав содержащий класс IDisposable и переместив туда логику удаления).   -  person Jess    schedule 22.11.2012
comment
@SLaks Очень интересный вопрос; рад, что прочитал это, но не уверен, насколько это актуально для этого вопроса. Был ли там обсуждаемый комментарий или подпункт, который я пропустил? Ясно, что помеченный ответ дает using, и никто не называет его плохим, но никто не объясняет, что это нормально (или почему это нормально), насколько я могу судить.   -  person Servy    schedule 22.11.2012
comment
Не могли бы вы исправить свой код, чтобы он компилировался? Прямо сейчас вы получаете коллекцию, поэтому тип возвращаемого значения будет IEnumerable<IEnumerable<string>>. Я полагаю, это не то, что вы имели в виду.   -  person svick    schedule 22.11.2012
comment
См. комментарии на stackoverflow.com/a/2274773/34397. yieldВыбирать из using - плохая идея, потому что вызывающие абоненты, которые не Dispose() пересчитывают счетчик, запутают вас.   -  person SLaks    schedule 22.11.2012
comment
@svick Я не это имел в виду, но это был просто пример, не имеющий отношения к вопросу.   -  person Jess    schedule 22.11.2012


Ответы (1)


Короче говоря, вам не нужно об этом беспокоиться, пока вызывающая сторона метода правильно обрабатывает объекты IEnumerator.

IEnumerator реализует IDisposable, и логика, используемая при создании блоков итератора, на самом деле достаточно умна, чтобы выполнять все невыполненные блоки finally при их удалении. В результате вызова using создается блок finally, в котором размещается ресурс IDisposable.

Таким образом, пока объекты IEnumerator, созданные из этого IEnumerable, либо полностью повторяются (в этом случае последний вызов MoveNext достигнет конца блока using и избавится от ресурса), либо будут удалены, клиент IDisposable будет удален.

Обратите внимание: если вы обеспокоены тем, что пользователь вашего кода может неправильно обрабатывать объекты IEnumerator, лучше всего не использовать блок итератора с ленивой оценкой. Если вы хотите убедиться, что даже если вызывающий объект не "играет хорошо", то с нетерпением оцените метод (т.е. возьмите код, который у вас есть, выгрузите результаты в список, а затем верните этот список). Если последствия неутилизации ресурса в первую очередь или полностью связаны с производительностью (неосвобождение некоторой памяти в течение некоторого времени, сохранение открытого соединения и т. д.), то это может не вызывать беспокойства, но если постоянное удержание блокировки является серьезная проблема (например, заблокированный ресурс, который может привести к взаимоблокировкам, если его не освободить), то преимущество ленивых вычислений может не стоить того.

person Servy    schedule 22.11.2012
comment
Вы правы, я неправильно понял, как yield обрабатывает using, я не понимал, что он будет открывать его каждый раз, а это совсем не то, чего я хочу, но это отвечает на мой вопрос. Я просто решу проблему, создав экземпляр моего базового класса, когда он мне понадобится. - person Jess; 22.11.2012
comment
Блоки итераторов @DmitryStarosta не были представлены до .NET 2.0, поэтому вопрос даже не относится к более ранним версиям. - person Servy; 23.11.2012
comment
@AndrewKoester Он не открывает его каждый раз; он открывает его один раз, когда вы запрашиваете первый элемент, и закрывает его, когда вы либо запрашиваете последний элемент, либо избавляетесь от IEnumerator. Как я уже сказал, если абсолютно на 100% необходимо, чтобы ресурс был утилизирован, не делайте этого. Если это в целом лучше, но мир не наступит, если код, использующий это, не будет хорошо работать, тогда все в порядке. - person Servy; 23.11.2012