Беспокойство о возврате доходности и нарушении от 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() завершается. Поэтому соединение правильно очищено.