19

When working with files in C#, I am conditioned to think about freeing the associated resources. Usually this is a using statement, unless its a one liner convenience method like File.ReadAllLines, which will open and close the file for me.

.Net 4.0 has introduced the convenience method File.ReadLines. This returns an IEnumerable and is billed as a more efficient way to process a file - it avoids storing the entire file in memory. To do this I'm assuming there is some deferred execution logic in the enumerator.

Obviously since this method returns an IEnumerable, not and IDisposable, I can't go with my gut reaction of a using statement.

My questions is: With this in mind, are there any gotchas on resource deallocation with this method?

Does calling this method mean that the release of the associated file locks is non-deterministic?

David
  • 1,519
  • 11
  • 11

3 Answers3

21

IEnumerable doesn't inherit from IDisposable because typically, the class that implements it only gives you the promise of being enumerable, it hasn't actually done anything yet that warrants disposal.

However, when you enumerate over it, you first retrieve an IEnumerator by calling the IEnumerable.GetEnumerator method, and typically, the underlying object you get back does implement IDisposable.

The way foreach is implemented is similar to this:

var enumerator = enumerable.GetEnumerator();
try
{
    // enumerate
}
finally
{
    IDisposable disposable = enumerator as IDisposable;
    if (disposable != null)
        disposable.Dispose();
}

This way, if the object does indeed implement IDisposable, it will be disposed of. For File.ReadLines, the file isn't really opened until you start enumerating over it, so the object you get from File.ReadLines doesn't need disposing, but the enumerator you get, does.

As the comments indicate, IEnumerator does not inherit from IDisposable, even though many typical implementations does, whereas the generic IEnumerator<T> does inherit IDisposable.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • Actually, IEnumerator doesn't implement IDisposable. But many *implementations* of IEnumerator do implement IDisposable, and the foreach loop, and the LINQ extension methods, all do an `as` cast to see if the enumerator implements IDisposable, and if so they call Dispose. – Joe White Jul 31 '10 at 18:14
  • 3
    Correction: the non-generic IEnumerator doesn't implement IDisposable, but the generic one (`IEnumerator`) does. – Joe White Jul 31 '10 at 18:15
  • Ah, you're right. Gah, silly mistake, let me edit the answer. – Lasse V. Karlsen Jul 31 '10 at 18:15
5

+1 for Lasse's answer.

Particularly for File.ReadLines where the enumerator calls .MoveNext() the internal TextReader will be disposed of when it encounters an EOF, or if a fault occurs.

private bool MoveNext()
{
    bool flag;
    try
    {
        switch (this.<>1__state)
        {
            case 0:
                this.<>1__state = -1;
                this.<>7__wrap2 = this.reader;
                this.<>1__state = 1;
                this.<line>5__1 = null;
                goto Label_005C;

            case 2:
                this.<>1__state = 1;
                goto Label_005C;

            default:
                goto Label_0078;
        }
    Label_003E:
        this.<>2__current = this.<line>5__1;
        this.<>1__state = 2;
        return true;
    Label_005C:
        if ((this.<line>5__1 = this.reader.ReadLine()) != null)
        {
            goto Label_003E;
        }
        this.<>m__Finally3(); // Disposal at end of file.
    Label_0078:
        flag = false;
    }
    fault
    {
        this.System.IDisposable.Dispose(); // Disposal due to fault.
    }
    return flag;
}
Matthew Abbott
  • 60,571
  • 9
  • 104
  • 129
0

Just to update:

In .net 5 the IEnumerable it returns does implement IDisposable and you may need to call Dispose after casting. But it self disposes if you read until the end of the file.

Erdogan Kurtur
  • 3,630
  • 21
  • 39