Як створити потік із рядка?


759

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

Stream s = GenerateStreamFromString("a,b \n c,d");

Для економії пам'яті рішення см StringReaderStreamв stackoverflow.com/a/55170901/254109
xmedeko

Відповіді:


956
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 (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
Важливою концепцією, яку слід зазначити, є те, що потік складається з байтів, а рядок складається з символів. Важливо розуміти, що для перетворення символу в один або кілька байтів (або в Потік, як у цьому випадку) завжди використовується (або передбачається) певне кодування. Ця відповідь, хоч і є правильною в деяких випадках, використовує кодування за замовчуванням, і може взагалі не підходити. Явне передавання кодування до конструктора StreamWriter зробить більш очевидним, що автору потрібно враховувати наслідки кодування.
drwatsoncode

6
Ви кажете "Не забудьте використовувати" Використання "для використання потоку, але у вашому GenerateStreamFromStringметоді ви не використовуєте Використання з StreamWriter. Чи є для цього причина?
Бен

12
@Ben Так. Якщо ви утилізуєте StreamWriter, базовий потік також буде закритий. Ми цього не хочемо. Єдиною причиною того, що Writer є одноразовим, є очищення потоку, тому його можна ігнорувати.
Камерон Макфарланд

2
Слід також зазначити, що весь рядок копіюється в пам'ять, що може бути важливим для великих рядків, тому що зараз у нас є одна додаткова копія в пам'яті.
UGEEN

1
@ahong Не дуже. StreamWriterце, мабуть, все одно, що ви сказали всередині. Перевагою є інкапсуляція та більш простий код, але ціною вилучення таких речей, як кодування. Це залежить від того, що ви намагаєтеся досягти.
Камерон Макфарланд

724

Ще одне рішення:

public static MemoryStream GenerateStreamFromString(string value)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
}

31
На всякий випадок, якщо хтось використовує це з деріаріалізацією рядка XML, мені довелося переключити UTF8 на Unicode, щоб він працював без прапора. Чудовий пост !!!
Гаспа79

2
Мені подобається цей (з підкручуванням Rhyous та тривіальним додатковим цукром для використання як метод продовження) краще, ніж прийнята відповідь; більш гнучка, менша кількість LOC та менша кількість об'єктів (без явної потреби в StreamWriter)
KeithS

2
new MemoryStream(Encoding.UTF8.GetBytes("\ufeff" + (value ?? ""))якщо вам потрібно включити BOM на початку потоку
robert4

5
Це дуже компактний синтаксис, але він спричинить багато виділення байтів [], тому остерігайтеся коду з високою продуктивністю.
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. Виправленням було використання іншого конструктора - такого, який дозволив мені вказати putOpen .
Беван


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

Томасе, чому голосувати проти? enc = enc ?? Encoding.UTF8 дозволяє мені спеціально запитувати потік із конкретним кодуванням або за замовчуванням UTF8, і тому що в .net (наскільки я його використовую .net 4.0) ви не можете надати посилальному типу, крім рядка, значення за замовчуванням у функції підпис цього рядка необхідний, чи це має сенс?
Робоцид

згадуючи, що вам потрібно помістити це в окремий клас (не загальний статичний клас?), також корисно і зменшити кількість голосів.
Алі

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 == nullNullReferenceException
розправляється

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

Модифікація, як було запропоновано у коментарі @ Палека до відповіді @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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.