Использование 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 этой серии объясняет, что компилятор добавляет специальную обработку для окончательных блоков, чтобы запускать только в реальном конце.