Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEnumerable<string> System.ObjectDisposedException

Tags:

c#

On some machines but not on others I get System.ObjectDisposedException using this class.

class LogComparer
    {
        private string firstFile;
        private string secondFile;
        private IEnumerable<string> inFirstNotInSecond;
        private IEnumerable<string> inSecondNotInFirst;

        public LogComparer(string firstFile, string secondFile)
        {
            if (!File.Exists(firstFile) || !File.Exists(secondFile))
            {
                throw new ArgumentException("Input file location is not valid.");
            }
            this.firstFile = firstFile;
            this.secondFile = secondFile;
            GenerateDiff();
        }

        public string FirstFile
        {
            get
            {
                return firstFile;
            }
        }

        public bool IsEqual
        {
            get
            {
                return inFirstNotInSecond.SequenceEqual(inSecondNotInFirst);
            }
        }

        public string SecondFile
        {
            get
            {
                return secondFile;
            }
        }

        public IEnumerable<string> InFirstNotInSecond
        {
            get
            {
                return inFirstNotInSecond;
            }
        }

        public IEnumerable<string> InSecondNotInFirst
        {
            get
            {
                return inSecondNotInFirst;
            }
        }

        private void GenerateDiff()
        {
            var file1Lines = File.ReadLines(firstFile);
            var file2Lines = File.ReadLines(secondFile);

            inFirstNotInSecond = file1Lines.Except(file2Lines);
            inSecondNotInFirst = file2Lines.Except(file1Lines);
        }
    }
System.ObjectDisposedException: Cannot read from a closed TextReader.
ObjectName: 
   at System.IO.__Error.ReaderClosed()
   at System.IO.StreamReader.ReadLine()
   at System.IO.File.<InternalReadLines>d__0.MoveNext()
   at System.Linq.Enumerable.<ExceptIterator>d__99`1.MoveNext()
   at System.Linq.Enumerable.Any[TSource](IEnumerable`1 source, Func`2 predicate)

After modifying GenerateDiff() to:

private void GenerateDiff()
{
    var file1Lines = File.ReadLines(firstFile).ToList();
    var file2Lines = File.ReadLines(secondFile).ToList();

    inFirstNotInSecond = file1Lines.Except(file2Lines);
    inSecondNotInFirst = file2Lines.Except(file1Lines);
}

I can't reproduce the exception.

Interesting thing is that this is not working:

private void GenerateDiff()
{
    var file1Lines = File.ReadLines(firstFile);
    var file2Lines = File.ReadLines(secondFile);

    inFirstNotInSecond = file1Lines.Except(file2Lines).ToList();
    inSecondNotInFirst = file2Lines.Except(file1Lines).ToList();
}

I'm using an instance of this class diff here for example. No using or Dispose anywhere. No tasks or threads.

if (diff.InSecondNotInFirst.Any(s => !s.Contains("bxsr")))

Could someone please explain the root cause? Thank you.

(Our guess is that this is because of IEnumerable<> implements lazy loading and the garbage collector closes the reader before I want to access InFirstNotInSecond or InSecondNotInFirst. But using GC.Collect() there are still no exception on some machines.)

like image 985
bencemeszaros Avatar asked Mar 20 '17 13:03

bencemeszaros


3 Answers

Using the source code we see that File.ReadLines returns a ReadLinesIterator.

And here you can see they Dispose after enumeration.

That means that the enumeration with File.ReadLines can happen only once. It's better to use File.ReadAllLines which will enumerate first and return a concrete array.

like image 189
Ofir Winegarten Avatar answered Nov 13 '22 18:11

Ofir Winegarten


With the immediate call to ToList() you force ReadLines to execute immediately and read the entire file. Going on, you now are dealing with a List<string> and not an IENumerable anymore.

The reason the second method doesn't work is, that you are, again, creating two IENumerables that are only (and at the same time repeatedly) evaluated when the Except methods are called. The ToList() behind the Except just converts the IENumerable you get from the Except method to a List<string>.

As to why you get a ObjectDisposedException I guess that the TextReader will be disposed after being enumerated and since you are trying to go through the same IENumerations twice a ToList() won't help if it's placed at the end of the Excepts

like image 27
Steffen Winkler Avatar answered Nov 13 '22 16:11

Steffen Winkler


This may require a lot of memory as both files will be loaded:

private void GenerateDiff()
{
    var file1Lines = File.ReadLines(firstFile).ToList();
    var file2Lines = File.ReadLines(secondFile).ToList();

    inFirstNotInSecond = file1Lines.Except(file2Lines);
    inSecondNotInFirst = file2Lines.Except(file1Lines);
}

The same is truth if you use ReadAllLines.

A little less performant solution, but much more memory efficient:

void GenerateDiff()
{
     inFirstNotInSecond = File.ReadLines(firstFile).Except(File.ReadLines(secondFile)).ToList();
     inSecondNotInFirst = File.ReadLines(secondFile).Except(File.ReadLines(firstFile)).ToList();
}

Since you are accessing same files they are likely to be cached, so drawback should be negligible.

P.S.: my answer is assuming deferred execution of Except().

like image 1
Sinatr Avatar answered Nov 13 '22 18:11

Sinatr