2016-03-04 3 views
5

В методе, возвращающем IEnumerable<>, я открываю и перебираю через ресурс (например, считыватель строк базы данных). По завершении цикла ресурс снова закрывается.Как я могу отменить IEnumerable?

Однако может случиться так, что вызывающий абонент решает не заканчивать перечисление. Это оставляет ресурс открытым.

Пример:

IEnumerable<Foo> Bar() 
{ 
    using (var r = OpenResource()) { 
     while (r.Read()) { 
      yield return r; 
     } 
    } 
} 

// OK - this closes the resource again 
foreach (var foo in Bar()) { 
    Console.WriteLine (foo); 
} 

// Not OK - resource stays open! 
Console.WriteLine (Bar().First()); 

Как бы решить эту проблему? Могу ли я легко отменить перечисление, т. Е. Сказать ему пропустить остаток цикла или удалить его (поместить код очистки в Dispose)?

Я решил вернуть Func<Result, bool>, чтобы пользователь мог вернуть его false, если он сделан с повторением. Точно так же можно использовать какой-то маркер отмены. Но оба подхода кажутся мне громоздкими.

+1

Я искал и не нашел аналогичного вопроса, хотя я уверен, что должен быть один - это довольно простой вопрос imo. – mafu

+5

'break;' должен отменить его, и сгенерированный код должен утилизировать перечислитель. если источник не делает что-то правильно, то это необходимо исправлять. –

+0

@ DanielA.White Я хочу отменить его в области вызывающего, а не внутри самого метода перечисления – mafu

ответ

12

Обычно это IEnumerator<>, который реализует IDisposable, и если вы посмотрите на определение IEnumerator<> вы увидите, что:

public interface IEnumerator<out T> : IDisposable, IEnumerator 

foreach утверждение правильно Dispose() в IEnumerator<>, который получает от IEnumerable<>, так что:

IEnumerable<SomeClass> res = SomeQuery(); 

foreach (SomeClass sc in res) 
{ 
    if (something) 
     break; 
} 

при выходе из foreach каким-либо образом (break, исключение, естественно заканчивая res), следует называть Dispose() из IEnumerator<>. См https://msdn.microsoft.com/en-us/library/aa664754(v=vs.71).aspx для примера того, как foreach реализуется (в try ... finally ... с Dispose() внутри finally)

Обратите внимание, что C# будет производить «правильный» код для using используется внутри yield функции. Смотри, например, здесь: http://goo.gl/Igzmiz

public IEnumerable<Foo> Bar() 
{ 
    using (var r = OpenResource()) 
    { 
     while (r.Read()) 
     { 
      yield return new Foo(); 
     } 
    } 
} 

превращается в то, что

void IDisposable.Dispose() 
{ 
    int num = this.<>1__state; 
    if (num == -3 || num == 1) 
    { 
     try 
     { 
     } 
     finally 
     { 
      this.<>m__Finally1(); 
     } 
    } 
} 

Метод IEnumerator<>Dispose() будет вызывать метод m__Finally1, который будет (IDisposable)this.<r>5__1.Dispose(); (где 5__1 является r вернулся из OpenResource()). m__Finally даже называется, если код просто «выходит» на while (r.Read()):

if (!this.<r>5__1.Read()) 
{ 
    this.<>m__Finally1(); 

и/или, если есть исключение.

catch 
{ 
    this.System.IDisposable.Dispose(); 
Смежные вопросы