Проблема FileStream StreamReader в C#

Я тестирую, как классы 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 (непосредственно относящегося к вашему комментарию о «yield»), я рекомендую бесплатную главу 6 C# in Depth - здесь: manning.com/skeet

Marc Gravell 13.11.2008 12:19

Ваша проблема в том, что ваш код выполняет итерацию (foreach) по каждому элементу в «файле» (который является строкой). Следовательно, каждый элемент - это «символ». Следовательно, появляется сообщение об ошибке компилятора, которое вы затем пытаетесь преобразовать в строковый тип. Вы должны выполнять итерацию (foreach) по данным из потока.

RichS 13.11.2008 18:02
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
21
2
71 795
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Проблема в:

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

Это потому, что «файл» - это строка, а строка реализует IEnumerable. Но этот перечислитель возвращает «char», а «char» не может быть явно преобразовано в строку.

Вы должны использовать цикл while, как вы сказали.

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

Вы уверены, что это то, что вам нужно?

foreach(string line in file)

Не совсем то, что я хочу, я просто проверяю возможности. Я никогда особо не использовал цикл foreach.

Vordreller 13.11.2008 11:48

Для меня это похоже на домашнее задание;)

Вы повторяете имя файла (строку), которое дает вам по одному символу за раз. Просто используйте подход while, который правильно использует sr.ReadLine ().

Это «своего рода» домашнее задание. Я работаю над чем-то еще, что содержит похожие вещи. Итак, я немного знакомлюсь с классами FileStream и StreamReader / StreamWriter ^^

Vordreller 13.11.2008 11:58

Вместо того, чтобы использовать 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))
{
  ..
}

См. Мой пост для версии без буферизации этого

Marc Gravell 13.11.2008 11:47

Если вы хотите читать файл построчно через 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);
        }

И снова все оценивается лениво (без буферизации).

Начиная с .net 4.0, он встроен в BCL как File.ReadLines

CodesInChaos 11.02.2013 00:22

не будет ли использование while (!reader.EndOfStream) { yield return reader.ReadLine() } немного лучше с точки зрения производительности? Очевидно, что совсем не с большим отрывом, но указанная выше переменная line не используется нигде, кроме yield return, и я лично считаю, что это условие более читабельно.

cogumel0 07.12.2016 23:40

Полагаю, вам нужно что-то вроде этого:

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 ...

Vordreller 13.11.2008 12:01

Ну не совсем так, как у меня работает, у тебя нет. ;-) Ваша проблема в том, что вы выполняете "foreach" для имени файла, а не для потока.

RichS 13.11.2008 17:58
Ответ принят как подходящий

Чтобы прочитать все строки в 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<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();
        }
    }
}

Другие вопросы по теме