简体   繁体   中英

IEnumerable<string> System.ObjectDisposedException

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.)

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.

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 IENumeration s twice a ToList() won't help if it's placed at the end of the Except s

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM