Доходность возврата внутри употребления - PullRequest
17 голосов
/ 19 апреля 2011

Если я правильно помню, что когда я использовал yield внутри блоков using SqlConnection, я получал исключения во время выполнения.

using (var connection = new SqlConnection(connectionString))
{
    var command = new SqlCommand(queryString, connection);
    connection.Open();

    SqlDataReader reader = command.ExecuteReader();

    // Call Read before accessing data.
    while (reader.Read())
    {
        yield reader[0];
    }

    // Call Close when done reading.
    reader.Close();
}

Эти проблемы были решены, когда я заменил yield списком, в который я добавлял элементы на каждой итерации.

Та же проблема со мной еще не возникала, когда внутри using StreamReader блоков

using (var streamReader = new StreamReader(fileName))
{
    string line;
    while ((line = streamReader.ReadLine()) != null)
    {
        yield return line;
    }
}

Есть ли какое-либо объяснение, почему исключения произошли в первом случае, а не во втором?Рекомендуется ли эта конструкция?

РЕДАКТИРОВАТЬ Чтобы получить ошибку (раннее удаление), которую я сделал в прошлом, вы должны вызвать первый метод ниже:

IEnumerable<string> Read(string fileName)
{
    using (var streamReader = new StreamReader(fileName))
    {
        return Read(streamReader);
    } // Dispose will be executed before ReadLine() because of deffered execution
}

IEnumerable<string> Read(StreamReader streamReader)
{
    string line;
    while ((line = streamReader.ReadLine()) != null)
    {
        yield return line;
    }
}

та же ошибка может быть достигнута другими способами отсрочки выполнения, такими как System.Linq.Enumerable.Select()

Ответы [ 2 ]

3 голосов
/ 19 апреля 2011

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

Также обычно более практично иметь using вокруг считывателя, а не соединения, и использовать CommandBehavior.CloseConnection чтобы освободить ресурсы, когда читатель закончил.Хотя в вашей ситуации это не имеет особого значения, если вы когда-нибудь вернете считыватель данных из метода, это обеспечит правильное закрытие соединения при утилизации считывателя.

   using(SqlDataReader reader = 
             command.ExecuteReader(CommandBehavior.CloseConnection)) {
        while (reader.Read())
        {
            yield reader[0];
        }
   }
2 голосов
/ 19 апреля 2011

Компилятор должен правильно обрабатывать yield внутри блока using в обоих случаях.Нет очевидной причины, по которой следует выдавать исключение.

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

 var enumerable = YourMethodThatYieldsFromTheDataReader();
 var enumerator = enumerable.GetEnumerator();
 enumerator.MoveNext();
 Thread.Sleep(forever);    // your connection will never be disposed
...