Использование yield для перебора хранилища данных может не закрыть соединение? - PullRequest
15 голосов
/ 06 сентября 2008

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

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

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

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

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

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

Ответы [ 4 ]

11 голосов
/ 07 сентября 2008

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

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

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

2 голосов
/ 06 сентября 2008

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

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

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

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

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;
    }
}
2 голосов
/ 06 сентября 2008

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

0 голосов
/ 06 сентября 2008

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

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

...