Как мне сгенерировать поток из строки?

760

Мне нужно написать модульный тест для метода, который принимает поток из текстового файла. Я хотел бы сделать что-то вроде этого:

Stream s = GenerateStreamFromString("a,b \n c,d");
Ома
источник
Для решения экономии памяти, см. StringReaderStreamВ stackoverflow.com/a/55170901/254109
xmedeko

Ответы:

957
public static Stream GenerateStreamFromString(string s)
{
    var stream = new MemoryStream();
    var writer = new StreamWriter(stream);
    writer.Write(s);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

Не забудьте использовать Using:

using (var stream = GenerateStreamFromString("a,b \n c,d"))
{
    // ... Do stuff to stream
}

О StreamWriterне распоряжаться. StreamWriterэто просто оболочка для основного потока, и она не использует ресурсы, которые должны быть удалены. DisposeМетод будет закрыть , лежащий в основе , Streamкоторый StreamWriterвыполняет запись. В этом случае это MemoryStreamмы и хотим вернуть.

В .NET 4.5 теперь есть перегрузка, благодаря StreamWriterкоторой основной поток остается открытым после удаления средства записи, но этот код делает то же самое и работает с другими версиями .NET.

См. Есть ли способ закрыть StreamWriter, не закрывая его BaseStream?

Кэмерон МакФарланд
источник
134
Важно отметить, что поток состоит из байтов, а строка состоит из символов. Важно понимать, что преобразование символа в один или несколько байтов (или в поток, как в этом случае) всегда использует (или предполагает) определенную кодировку. Этот ответ, хотя и является правильным в некоторых случаях, использует кодировку по умолчанию и может не подходить вообще. Явная передача Encoding конструктору StreamWriter сделает более очевидным, что автору необходимо учитывать последствия Encoding.
drwatsoncode
6
Вы говорите «Не забудьте использовать Using» для использования потока, но в вашем GenerateStreamFromStringметоде вы не используете Use with StreamWriter. Есть причина для этого?
Бен
12
@ Бен Да. Если вы избавитесь от StreamWriter, основной поток также будет закрыт. Мы этого не хотим. Единственная причина, по которой Writer является одноразовым, заключается в очистке потока, поэтому его можно игнорировать.
Кэмерон МакФарланд,
2
Следует также отметить, что вся строка копируется в память, что может быть важно для больших строк, потому что теперь у нас есть одна дополнительная копия в памяти.
UGEEN
1
@ahong Не совсем. StreamWriterв любом случае, вероятно, делает то, что вы сказали внутри. Преимущество - инкапсуляция и более простой код, но за счет абстрагирования от таких вещей, как кодирование. Это зависит от того, чего вы пытаетесь достичь.
Кэмерон Макфарланд,
725

Другое решение:

public static MemoryStream GenerateStreamFromString(string value)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
}
joelnet
источник
31
На всякий случай, если кто-то использует это с десериализацией строки XML, мне пришлось переключить UTF8 на Unicode, чтобы он работал без флага. Отличный пост !!!
Gaspa79
2
Мне нравится этот (с твиком Rhyous и дополнительным дополнительным сахаром для использования в качестве метода расширения) лучше, чем принятый ответ; более гибкий, меньше LOC и меньше вовлеченных объектов (нет явной необходимости в StreamWriter)
KeithS
2
new MemoryStream(Encoding.UTF8.GetBytes("\ufeff" + (value ?? ""))если вам нужно включить спецификацию в начале потока
robert4
5
Это очень компактный синтаксис, но он будет вызывать много выделений byte [], так что будьте осторожны с высокопроизводительным кодом.
michael.aird
1
Это решение все еще оставляло возможность сделать поток доступным только для чтения. new MemoryStream( value, false ), Вы не можете сделать поток доступным только для чтения, если вам нужно записать его с помощью средства записи потока.
codekandis
106

Добавьте это в служебный класс статической строки:

public static Stream ToStream(this string str)
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(str);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

Это добавляет функцию расширения, чтобы вы могли просто:

using (var stringStream = "My string".ToStream())
{
    // use stringStream
}
Джош Дж
источник
5
Я обнаружил, что возвращаемый поток закрывается (вызывая полуслучайные исключения), когда сборщик мусора очищает StreamWriter. Исправление состояло в том, чтобы использовать другой конструктор - тот, который позволял мне указывать оставлять открытыми .
Беван
46
public Stream GenerateStreamFromString(string s)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(s));
}
Чернокнижник
источник
24

Используйте MemoryStreamкласс, вызывая, Encoding.GetBytesчтобы сначала превратить вашу строку в массив байтов.

Нужно ли вам впоследствии TextReaderв потоке? Если это так, вы можете поставить StringReaderнапрямую, и обойти MemoryStreamи Encodingшаги.

Тим Робинсон
источник
23

Я использовал смесь ответов, как это:

public static Stream ToStream(this string str, Encoding enc = null)
{
    enc = enc ?? Encoding.UTF8;
    return new MemoryStream(enc.GetBytes(str ?? ""));
}

И тогда я использую это так:

String someStr="This is a Test";
Encoding enc = getEncodingFromSomeWhere();
using (Stream stream = someStr.ToStream(enc))
{
    // Do something with the stream....
}
Robocide
источник
Томас, почему проголосовали? enc = enc ?? Encoding.UTF8 позволяет мне специально запрашивать поток с определенной кодировкой или значением по умолчанию UTF8, и потому что в .net (насколько я использую это .net 4.0) вы не можете дать ссылочный тип, кроме строки, значение по умолчанию в функции подпись эта строка необходима, имеет ли это смысл?
Robocide
упоминание того, что вам нужно поместить это в отдельный класс (не универсальный статический класс?), также полезно и уменьшает количество голосов.
Али
13

Мы используем методы расширения, перечисленные ниже. Я думаю, что вы должны сделать так, чтобы разработчик принял решение о кодировке, чтобы не было никакой магии.

public static class StringExtensions {

    public static Stream ToStream(this string s) {
        return s.ToStream(Encoding.UTF8);
    }

    public static Stream ToStream(this string s, Encoding encoding) {
        return new MemoryStream(encoding.GetBytes(s ?? ""));
    }
}
Шон Боу
источник
1
Я бы предпочел реализовать первый метод как return ToStream(s, Encoding.UTF8);. В текущей реализации ( return s.ToStream(Encoding.UTF8);разработчик вынужден думать сложнее, чтобы понять код, и кажется, что случай s == nullне обрабатывается и выбрасывает NullReferenceException.
Palec
10

Ну вот:

private Stream GenerateStreamFromString(String p)
{
    Byte[] bytes = UTF8Encoding.GetBytes(p);
    MemoryStream strm = new MemoryStream();
    strm.Write(bytes, 0, bytes.Length);
    return strm;
}
Дальневосточные
источник
1
Положение должно быть сброшено после записи. Лучше использовать конструктор, как в ответе joelnet.
Джим Балтер
10

Модернизированная и слегка модифицированная версия методов расширения для ToStream:

public static Stream ToStream(this string value) => ToStream(value, Encoding.UTF8);

public static Stream ToStream(this string value, Encoding encoding) 
                          => new MemoryStream(encoding.GetBytes(value ?? string.Empty));

Модификация, предложенная в комментарии @ Palec к ответу @Shaun Bowe.

Ник Н.
источник
4

Если вам нужно изменить кодировку, я голосую за решение @ShaunBowe . Но каждый ответ здесь копирует всю строку в памяти хотя бы один раз. Ответы с ToCharArray+ BlockCopyкомбо делают это дважды.

Если это имеет значение, это простая Streamоболочка для необработанной строки UTF-16. Если используется с StreamReaderвыбором Encoding.Unicodeдля него:

public class StringStream : Stream
{
    private readonly string str;

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => str.Length * 2;
    public override long Position { get; set; } // TODO: bounds check

    public StringStream(string s) => str = s ?? throw new ArgumentNullException(nameof(s));

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                Position = offset;
                break;
            case SeekOrigin.Current:
                Position += offset;
                break;
            case SeekOrigin.End:
                Position = Length - offset;
                break;
        }

        return Position;
    }

    private byte this[int i] => (i & 1) == 0 ? (byte)(str[i / 2] & 0xFF) : (byte)(str[i / 2] >> 8);

    public override int Read(byte[] buffer, int offset, int count)
    {
        // TODO: bounds check
        var len = Math.Min(count, Length - Position);
        for (int i = 0; i < len; i++)
            buffer[offset++] = this[(int)(Position++)];
        return (int)len;
    }

    public override int ReadByte() => Position >= Length ? -1 : this[(int)Position++];
    public override void Flush() { }
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    public override string ToString() => str; // ;)     
}

А вот более полное решение с необходимыми проверками привязки (полученными из MemoryStreamтаковых ToArrayи WriteToметодов).

Дьёрдь Кёсег
источник
-2

Хорошая комбинация расширений String:

public static byte[] GetBytes(this string str)
{
    byte[] bytes = new byte[str.Length * sizeof(char)];
    System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
    return bytes;
}

public static Stream ToStream(this string str)
{
    Stream StringStream = new MemoryStream();
    StringStream.Read(str.GetBytes(), 0, str.Length);
    return StringStream;
}
MarkWalls
источник