Yield return inside usings

感情迁移 提交于 2019-12-20 18:30:04

问题


If I recall correctly that when I used yield inside using SqlConnection blocks I got runtime exceptions.

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

Those problems were solved when I replaced yield by a List where I added items each iteration.

The same problem didn't happen yet to me when inside using StreamReader blocks

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

Is there any explanation why Exceptions happened in the former case and not in the latter? Is this construction advisable?

EDIT To get the error (early disposal) that I did in the past you should call the first method below:

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

The same error can be achieved with other ways of deferring execution, such as System.Linq.Enumerable.Select()


回答1:


See this post for a good explanation of the issues with using and yield. Because you return in enumerator, the using block will already have destroyed the context before anything is accessed. The answers have good solutions, basically, either make the wrapper method an enumerator, or build a list instead.

Also it's usually more practical to have using around the reader, not the connection, and use CommandBehavior.CloseConnection to ensure resources are released when the reader's done. Though it doesn't really matter in your situation, if you ever return a data reader from a method, this will ensure the connection is closed properly when the reader is disposed.

   using(SqlDataReader reader = 
             command.ExecuteReader(CommandBehavior.CloseConnection)) {
        while (reader.Read())
        {
            yield reader[0];
        }
   }



回答2:


The compiler should handle the yield inside the using block correctly in both cases. There's no obvious reason why it should throw an exception.

One thing to be aware of is that the connection will only be disposed once you've completed iterating and/or manually disposed the enumerator object. If you expose this code in a public method then it's possible that stupid or malicious code could keep your connection open for a long time:

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


来源:https://stackoverflow.com/questions/5716350/yield-return-inside-usings

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!