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?
The readlines method does not close the file, the garbage collector does.
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.
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.
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.
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:
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; } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With