2016-06-09 8 views
36

предположим у меня есть«выход» перечислений, которые не получают «готовой» по вызывающему - то, что происходит

IEnumerable<string> Foo() 
{ 
    try 
    { 

     /// open a network connection, start reading packets 
     while(moredata) 
     { 
      yield return packet; 
     } 
    } 
    finally 
     { 
     // close connection 
     } 
} 

(Или, может быть, я сделал «используя» - то же самое). Что произойдет, если мой абонент отправится

var packet = Foo().First(); 

Я просто остался с просочившимся соединением. Когда наконец вызывается? Или же правильно всегда происходит по волшебству

редактировать с ответом и мыслями

Моим образцом и другим «нормальным» (Еогеаспом, ..) вызовом моделей будут работать хорошо, потому что они распоряжаются в IEnumerable (на самом деле IEnumerator, возвращаемый GetEnumerator). Поэтому я должен иметь вызывающего абонента, который делает что-то напуганное (явно получая перечислитель, а не удаляя его или тому подобное). Я буду иметь их расстреляли

плохого кода

Я нашел вызывающий делает

IEnumerator<T> enumerator = foo().GetEnumerator(); 

изменен

using(IEnumerator<T> enumerator = foo().GetEnumerator()) 
+0

Мое лучшее предположение: возвращает только первый «пакет» (?) –

+2

@MaciejLos Это не отвечает на вопрос, который он задал. – Servy

+1

@ Серви, я знаю. Вот почему я опубликовал комментарий. Как вы можете видеть, я добавил '?' В конце инструкции, потому что я не уверен. Спасибо за ваш комментарий. –

ответ

37

Я просто ушел с просочились связями.

Нет, это не так.

Когда наконец вызывается?

Когда IEnumerator<T> расположен, что First собирается делать после получения первого элемента последовательности (так же, как все должны делать, когда они используют IEnumerator<T>).

Теперь, если кто-то писал:

//note no `using` block on `iterator` 
var iterator = Foo().GetEnumerator(); 
iterator.MoveNext(); 
var first = iterator.Current; 
//note no disposal of iterator 

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

+0

Делает смысл. [Документация MSDN] (https://msdn.microsoft.com/en-us/library/9k7k7cf0.aspx) указывает то же самое. –

27

У вас не будет просочившегося соединения. Объекты итератора, создаваемые yield return, являются IDisposable, а функции LINQ стараются обеспечить надлежащую утилизацию.

Например, First() реализуется следующим образом:

public static TSource First<TSource>(this IEnumerable<TSource> source) { 
    if (source == null) throw Error.ArgumentNull("source"); 
    IList<TSource> list = source as IList<TSource>; 
    if (list != null) { 
     if (list.Count > 0) return list[0]; 
    } 
    else { 
     using (IEnumerator<TSource> e = source.GetEnumerator()) { 
      if (e.MoveNext()) return e.Current; 
     } 
    } 
    throw Error.NoElements(); 
} 

Обратите внимание, как результат source.GetEnumerator() обернут в using. Это обеспечивает вызов Dispose, который, в свою очередь, обеспечивает вызов вашего кода в блоке finally.

То же самое касается итераций foreach цикл: код обеспечивает удаление счетчика независимо от того, завершено ли перечисление или нет.

Единственный случай, когда вы можете попасть в просочившееся соединение, - это когда вы вызываете GetEnumerator самостоятельно и не можете его правильно утилизировать. Однако это ошибка в коде с использованием IEnumerable, а не в самом IEnumerable.

22

Хорошо, этот вопрос может использовать некоторые эмпирические данные.

Использование VS2015 и проект царапанию, я написал следующий код:

private IEnumerable<string> Test() 
{ 
    using (TestClass t = new TestClass()) 
    { 
     try 
     { 
      System.Diagnostics.Debug.Print("1"); 
      yield return "1"; 
      System.Diagnostics.Debug.Print("2"); 
      yield return "2"; 
      System.Diagnostics.Debug.Print("3"); 
      yield return "3"; 
      System.Diagnostics.Debug.Print("4"); 
      yield return "4"; 
     } 
     finally 
     { 
      System.Diagnostics.Debug.Print("Finally"); 
     } 
    } 
} 

private class TestClass : IDisposable 
{ 
    public void Dispose() 
    { 
     System.Diagnostics.Debug.Print("Disposed"); 
    } 
} 

А потом назвал его двумя способами:

foreach (string s in Test()) 
{ 
    System.Diagnostics.Debug.Print(s); 
    if (s == "3") break; 
} 

string f = Test().First(); 

Который производит следующий вывод отладки

1 
1 
2 
2 
3 
3 
Finally 
Disposed 
1 
Finally 
Disposed 

Как мы видим, он выполняет как блок finally, так и Dispose способ.

+4

если у вас есть сомнения, напишите тестовую программу :-) ty – pm100

+0

@ pm100 Чтобы добавить дополнительную информацию: я указал, что я использую компилятор 2015 года, но я подозреваю, что правила определения области видимости в спецификации C# требуют такого поведения. Мое подозрение заключается в том, что завершение перечислителя неявно действует подобно последнему возврату доходности - это просто возврат, и применяются все нормальные правила возврата. – theB

1

Специальной магии нет. Если вы проверите документ на IEnumerator<T>, вы обнаружите, что он наследует от IDisposable. Конструкция foreach, как вы знаете, представляет собой синтаксический сахар, который разлагается компилятором в последовательность операций над перечислителем, и все это завернуто в блок /, вызывая Dispose на объекте перечислителя.

Когда компилятор преобразует метод итератора (метод. Е. Содержащий yield заявления) в осуществление IEnumerable<T>/IEnumerator<T>, он обрабатывает логику try/finally в Dispose методе сгенерированного класса.

Вы можете попытаться использовать ILDASM для анализа кода, созданного в вашем случае. Это будет довольно сложно, но это даст вам эту идею.

 Смежные вопросы

  • Нет связанных вопросов^_^