IEnumerable.Take(0) в File.ReadLines, похоже, не удаляет/закрывает дескриптор файла
У меня есть функция, которая пропускает строки n
кода и берет y
строки из заданного файла с помощью File.ReadLines
с комбинацией Skip
и Take
. Когда я попытаюсь открыть файл, указанный filePath
, в следующий раз:
string[] Lines = File.ReadLines(filePath).Skip(0).Take(0).ToArray();
using (StreamWriter streamWriter = new StreamWriter(filePath))
{
// ...
}
Я получаю исключение File in use by another process
в строке "using
".
Похоже, что IEnumerable.Take(0)
является виновником, так как он возвращает пустой IEnumerable
без перечисления на объект, возвращенный File.ReadLines()
, который, я считаю, не удаляет файл.
Я прав? Должны ли они не перечислять, чтобы избежать таких ошибок? Как это сделать правильно?
Ответы
Ответ 1
Это в основном ошибка в File.ReadLines
, а не Take
. ReadLines
возвращает IEnumerable<T>
, который должен логически быть ленивым, но он с нетерпением открывает файл. Если вы на самом деле не перебираете возвращаемое значение, вам нечего распоряжаться.
Он также нарушается только с повторением. Например, вы должны иметь возможность написать:
var lines = File.ReadLines("text.txt");
var query = from line1 in lines
from line2 in lines
select line1 + line2;
..., который должен давать кросс-произведение строк в файле. Это не из-за нарушения.
File.ReadLines
должно быть реализовано примерно так:
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;
}
}
}
К сожалению, это не: (
Параметры:
- Используйте выше, а не
File.ReadLines
-
Напишите свою собственную реализацию Take
, которая всегда запускает итерацию, например
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;
}
}
}
Ответ 2
Из комментария выше File.ReadLines()
в исходном источнике становится очевидным, что ответственная команда знала об этой "ошибке":
Известные проблемы, которые нельзя изменить, чтобы оставаться совместимыми с 4.0:
- Основной
StreamReader
присваивается авансом для IEnumerable<T>
до GetEnumerator
даже вызвано. Хотя это хорошо в тех исключениях, как DirectoryNotFoundException
и FileNotFoundException
выбрасываются непосредственно File.ReadLines
(что, вероятно, ожидает пользователь), это также означает, что читатель будет просачиваться, если пользователь никогда не будет предвидеть перечислимое (и, следовательно, вызывает Dispose по крайней мере на одном экземпляре IEnumerator<T>
).
Поэтому они хотели, чтобы File.ReadLines()
сразу бросался, когда передавался недопустимый или нечитаемый путь, в отличие от метаданных при перечислении.
Альтернатива проста: не вызывать Take(0)
, или вместо этого не читать файл вообще, если вы действительно не заинтересованы в его содержимом.
Ответ 3
По моему мнению, основная причина: Enumerable.Take
Итератор не удаляет базовый итератор, если count
равен нулю, так как код не вводит цикл foreach
- см. referencesource.
Если один из них изменяет код следующим образом, проблема будет решена:
static IEnumerable<TSource> TakeIterator<TSource>(IEnumerable<TSource> source, int count)
{
foreach (TSource element in source)
{
if (--count < 0) break;
yield return element;
}
}