Я тестирую, как классы FileStream и StreamReader работают вместе. Через консольное приложение. Я пытаюсь зайти в файл, прочитать строки и распечатать их на консоли.
Мне удалось сделать это с помощью цикла while, но я хочу попробовать это с помощью цикла foreach.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace testing
{
public class Program
{
public static void Main(string[] args)
{
string file = @"C:\Temp\New Folder\New Text Document.txt";
using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr = new StreamReader(fs))
{
foreach(string line in file)
{
Console.WriteLine(line);
}
}
}
}
}
}
Ошибка, которую я получаю по этому поводу: не удается преобразовать тип char в строку
Цикл while, который действительно работает, выглядит так:
while((line = sr.ReadLine()) != null)
{
Console.WriteLine(line);
}
Я, наверное, упускаю из виду что-то действительно простое, но не вижу этого.
Ваша проблема в том, что ваш код выполняет итерацию (foreach) по каждому элементу в «файле» (который является строкой). Следовательно, каждый элемент - это «символ». Следовательно, появляется сообщение об ошибке компилятора, которое вы затем пытаетесь преобразовать в строковый тип. Вы должны выполнять итерацию (foreach) по данным из потока.





Проблема в:
foreach(string line in file)
{
Console.WriteLine(line);
}
Это потому, что «файл» - это строка, а строка реализует IEnumerable. Но этот перечислитель возвращает «char», а «char» не может быть явно преобразовано в строку.
Вы должны использовать цикл while, как вы сказали.
Вы перечисляете строку, и когда вы это делаете, вы берете по одному символу за раз.
Вы уверены, что это то, что вам нужно?
foreach(string line in file)
Не совсем то, что я хочу, я просто проверяю возможности. Я никогда особо не использовал цикл foreach.
Для меня это похоже на домашнее задание;)
Вы повторяете имя файла (строку), которое дает вам по одному символу за раз. Просто используйте подход while, который правильно использует sr.ReadLine ().
Это «своего рода» домашнее задание. Я работаю над чем-то еще, что содержит похожие вещи. Итак, я немного знакомлюсь с классами FileStream и StreamReader / StreamWriter ^^
Вместо того, чтобы использовать StreamReader и затем пытаться найти строки внутри переменной String file, вы можете просто использовать File.ReadAllLines:
string[] lines = File.ReadAllLines(file);
foreach(string line in lines)
Console.WriteLine(line);
Упрощенный (неэффективный с точки зрения памяти) подход к итерации каждой строки в файле:
foreach (string line in File.ReadAllLines(file))
{
..
}
См. Мой пост для версии без буферизации этого
Если вы хотите читать файл построчно через foreach (с возможностью многократного использования), рассмотрите следующий блок итератора:
public static IEnumerable<string> ReadLines(string path)
{
using (StreamReader reader = File.OpenText(path))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
Обратите внимание, что это вычисляется лениво - нет никакой буферизации, которую вы бы связали с File.ReadAllLines(). Синтаксис foreach гарантирует, что итератор правильно настроен на Dispose()d даже для исключений, закрыв файл:
foreach(string line in ReadLines(file))
{
Console.WriteLine(line);
}
(этот бит добавлен просто для интереса ...)
Еще одним преимуществом этого типа абстракции является то, что он прекрасно работает с LINQ, то есть с помощью этого подхода легко выполнять преобразования / фильтры и т. д.:
DateTime minDate = new DateTime(2000,1,1);
var query = from line in ReadLines(file)
let tokens = line.Split('\t')
let person = new
{
Forname = tokens[0],
Surname = tokens[1],
DoB = DateTime.Parse(tokens[2])
}
where person.DoB >= minDate
select person;
foreach (var person in query)
{
Console.WriteLine("{0}, {1}: born {2}",
person.Surname, person.Forname, person.DoB);
}
И снова все оценивается лениво (без буферизации).
не будет ли использование while (!reader.EndOfStream) { yield return reader.ReadLine() } немного лучше с точки зрения производительности? Очевидно, что совсем не с большим отрывом, но указанная выше переменная line не используется нигде, кроме yield return, и я лично считаю, что это условие более читабельно.
Полагаю, вам нужно что-то вроде этого:
using ( FileStream fileStream = new FileStream( file, FileMode.Open, FileAccess.Read ) )
{
using ( StreamReader streamReader = new StreamReader( fileStream ) )
{
string line = "";
while ( null != ( line = streamReader.ReadLine() ) )
{
Console.WriteLine( line );
}
}
}
Это именно то, что я напечатал в своем первом посте, просто другое имя указателя для StreamReader и! = Null на другой стороне цикла while ...
Ну не совсем так, как у меня работает, у тебя нет. ;-) Ваша проблема в том, что вы выполняете "foreach" для имени файла, а не для потока.
Чтобы прочитать все строки в New Text Document.txt:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace testing
{
public class Program
{
public static void Main(string[] args)
{
string file = @"C:\Temp\New Folder\New Text Document.txt";
using(FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using(StreamReader sr = new StreamReader(fs))
{
while(!sr.EndOfStream)
{
Console.WriteLine(sr.ReadLine());
}
}
}
}
}
}
У меня есть класс LineReader в моем проекте MiscUtil. Он немного более общий, чем приведенные здесь решения, в основном с точки зрения способа его построения:
Класс «владеет» всеми ресурсами, которые он использует, и закрывает их соответствующим образом. Однако он делает это без реализации самого IDisposable. Вот почему он принимает Func<Stream> и Func<TextReader> вместо потока или считывателя напрямую - он должен иметь возможность откладывать открытие до тех пор, пока оно ему не понадобится. Это сам итератор (который автоматически удаляется циклом foreach) закрывает ресурс.
Как отметил Марк, это очень хорошо работает в LINQ. Я хотел бы привести один пример:
var errors = from file in Directory.GetFiles(logDirectory, "*.log")
from line in new LineReader(file)
select new LogEntry(line) into entry
where entry.Severity == Severity.Error
select entry;
Это будет передавать все ошибки из целой кучи файлов журнала, открывая и закрываясь по ходу. В сочетании с Push LINQ вы можете делать разные приятные вещи :)
Это не особо «хитрый» класс, но действительно удобный. Вот полный исходный код для удобства, если вы не хотите загружать MiscUtil. Лицензия на исходный код - здесь.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace MiscUtil.IO
{
/// <summary>
/// Reads a data source line by line. The source can be a file, a stream,
/// or a text reader. In any case, the source is only opened when the
/// enumerator is fetched, and is closed when the iterator is disposed.
/// </summary>
public sealed class LineReader : IEnumerable<string>
{
/// <summary>
/// Means of creating a TextReader to read from.
/// </summary>
readonly Func<TextReader> dataSource;
/// <summary>
/// Creates a LineReader from a stream source. The delegate is only
/// called when the enumerator is fetched. UTF-8 is used to decode
/// the stream into text.
/// </summary>
/// <param name = "streamSource">Data source</param>
public LineReader(Func<Stream> streamSource)
: this(streamSource, Encoding.UTF8)
{
}
/// <summary>
/// Creates a LineReader from a stream source. The delegate is only
/// called when the enumerator is fetched.
/// </summary>
/// <param name = "streamSource">Data source</param>
/// <param name = "encoding">Encoding to use to decode the stream
/// into text</param>
public LineReader(Func<Stream> streamSource, Encoding encoding)
: this(() => new StreamReader(streamSource(), encoding))
{
}
/// <summary>
/// Creates a LineReader from a filename. The file is only opened
/// (or even checked for existence) when the enumerator is fetched.
/// UTF8 is used to decode the file into text.
/// </summary>
/// <param name = "filename">File to read from</param>
public LineReader(string filename)
: this(filename, Encoding.UTF8)
{
}
/// <summary>
/// Creates a LineReader from a filename. The file is only opened
/// (or even checked for existence) when the enumerator is fetched.
/// </summary>
/// <param name = "filename">File to read from</param>
/// <param name = "encoding">Encoding to use to decode the file
/// into text</param>
public LineReader(string filename, Encoding encoding)
: this(() => new StreamReader(filename, encoding))
{
}
/// <summary>
/// Creates a LineReader from a TextReader source. The delegate
/// is only called when the enumerator is fetched
/// </summary>
/// <param name = "dataSource">Data source</param>
public LineReader(Func<TextReader> dataSource)
{
this.dataSource = dataSource;
}
/// <summary>
/// Enumerates the data source line by line.
/// </summary>
public IEnumerator<string> GetEnumerator()
{
using (TextReader reader = dataSource())
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
}
/// <summary>
/// Enumerates the data source line by line.
/// </summary>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Немного элегантнее выглядит следующее ...
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
using (var streamReader = new StreamReader(fileStream))
{
while (!streamReader.EndOfStream)
{
yield return reader.ReadLine();
}
}
}
Что касается foreach (непосредственно относящегося к вашему комментарию о «yield»), я рекомендую бесплатную главу 6 C# in Depth - здесь: manning.com/skeet