23 Stimmen

Probleme mit FileStream StreamReader in C#

Ich teste, wie die Klassen FileStream und StreamReader zusammenarbeiten. Über eine Konsolenanwendung. Ich versuche, in eine Datei zu gehen, die Zeilen zu lesen und sie auf der Konsole auszugeben.

Ich konnte es mit einer while-Schleife machen, aber ich möchte es mit einer foreach-Schleife versuchen.

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);
                    }
                }
            }
        }
    }
}

Der Fehler, den ich dabei immer bekomme, lautet: Kann Typ 'char' nicht in Typ 'string' konvertieren

Die while-Schleife, die funktioniert, sieht so aus:

while((line = sr.ReadLine()) != null)
{
    Console.WriteLine(line);
}

Ich übersehe wahrscheinlich etwas wirklich Grundlegendes, aber ich sehe es nicht.

43voto

Marc Gravell Punkte 970173

Wenn Sie eine Datei zeilenweise mit foreach (auf eine wiederverwendbare Weise) lesen möchten, sollten Sie den folgenden Iterator-Block in Betracht ziehen:

    public static IEnumerable ReadLines(string path)
    {
        using (StreamReader reader = File.OpenText(path))
        {
            string line;
            while ((line = reader.ReadLine()) != null)
            {
                yield return line;
            }
        }
    }

Beachten Sie, dass dies faul ausgewertet wird - es gibt keine Pufferung, die Sie mit File.ReadAllLines() in Verbindung bringen würden. Die foreach-Syntax stellt sicher, dass der Iterator auch bei Ausnahmen richtig Dispose() wird und die Datei schließt:

foreach(string line in ReadLines(file))
{
    Console.WriteLine(line);
}

(Dieser Teil wurde nur aus Interesse hinzugefügt...)

Ein weiterer Vorteil dieser Art der Abstraktion ist, dass sie wunderbar mit LINQ harmoniert - d.h. es ist einfach, Transformationen/Filter usw. mit diesem Ansatz durchzuführen:

        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);
        }

Und wieder alles faul ausgewertet (keine Pufferung).

27voto

Aleksandar Punkte 1341

Um alle Zeilen in New Text Document.txt zu lesen:

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());
                    }
                }
            }
        }
    }
}

26voto

Jon Skeet Punkte 1325502

Ich habe eine LineReader-Klasse in meinem MiscUtil-Projekt. Sie ist etwas allgemeiner als die hier angegebenen Lösungen, hauptsächlich in Bezug darauf, wie Sie sie konstruieren können:

  • Aus einer Funktion, die einen Stream zurückgibt, in diesem Fall wird UTF-8 verwendet
  • Aus einer Funktion, die einen Stream zurückgibt, und eine Codierung
  • Aus einer Funktion, die einen Textleser zurückgibt
  • Nur aus einem Dateinamen, in diesem Fall wird UTF-8 verwendet
  • Aus einem Dateinamen und einer Codierung

Die Klasse "besitzt" die von ihr verwendeten Ressourcen und schließt sie angemessen. Sie implementiert jedoch nicht selbst IDisposable. Das ist der Grund, warum sie Func und Func anstelle des Streams oder des Readers direkt nimmt - sie muss die Öffnung bis zu dem Zeitpunkt verschieben können, an dem sie benötigt wird. Der Iterator selbst (der automatisch durch eine foreach-Schleife entsorgt wird) schließt die Ressource.

Wie Marc angemerkt hat, funktioniert dies sehr gut in LINQ. Ein Beispiel, das ich gerne gebe, ist:

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;

Dies streamt alle Fehler aus einer Vielzahl von Protokolldateien, öffnet und schließt dabei. In Kombination mit Push LINQ können Sie allerlei schöne Dinge tun :)

Es handelt sich nicht um eine besonders "tricky" Klasse, aber sie ist wirklich praktisch. Hier ist der vollständige Quellcode, falls Sie MiscUtil nicht herunterladen möchten. Die Lizenz für den Quellcode finden Sie hier.

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace MiscUtil.IO
{
    /// 
    /// Liest eine Datenquelle zeilenweise. Die Quelle kann eine Datei, ein Stream oder ein Textleser sein. In jedem Fall wird die Quelle nur geöffnet, wenn der Enumerator abgerufen wird, und wird geschlossen, wenn der Iterator entsorgt wird.
    /// 
    public sealed class LineReader : IEnumerable
    {
        /// 
        /// Mittel zur Erstellung eines Textreaders zum Lesen.
        /// 
        readonly Func dataSource;

        /// 
        /// Erstellt einen LineReader aus einer Streamquelle. Der Delegat wird erst aufgerufen, wenn der Enumerator abgerufen wird. UTF-8 wird verwendet, um den Stream in Text zu decodieren.
        /// 
        /// Datenquelle
        public LineReader(Func streamSource)
            : this(streamSource, Encoding.UTF8)
        {
        }

        /// < summary>
        /// Erstellt einen LineReader aus einer Streamquelle. Der Delegat wird erst aufgerufen, wenn der Enumerator abgerufen wird.
        /// 
        /// Datenquelle
        /// < param name = "encoding">Codierung, die zum Decodieren des Streams in Text verwendet wird
        public LineReader(Func streamSource, Encoding encoding)
            : this(() => new StreamReader(streamSource(), encoding))
        {
        }

        /// 
        /// Erstellt einen LineReader aus einem Dateinamen. Die Datei wird erst geöffnet (oder sogar auf Existenz überprüft), wenn der Enumerator abgerufen wird. UTF8 wird verwendet, um die Datei in Text zu decodieren.
        /// 
        /// Datei zum Lesen
        public LineReader(string filename)
            : this(filename, Encoding.UTF8)
        {
        }

        /// < summary>
        /// Erstellt einen LineReader aus einem Dateinamen. Die Datei wird erst geöffnet (oder sogar auf Existenz überprüft), wenn der Enumerator abgerufen wird.
        /// 
        /// Datei zum Lesen
        /// Zum Decodieren der Datei in Text zu verwendende Codierung
        public LineReader(string filename, Encoding encoding)
            : this(() => new StreamReader(filename, encoding))
        {
        }

        /// 
        /// Erstellt einen LineReader aus einer Textleserquelle. Der Delegat wird erst aufgerufen, wenn der Enumerator abgerufen wird.
        /// 
        /// Datenquelle
        public LineReader(Func dataSource)
        {
            this.dataSource = dataSource;
        }

        /// 
        /// Enumeriert die Datenquelle zeilenweise.
        /// 
        public IEnumerator GetEnumerator()
        {
            using (TextReader reader = dataSource())
            {
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    yield return line;
                }
            }
        }

        /// 
        /// Enumeriert die Datenquelle zeilenweise.
        /// 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

3voto

TcKs Punkte 24671

Das Problem liegt in:

foreach(string line in file)
{
    Console.WriteLine(line);
}

Das liegt daran, dass "file" ein String ist und String IEnumerable implementiert. Aber dieser Enumerator gibt "char" zurück und "char" kann nicht implizit in einen String konvertiert werden.

Du solltest die while-Schleife verwenden, wie du gesagt hast.

3voto

Bob Punkte 31

Etwas eleganter ist folgendes...

using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read))
{
    using (var streamReader = new StreamReader(fileStream))
    {
        while (!streamReader.EndOfStream)
        {
            yield return reader.ReadLine();
        }
    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X