Thursday, April 1, 2010

Problems With IEnumerable, Yield, IDisposable and Using

There is a very odd thing happening with IDisposable and IEnumerable and yield and I wanted to highlight it here in case other people are having and issue, it is probably a compiler problem.  I am running .NET CLR 3.5 SP1 with C#.

 

This code doesn’t work.  Specifically, there is no compiler error nor warning that returning the IEnumerble<> of the function will result in the using statement disposing the object after the first iteration of Main() foreach statement.  In other words fetching the first item from A() lazily (only when requested) succeeds and the second request fails because the the HttpWebResponse, Stream, XmlReader will have been disposed by the using.  Below this is working code – using the yield triggers something in the compilation to delay the dispose until the iteration is complete.

 

Non-Working Code

internal Main()
{
    foreach (SyndicationItem syndicationItem in A())
        Console.WriteLine(syndicationItem.Title.Text);
}
internal IEnumerable<SyndicationItem> A()
{
    HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://bellingham.craigslist.org/sss/index.rss");
    using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
    {
        return (B(httpWebResponse));
    }
}

internal IEnumerable<SyndicationItem> B(HttpWebResponse httpWebResponse)
{
    using (Stream responseStream = httpWebResponse.GetResponseStream())
    {
        return (C(responseStream));
    }
}

internal IEnumerable<SyndicationItem> C(Stream stream)
{
    using (XmlReader xmlReader = XmlReader.Create(stream))
    {
        return (D(xmlReader));
    }
}

internal IEnumerable<SyndicationItem> D(XmlReader xmlReader)
{
    SyndicationFeed syndicationFeed = SyndicationFeed.Load(xmlReader);
    return (syndicationFeed.Items);
}

Working Code

This code works fine:

internal IEnumerable<SyndicationItem> A()
{
    HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://bellingham.craigslist.org/sss/index.rss");
    using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
    {
        foreach (SyndicationItem syndicationItem in B(httpWebResponse))
            yield return syndicationItem;
    }
}

internal IEnumerable<SyndicationItem> B(HttpWebResponse httpWebResponse)
{
    using (Stream responseStream = httpWebResponse.GetResponseStream())
    {
        foreach (SyndicationItem syndicationItem in C(responseStream))
            yield return syndicationItem;
    }
}

internal IEnumerable<SyndicationItem> C(Stream stream)
{
    using (XmlReader xmlReader = XmlReader.Create(stream))
    {
        foreach (SyndicationItem syndicationItem in D(xmlReader))
            yield return syndicationItem;
    }
}

internal IEnumerable<SyndicationItem> D(XmlReader xmlReader)
{
    SyndicationFeed syndicationFeed = SyndicationFeed.Load(xmlReader);
    foreach (SyndicationItem syndicationItem in syndicationFeed.Items)
        yield return syndicationItem;
}

 

I am not an expert at the inner working on IEnumerable<> so I am sure someone is going to comment that I am using it wrong and that I deserve to get runtime errors.  That I would believe.  However, I think that if the complier is going to work some IEnumerable<> magic for me, it should warn me of the runtime error, or at the very most perform a non-lazy operation of fetching all the objects in the iteration before disposing.

 

{6230289B-5BEE-409e-932A-2F01FA407A92}

 

1 comment: