Использование yield для итерации над datareader может не закрыть соединение?

Вот пример кода для извлечения данных из базы данных с использованием ключевого слова yield, которое я нашел в нескольких местах при поиске в google:

public IEnumerable<object> ExecuteSelect(string commandText)
{
    using (IDbConnection connection = CreateConnection())
    {
        using (IDbCommand cmd = CreateCommand(commandText, connection))
        {
             connection.Open();
             using (IDbDataReader reader = cmd.ExecuteReader())
             {
                while(reader.Read())
                {
                    yield return reader["SomeField"];
                }
             }
             connection.Close();
        }
    }
}

Я правильно понял, что в этом примере кода соединение не будет закрыто, если мы не будем перебирать весь файл данных?

Вот пример, который не закроет соединение, если я правильно понял выход.

foreach(object obj in ExecuteSelect(commandText))
{
  break;
}

Для подключения db, которое может быть не катастрофическим, я предполагаю, что GC в конечном итоге очистит его, но что, если вместо соединения это был более важный ресурс?

Ответ 1

Итератор, который синтезирует компилятор, реализует IDisposable, который запрашивает вызовы при выходе из цикла foreach.

Метод Iterator Dispose() очистит операторы using при раннем выходе.

Пока вы используете итератор в цикле foreach, используя() блок или вызываете метод Dispose() каким-то другим способом, произойдет очистка Iterator.

Ответ 2

Соединение будет закрыто автоматически, так как вы используете его внутри блока "using".

Ответ 3

Из простого теста, который я пробовал, aku прав, dispose вызывается сразу после выхода блока foreach.

@David: Однако стек вызовов сохраняется между вызовами, поэтому соединение не будет закрыто, потому что при следующем вызове мы вернемся к следующей команде после выхода, который является блоком while.

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

Вот простая программа, с которой я пытался проверить поведение...

class Program
{
    static void Main(string[] args)
    {
        foreach (int v in getValues())
        {
            Console.WriteLine(v);
        }
        Console.ReadKey();

        foreach (int v in getValues())
        {
            Console.WriteLine(v);
            break;
        }
        Console.ReadKey();
    }

    public static IEnumerable<int> getValues()
    {
        using (TestDisposable t = new TestDisposable())
        {
            for(int i = 0; i<10; i++)
                yield return t.GetValue();
        }
    }
}

public class TestDisposable : IDisposable
{
    private int value;

    public void Dispose()
    {
        Console.WriteLine("Disposed");
    }

    public int GetValue()
    {
        value += 1;
        return value;
    }
}

Ответ 4

Судя по это техническое объяснение, ваш код будет работать не так, как ожидалось, но прервать второй элемент, потому что соединение уже было закрыто при возврате первого элемента.

@Joel Gauvreau: Да, я должен был прочитать. Часть 3 этой серии объясняет, что компилятор добавляет специальную обработку для окончательных блоков, чтобы запускать только в реальном конце.