Ошибка в методе File.ReadLines (..) .net Framework 4.0 - PullRequest
11 голосов
/ 21 февраля 2010

Этот код:

IEnumerable<string> lines = File.ReadLines("file path");
foreach (var line in lines)
{
    Console.WriteLine(line); 
}
foreach (var line in lines)
{ 
    Console.WriteLine(line); 
} 

выдает ObjectDisposedException : {"Cannot read from a closed TextReader."}, если выполнено второе foreach. Кажется, что объект итератора, возвращенный из File.ReadLines(..), не может быть перечислен более одного раза. Вы должны получить новый объект итератора, вызвав File.ReadLines(..), а затем использовать его для итерации.

Если я заменим File.ReadLines(..) своей версией (параметры не проверены, это всего лишь пример):

public static IEnumerable<string> MyReadLines(string path)
{
    using (var stream = new TextReader(path))
    {
        string line;
        while ((line = stream.ReadLine()) != null)
        {
            yield return line;
        }
    }
}

возможно повторять строки файла более одного раза.

Исследование с использованием .Net Reflector показало, что реализация File.ReadLines(..) вызывает приватный File.InternalReadLines(TextReader reader), который создает фактический итератор. Считыватель, переданный в качестве параметра, используется в методе итератора MoveNext() для получения строк файла и удаляется, когда мы достигаем конца файла. Это означает, что как только MoveNext() вернет false, повторять итерацию во второй раз невозможно, потому что читатель закрыт, и вам нужно получить нового читателя, создав новый итератор с методом ReadLines(..). создается в методе MoveNext() каждый раз, когда мы начинаем новую итерацию.

Это ожидаемое поведение метода File.ReadLines(..)?

Меня беспокоит тот факт, что необходимо вызывать метод каждый раз, прежде чем перечислять результаты. Вам также придется каждый раз вызывать метод, прежде чем выполнять итерацию результатов запроса Linq, использующего метод.

Ответы [ 7 ]

6 голосов
/ 21 декабря 2013

Я знаю, что это старый, но я действительно столкнулся с этим, работая над кодом на компьютере с Windows 7. Вопреки тому, что люди говорили здесь, это на самом деле было ошибкой. Смотрите эту ссылку .

Таким образом, легко исправить это .net framefork. Я думал, что это стоит обновить, так как это был лучший результат поиска.

5 голосов
/ 21 февраля 2010

Я не думаю, что это ошибка, и я не думаю, что это необычно - на самом деле это то, что я ожидал бы от чего-то вроде программы чтения текстовых файлов.IO - дорогостоящая операция, поэтому, как правило, вы хотите сделать все за один проход.

1 голос
/ 21 февраля 2010

Это не ошибка. Но я считаю, что вы можете использовать ReadAllLines (), чтобы делать то, что вы хотите вместо этого. ReadAllLines создает строковый массив и вытягивает все строки в массив, а не просто перечислитель над потоком, как это делает ReadLines.

0 голосов
/ 21 февраля 2010

Это не ошибка. File.ReadLines () использует ленивую оценку и не является идемпотентным . Поэтому перечислять его дважды подряд небезопасно. Помните, что IEnumerable представляет источник данных, который может быть перечислен, он не утверждает, что он может быть перечислен дважды, хотя это может быть неожиданно, поскольку большинство людей привыкли использовать IEnumerable над идемпотентными коллекциями.

Из MSDN :

ReadLines (строка, система) и Методы ReadAllLines (String, System) отличаются следующим образом: при использовании ReadLines, вы можете начать перечисление коллекция строк перед вся коллекция возвращается; когда ты использовать ReadAllLines, вы должны ждать весь массив строк будет возвращен прежде чем вы сможете получить доступ к Поэтому, когда вы работаете с очень большими файлами, ReadLines может быть более эффективным.

Ваши выводы с помощью отражателя верны и проверьте это поведение. Реализация, которую вы предоставили, позволяет избежать этого неожиданного поведения, но все еще использует ленивую оценку.

0 голосов
/ 21 февраля 2010

Полагаю, вы путаете IQueryable с IEnumerable. Да, это правда, что IQueryable можно рассматривать как IEnumerable, но это не совсем одно и то же. IQueryable запрашивает каждый раз, когда он используется, в то время как IEnumerable не имеет такого подразумеваемого повторного использования.

Запрос Linq возвращает IQueryable. ReadLines возвращает IEnumerable.

Здесь есть тонкое различие из-за способа создания Перечислителя. IQueryable создает IEnumerator, когда вы вызываете GetEnumerator () для него (что выполняется foreach автоматически). ReadLines () создает IEnumerator при вызове функции ReadLines (). Таким образом, когда вы повторно используете IQueryable, он создает новый IEnumerator при повторном его использовании, но поскольку ReadLines () создает IEnumerator (а не IQueryable), единственный способ получить новый IEnumerator - это снова вызвать ReadLines () .

Другими словами, вы можете рассчитывать только на повторное использование IQueryable, а не IEnumerator.

EDIT:

Что касается дальнейших размышлений (без каламбура), я думаю, что мой первоначальный ответ был слишком упрощенным. Если IEnumerable нельзя использовать повторно, вы не сможете сделать что-то вроде этого:

List<int> li = new List<int>() {1, 2, 3, 4};

IEnumerable<int> iei = li;

foreach (var i in iei) { Console.WriteLine(i); }
foreach (var i in iei) { Console.WriteLine(i); }

Очевидно, никто не ожидал, что второй foreach потерпит неудачу.

Проблема, как это часто бывает с подобными абстракциями, заключается в том, что не все идеально подходит. Например, потоки, как правило, односторонние, но для использования в сети их необходимо было адаптировать для работы в двух направлениях.

В этом случае IEnumerable изначально предполагалось использовать повторно, но с тех пор он был адаптирован так, чтобы повторное использование не было гарантией или даже ожидалось. Станьте свидетелем взрыва различных библиотек, использующих IEnumerables без возможности повторного использования, таких как библиотека Jeffery Richters PowerThreading.

Я просто не думаю, что мы можем предположить, что IEnumerables больше пригодны для использования во всех случаях.

0 голосов
/ 21 февраля 2010

Я не знаю, можно ли это считать ошибкой или нет, если это по замыслу, но я, конечно, могу сказать две вещи ...

  1. Это должно быть опубликовано в Connect, а не в StackOverflow, хотя они не собираются менять его до выпуска 4.0. И это обычно означает, что они никогда не будут это исправлять.
  2. Дизайн метода, безусловно, ошибочен.

Вы правы, отметив, что возвращение IEnumerable подразумевает, что он должен быть многократно используемым, и он не гарантирует одинаковых результатов, если повторяется дважды. Если бы он вернул IEnumerator, то это была бы другая история.

Так что, в любом случае, я думаю, что это хорошая находка, и я думаю, что API - это паршивое начало. ReadAllLines и ReadAllText предоставляют вам удобный удобный способ получить доступ ко всему файлу, но если вызывающая сторона достаточно заботится о производительности, чтобы использовать lazy enumerable, они не должны в первую очередь передавать такую ​​большую ответственность статическому вспомогательному методу.

0 голосов
/ 21 февраля 2010

Если вам нужен двойной доступ к строкам, вы всегда можете буферизовать их в List<T>

using System.Linq;

List<string> lines = File.ReadLines("file path").ToList(); 
foreach (var line in lines) 
{ 
    Console.WriteLine(line);  
} 
foreach (var line in lines) 
{  
    Console.WriteLine(line);  
} 
...