Подтвердить что ты не робот

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(), который, я считаю, не удаляет файл.

Я прав? Должны ли они не перечислять, чтобы избежать таких ошибок? Как это сделать правильно?

4b9b3361

Ответ 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;
    }
}