Erro no método File.ReadLines (..) do .net framework 4.0

Este código:

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

lança um ObjectDisposedException : {"Cannot read from a closed TextReader."} se o segundo foreach for executado. Parece que o object iterador retornado de File.ReadLines(..) não pode ser enumerado mais de uma vez. Você precisa obter um novo object iterador chamando File.ReadLines(..) e, em seguida, usá-lo para iterar.

Se eu replace File.ReadLines(..) pela minha versão (os parâmetros não são verificados, é apenas um exemplo):

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

É possível iterar mais de uma vez as linhas do arquivo.

Uma investigação usando o .Net Reflector mostrou que a implementação do File.ReadLines(..) chama um File.InternalReadLines(TextReader reader) privado que cria o iterador real. O leitor passado como um parâmetro é usado no método MoveNext() do iterador para obter as linhas do arquivo e é descartado quando chegarmos ao final do arquivo. Isso significa que, uma vez que MoveNext() retorna false, não há como iterar uma segunda vez porque o leitor é fechado e você precisa obter um novo leitor criando um novo iterador com o método ReadLines(..) Na minha versão, um novo O leitor é criado no método MoveNext() toda vez que iniciamos uma nova iteração.

Esse é o comportamento esperado do método File.ReadLines(..) ?

Eu acho preocupante o fato de que é necessário chamar o método a cada vez antes de enumerar os resultados. Você também teria que chamar o método a cada vez antes de repetir os resultados de uma consulta Linq que usa o método.

Eu sei que isso é antigo, mas na verdade eu só corri para isso enquanto trabalhava em algum código em uma máquina com Windows 7. Ao contrário do que as pessoas estavam dizendo aqui, isso realmente era um erro. Veja este link .

Portanto, a solução mais fácil é atualizar seu .net framefork. Eu achei que isso valeria a pena atualizar, já que esse era o melhor resultado de pesquisa.

Eu não acho que é um bug, e eu não acho que é incomum – na verdade é o que eu esperaria para algo como um leitor de arquivos de texto para fazer. IO é uma operação cara, então, em geral, você quer fazer tudo de uma só vez.

Não é um bug. Mas acredito que você pode usar ReadAllLines () para fazer o que quiser. ReadAllLines cria uma matriz de string e puxa todas as linhas para a matriz, em vez de apenas um enumerador simples em um stream como o ReadLines.

Se você precisar acessar as linhas duas vezes, você sempre poderá armazená-las em buffer em uma List

 using System.Linq; List lines = File.ReadLines("file path").ToList(); foreach (var line in lines) { Console.WriteLine(line); } foreach (var line in lines) { Console.WriteLine(line); } 

Eu não sei se pode ser considerado um bug ou não, se é por design, mas certamente posso dizer duas coisas …

  1. Isso deve ser postado no Connect, não no StackOverflow, embora não seja possível alterá-lo antes que o 4.0 seja lançado. E isso geralmente significa que eles nunca vão consertar isso.
  2. O design do método certamente parece ser falho.

Você está correto em observar que retornar um IEnumerable implica que ele deve ser reutilizável e não garante os mesmos resultados se for iterado duas vezes. Se tivesse retornado um IEnumerator, seria uma história diferente.

De qualquer forma, acho que é um bom achado e acho que a API é péssima para começar. ReadAllLines e ReadAllText dão a você uma maneira conveniente de acessar o arquivo inteiro, mas se o chamador se preocupa o suficiente com o desempenho para usar um enumerável lento, eles não devem delegar tanta responsabilidade a um método auxiliar estático em primeiro lugar.

Eu acredito que você está confundindo um IQueryable com um IEnumerable. Sim, é verdade que o IQueryable pode ser tratado como um IEnumerable, mas eles não são exatamente a mesma coisa. Um IQueryable consulta cada vez que é usado, enquanto um IEnumerable não tem essa reutilização implícita.

Uma consulta de Linq retorna um IQueryable. ReadLines retorna um IEnumerable.

Há uma distinção sutil aqui por causa da forma como um Enumerador é criado. Um IQueryable cria um IEnumerator quando você chama GetEnumerator () nele (o que é feito automaticamente por foreach). ReadLines () cria o IEnumerator quando a function ReadLines () é chamada. Assim, quando você reutiliza um IQueryable, ele cria um novo IEnumerator quando você o reutiliza, mas como o ReadLines () cria o IEnumerator (e não um IQueryable), a única maneira de obter um novo IEnumerator é chamar ReadLines () novamente .

Em outras palavras, você só deve poder reutilizar um IQueryable, não um IEnumerator.

EDITAR:

Em uma reflection posterior (sem trocadilhos), acho que minha resposta inicial foi um pouco simplista demais. Se IEnumerable não fosse reutilizável, você não poderia fazer algo assim:

 List li = new List() {1, 2, 3, 4}; IEnumerable iei = li; foreach (var i in iei) { Console.WriteLine(i); } foreach (var i in iei) { Console.WriteLine(i); } 

Obviamente, não se esperaria que o segundo foreach falhasse.

O problema, como tantas vezes acontece com esses tipos de abstrações, é que nem tudo se encheckbox perfeitamente. Por exemplo, Streams são tipicamente unidirecionais, mas para uso em rede eles precisavam ser adaptados para trabalhar bidirecionalmente.

Neste caso, um IEnumerable foi originalmente concebido para ser um recurso reutilizável, mas desde então foi adaptado para ser tão genérico que a reutilização não é uma garantia ou mesmo deveria ser esperada. Veja a explosão de várias bibliotecas que usam IEnumerables de maneiras não reutilizáveis, como a biblioteca Jeffery Richters PowerThreading.

Eu simplesmente não acho que podemos assumir IEnumerables são reutilizáveis ​​em todos os casos mais.

Não é um bug. File.ReadLines () usa avaliação lenta e não é idempotente . É por isso que não é seguro enumerá-lo duas vezes seguidas. Lembre-se de que um IEnumerable representa uma fonte de dados que pode ser enumerada, ele não declara que é seguro ser enumerado duas vezes, embora isso possa ser inesperado, já que a maioria das pessoas está acostumada a usar collections IEnumerable over idempotent.

Do MSDN :

Os methods ReadLines (String, System) e ReadAllLines (String, System) diferem da seguinte forma: Quando você usa ReadLines, é possível começar a enumerar a coleção de strings antes que toda a coleção seja retornada; quando você usa ReadAllLines, você deve esperar que toda a matriz de strings seja retornada antes de poder acessar a matriz. Portanto, quando você estiver trabalhando com arquivos muito grandes, os ReadLines poderão ser mais eficientes.

Suas descobertas por meio do refletor estão corretas e verificam esse comportamento. A implementação que você forneceu evita esse comportamento inesperado, mas ainda faz uso de avaliação lenta.