Беспокойство о возврате доходности и нарушении от foreach
Есть ли способ переломать из foreach таким образом, что IEnumerable < > знает, что я сделан, и он должен очищаться.
Рассмотрим следующий код:
private static IEnumerable<Person> getPeople()
{
using (SqlConnection sqlConnection = new SqlConnection("..."))
{
try
{
sqlConnection.Open();
using (SqlCommand sqlCommand = new SqlCommand("select id, firstName, lastName from people", sqlConnection))
{
using (SqlDataReader reader = sqlCommand.ExecuteReader())
{
while (reader.Read())
yield return new Person(reader.GetGuid(0), reader.GetString(1), reader.GetString(2));
}
}
}
finally
{
Console.WriteLine("finally disposing of the connection");
if (sqlConnection.State == System.Data.ConnectionState.Open)
sqlConnection.Close();
}
}
}
Если потребитель не отрывается от foreach, то все в порядке, и читатель вернет false, цикл while willend и функция очистит команду и соединение с базой данных. Но что произойдет, если вызывающий абонент перейдет из файла foreach, прежде чем я закончу?
Ответы
Ответ 1
Отличный вопрос. Вам не нужно беспокоиться об этом; компилятор позаботится об этом для вас. В принципе, мы делаем код очистки для блоков finally в специальный метод очистки на сгенерированном итераторе. Когда элемент управления выходит из блока caller foreach, компилятор генерирует код, который вызывает код очистки на итераторе.
Упрощенный пример:
static IEnumerable<int> GetInts()
{
try { yield return 1; yield return 2;}
finally { Cleanup(); }
}
В этом случае ваш вопрос в основном называется "Is Cleanup()?"
foreach(int i in GetInts()) { break; }
Да. Блок итератора генерируется как класс с методом Dispose, который вызывает очистку, а затем цикл foreach генерируется как нечто похожее на:
{
IEnumerator<int> enumtor = GetInts().GetEnumerator();
try
{
while(enumtor.MoveNext())
{
i = enumtor.Current;
break;
}
}
finally
{
enumtor.Dispose();
}
}
Итак, когда происходит разрыв, наконец, берет на себя и вызывается вызывающий.
Смотрите мою недавнюю серию статей, если вы хотите получить дополнительную информацию о некоторых странных угловых случаях, которые мы рассмотрели при разработке этой функции.
http://blogs.msdn.com/ericlippert/archive/tags/Iterators/default.aspx
Ответ 2
Вы можете использовать оператор
yield break;
чтобы вырваться из цикла доходности рано, но ваш код показывает недоразумение, я думаю...
Когда вы используете оператор "using",
using (SqlConnection sqlConnection = new SqlConnection("..."))
{
// other stuff
}
ВЫ АВТОМАТИЧЕСКИ получите попытку, наконец, блок в скомпилированном IL-коде, и блок finnaly вызовет Dispose, а в коде Dispose соединение будет закрыто...
Ответ 3
Посмотрим, получится ли у меня вопрос.
foreach(Person p in getPeople())
{
// break here
}
из-за ключевого слова foreach, Enumerator правильно настроен. Во время удаления Enumerator выполнение getPeople() завершается. Поэтому соединение правильно очищено.