Ошибка в Файле. ReadLines (..) метод платформы .NET 4.0

Этот код:

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, который использует метод.

11
задан AndreyAkinshin 23 February 2010 в 11:06
поделиться

6 ответов

Я пытаюсь сделать «пузырь», который может всплывающее окно, когда событие onmouseover уволен и будет оставаться открытым до тех пор, пока мышь наведена на предмет, который был брошен событие onmouseover ИЛИ, если мышь перемещается в пузырь. Мой пузырь нужно будет иметь все манеры html и стиль, включая гиперссылки, изображения и т.д.

Все эти события полностью управляются этим плагином...

http://plugins.jquery.com/project/jqBubblePopup

-121--737574-

Посмотрите на Manticore , его предшественник NESL, и его родного брата ZPL. Все они являются, по меньшей мере, частично функциональными языками с параллельными конструкциями для управлять над всем содержимым структур данных одновременно.

-121--2714626-

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

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

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

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

0
ответ дан 3 December 2019 в 09:20
поделиться

Если вам нужно дважды получить доступ к линиям, вы всегда можете буферизовать их в Список < 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);  
} 
-121--2991052-

Полностью не протестирован:

public class ViewModelBuilderFactory
{
    public IViewModelBuilder GetViewModelBuilder (string docType, IRepository repository)
    {
        switch (docType)
        {
            case "ProgressNotes":
                return new ProgressNotesViewModelBuilder(repository);
            case "Labs":
                return new LabsViewModelBuilder(repository);
            default:
                throw new ArgumentException(
                    string.Format("docType \"{0}\" Invalid", docType);
        }
    }
}

public interface IViewModelBuilder
{
    TreeViewModel GetDocTreeViewModel();
    WorkSpace GetWorkSpace(Patient patient);
}

public class LabsViewModelBuilder : IViewModelBuilder
{
    private IRepository _repository;
    public LabsViewModelBuilder(IRepository repository)
    {
        _repository = repository;
    }

    public TreeViewModel GetDocTreeViewModel()
    {
        return new TreeViewModel(_repository.GetPatientLabs());
    }

    public Workspace GetWorkspace(Patient patient)
    {
        return LabViewModel.NewLabViewModel(patient);
    }
}

public class ProgressNotesViewModelBuilder : IViewModelBuilder
{
    private IRepository _repository;
    public ProgressNotesViewModelBuilder(IRepository repository)
    {
        _repository = repository;
    }

    public TreeViewModel GetDocTreeViewModel()
    {
        return new TreeViewModel(_repository.GetPatientProgressNotes());
    }

    public Workspace GetWorkspace(Patient patient)
    {
        return ProgressNoteViewModel.NewProgressNoteViewModel(patient);
    }
}

Теперь ваш вызывающий код:

ViewModelBuilderFactory factory = new ViewModelBuilderFactory();
IViewModelBuilder modelBuilder = factory.GetViewModelBuilder(docType, repository);
this.DocTreeViewModel = modelBuilder.GetDocTreeViewModel();
Workspace workspace = modelBuilder.GetWorkspace(patient);
this.Workspaces.Add(workspace);
this.SetActiveWorkspace(workspace);

[4 изменения с момента первого сообщения; продолжать видеть ошибки]

[Далее редактировать, отмечая, что вы используете Castle IOC]

В вашей конфигурации Castle xml можно добавить (и я работаю только над неопределенным знанием Castle здесь)

<component id="ProgressNotesViewModelBuilder"
           type="MyNamespace.ProgressNotesViewModelBuilder, MyAssembly">
    <parameters>
        <!-- reference to repository here -->
    </parameters>
</component>
<component id="LabsViewModelBuilder"
           type="MyNamespace.LabsViewModelBuilder, MyAssembly">
    <parameters>
        <!-- reference to repository here -->
    </parameters>
</component>

Тогда вам не нужен ViewModelBuilderFactory, вы можете просто заменить

IViewModelBuilder modelBuilder = factory.GetViewModelBuilder(docType, repository);

на

IViewModelBuilder modelBuilder = (IViewModelBuilder)
    container.Resolve(docType + "ViewModelBuilder");

Теперь вам вообще не нужна инструкция switch.

Однако стоит отметить, что переключатели не являются злом, они просто плохо пахнут и, как и все плохие запахи, должны быть изолированы от всего, что хорошо пахнет; это то, чего должна достичь образец Фабрики.

-121--4028580-

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

1
ответ дан 3 December 2019 в 09:20
поделиться

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

Из MSDN:

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

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

0
ответ дан 3 December 2019 в 09:20
поделиться

Я думаю, вы путаете 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
ответ дан 3 December 2019 в 09:20
поделиться

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

5
ответ дан 3 December 2019 в 09:20
поделиться

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

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);  
} 
0
ответ дан 3 December 2019 в 09:20
поделиться
Другие вопросы по тегам:

Похожие вопросы: