我正在测试类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'类型转换为'string'
while循环确实有效,如下所示:
while((line = sr.ReadLine()) != null) { Console.WriteLine(line); }
我可能忽略了一些非常基本的东西,但我看不到它.
如果要通过foreach(以可重用的方式)逐行读取文件,请考虑以下迭代器块:
public static IEnumerableReadLines(string path) { using (StreamReader reader = File.OpenText(path)) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } }
请注意,这是懒惰的评估 - 没有与之关联的缓冲File.ReadAllLines()
.该foreach
语法将确保迭代器Dispose()
甚至例外正确Ð,关闭文件:
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); }
而且,所有人都懒洋洋地评估(没有缓冲).
要读取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项目中有一个类.它比这里给出的解决方案略胜一筹,主要是根据你构建它的方式:
从返回流的函数,在这种情况下,它将使用UTF-8
从返回流的函数和编码
从返回文本阅读器的函数
仅从文件名,在这种情况下,它将使用UTF-8
从文件名和编码
该类"拥有"它使用的任何资源,并适当地关闭它们.但是,它没有实现IDisposable
自己就这样做了.这就是为什么它需要Func
而Func
不是流或读取器直接 - 它需要能够推迟开放直到它需要它.它是迭代器本身(由foreach
循环自动处理),它关闭资源.
正如Marc所指出的,这在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 { ////// 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. /// public sealed class LineReader : IEnumerable{ /// /// Means of creating a TextReader to read from. /// readonly FuncdataSource; /// /// 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. /// /// Data source public LineReader(FuncstreamSource) : this(streamSource, Encoding.UTF8) { } /// /// Creates a LineReader from a stream source. The delegate is only /// called when the enumerator is fetched. /// /// Data source /// Encoding to use to decode the stream /// into text public LineReader(FuncstreamSource, Encoding encoding) : this(() => new StreamReader(streamSource(), encoding)) { } /// /// 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. /// /// File to read from public LineReader(string filename) : this(filename, Encoding.UTF8) { } ////// Creates a LineReader from a filename. The file is only opened /// (or even checked for existence) when the enumerator is fetched. /// /// File to read from /// Encoding to use to decode the file /// into text public LineReader(string filename, Encoding encoding) : this(() => new StreamReader(filename, encoding)) { } ////// Creates a LineReader from a TextReader source. The delegate /// is only called when the enumerator is fetched /// /// Data source public LineReader(FuncdataSource) { this.dataSource = dataSource; } /// /// Enumerates the data source line by line. /// public IEnumeratorGetEnumerator() { using (TextReader reader = dataSource()) { string line; while ((line = reader.ReadLine()) != null) { yield return line; } } } /// /// Enumerates the data source line by line. /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } }