Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEnumerable.Take(0) on File.ReadLines seems not to dispose/close the File handle

Tags:

c#

I have a function which Skips n lines of code and Takes y lines from a given file using File.ReadLines with Skip and Take combination. When I try to open the file given by filePath the next time:

string[] Lines = File.ReadLines(filePath).Skip(0).Take(0).ToArray(); using (StreamWriter streamWriter = new StreamWriter(filePath)) {     // ... } 

I get a File in use by another process exception on the "using" line.

It looks like IEnumerable.Take(0) is the culprit, since it returns an empty IEnumerable without enumerating on the object returned by File.ReadLines(), which I believe is not disposing the file.

Am I right? Should they not enumerate to avoid this kind of errors? How to do this properly?

like image 637
Titus Avatar asked Aug 25 '16 10:08

Titus


People also ask

Does file Readlines close file?

The readlines method does not close the file, the garbage collector does.

What is IEnumerable<>?

IEnumerable is an interface defining a single method GetEnumerator() that returns an IEnumerator interface. It is the base interface for all non-generic collections that can be enumerated. This works for read-only access to a collection that implements that IEnumerable can be used with a foreach statement.

Why use IEnumerable in c#?

IEnumerable is best to query data from in-memory collections like List, Array etc. IEnumerable doesn't support add or remove items from the list. Using IEnumerable we can find out the no of elements in the collection after iterating the collection. IEnumerable supports deferred execution.

What is IEnumerable string in c#?

IEnumerable in C# is an interface that defines one method, GetEnumerator which returns an IEnumerator interface. This allows readonly access to a collection then a collection that implements IEnumerable can be used with a for-each statement.


1 Answers

This is basically a bug in File.ReadLines, not Take. ReadLines returns an IEnumerable<T>, which should logically be lazy, but it eagerly opens the file. Unless you actually iterate over the return value, you have nothing to dispose.

It's also broken in terms of only iterating once. For example, you should be able to write:

var lines = File.ReadLines("text.txt"); var query = from line1 in lines             from line2 in lines             select line1 + line2; 

... that should give a cross-product of lines in the file. It doesn't, due to the brokenness.

File.ReadLines should be implemented something like this:

public static IEnumerable<string> ReadLines(string filename) {     return ReadLines(() => File.OpenText(filename)); }  private static IEnumerable<string> ReadLines(Func<TextReader> readerProvider) {     using (var reader = readerProvider())     {         string line;         while ((line = reader.ReadLine()) != null)         {             yield return line;         }     } } 

Unfortunately it's not :(

Options:

  • Use the above instead of File.ReadLines
  • Write your own implementation of Take which always starts iterating, e.g.

    public static IEnumerable<T> Take<T>(this IEnumerable<T> source, int count) {     // TODO: Argument validation     using (var iterator = source.GetEnumerator())     {         while (count > 0 && iterator.MoveNext())         {             count--;             yield return iterator.Current;         }     } } 
like image 90
Jon Skeet Avatar answered Sep 25 '22 03:09

Jon Skeet