Jak wygenerować strumień z ciągu?

759

Muszę napisać test jednostkowy dla metody, która pobiera strumień pochodzący z pliku tekstowego. Chciałbym zrobić coś takiego:

Stream s = GenerateStreamFromString("a,b \n c,d");
Omu
źródło
Rozwiązanie dotyczące oszczędzania pamięci znajduje się StringReaderStreamw stackoverflow.com/a/55170901/254109
xmedeko

Odpowiedzi:

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

Nie zapomnij użyć przy użyciu:

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

O StreamWriterniezbywaniu. StreamWriterjest tylko opakowaniem wokół strumienia podstawowego i nie wykorzystuje żadnych zasobów, które należy usunąć. DisposeMetoda zamknie instrumentu bazowego Stream, który StreamWriterpisze do. W takim przypadku MemoryStreamchcemy wrócić.

W .NET 4.5 występuje teraz przeciążenie, StreamWriterktóre utrzymuje otwarty strumień źródłowy po usunięciu programu piszącego, ale ten kod robi to samo i działa również z innymi wersjami .NET.

Zobacz Czy jest jakiś sposób na zamknięcie StreamWriter bez zamykania BaseStream?

Cameron MacFarland
źródło
134
Ważną koncepcją punktową, na którą należy zwrócić uwagę, jest to, że strumień składa się z bajtów, a łańcuch składa się ze znaków. Ważne jest, aby zrozumieć, że konwersja znaku na jeden lub więcej bajtów (lub na strumień, jak w tym przypadku) zawsze używa (lub zakłada) określone kodowanie. Ta odpowiedź, choć poprawna w niektórych przypadkach, wykorzystuje kodowanie domyślne i może być ogólnie nieodpowiednia. Jawne przekazanie kodowania do konstruktora StreamWriter sprawi, że bardziej oczywiste będzie, że autor musi rozważyć konsekwencje kodowania.
drwatsoncode
6
Mówisz „Nie zapomnij użyć przy użyciu” do korzystania ze strumienia, ale w swojej GenerateStreamFromStringmetodzie nie używasz przy użyciu przy użyciu StreamWriter. Czy jest tego powód?
Ben
12
@Ben Tak. Jeśli pozbędziesz się StreamWriter, strumień również zostanie zamknięty. Nie chcemy tego. Jedynym powodem, dla którego Writer jest jednorazowy, jest czyszczenie strumienia, więc można go zignorować.
Cameron MacFarland
2
Należy również zauważyć, że cały ciąg jest kopiowany do pamięci, co może być ważne dla dużych ciągów, ponieważ teraz mamy jedną dodatkową kopię w pamięci.
UGEEN
1
@ahong Nie bardzo. StreamWriteri tak prawdopodobnie robi to, co powiedziałeś wewnętrznie. Zaletą jest enkapsulacja i prostszy kod, ale kosztem abstrakcji takich rzeczy, jak kodowanie. To zależy od tego, co próbujesz osiągnąć.
Cameron MacFarland
724

Inne rozwiązanie:

public static MemoryStream GenerateStreamFromString(string value)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(value ?? ""));
}
joelnet
źródło
31
Na wypadek, gdyby ktoś użył tego z deserializacją łańcucha XML, musiałem przełączyć UTF8 na Unicode, aby działał bez flagi. Wspaniały post!!!
Gaspa79
2
Podoba mi się ten (z poprawką Rhyousa i trywialnym dodatkowym cukrem do stosowania jako metoda rozszerzenia) lepiej niż zaakceptowana odpowiedź; bardziej elastyczny, mniej LOC i mniej zaangażowanych obiektów (nie ma wyraźnej potrzeby StreamWriter)
KeithS
2
new MemoryStream(Encoding.UTF8.GetBytes("\ufeff" + (value ?? ""))jeśli chcesz dołączyć BOM na początku strumienia
robert4,
5
Jest to bardzo zwarta składnia, ale spowoduje wiele alokacji bajtów [], więc uważaj na kod o wysokiej wydajności.
michael.aird
1
To rozwiązanie wciąż pozostawiło okazję do odczytu strumienia. new MemoryStream( value, false ). Nie możesz zrobić strumienia tylko do odczytu, jeśli musisz napisać go za pomocą programu do zapisu strumieniowego.
codekandis,
106

Dodaj to do klasy narzędzia ciąg statyczny:

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

Dodaje to funkcję rozszerzenia, dzięki czemu możesz po prostu:

using (var stringStream = "My string".ToStream())
{
    // use stringStream
}
Josh G.
źródło
5
Odkryłem, że zwrócony strumień zamyka się (powodując pół losowe wyjątki), gdy moduł czyszczenia pamięci czyści plik StreamWriter. Poprawka polegała na użyciu innego konstruktora - takiego, który pozwolił mi określić parametr LeaveOpen .
Bevan
45
public Stream GenerateStreamFromString(string s)
{
    return new MemoryStream(Encoding.UTF8.GetBytes(s));
}
Czarodziej
źródło
24

Użyj MemoryStreamklasy, wywołując, Encoding.GetBytesaby najpierw przekształcić łańcuch znaków w tablicę bajtów.

Czy potrzebujesz później TextReaderw strumieniu? Jeśli tak, możesz podać StringReaderbezpośrednio i ominąć kroki MemoryStreami Encoding.

Tim Robinson
źródło
23

Użyłem kombinacji takich odpowiedzi:

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

A potem używam tego w ten sposób:

String someStr="This is a Test";
Encoding enc = getEncodingFromSomeWhere();
using (Stream stream = someStr.ToStream(enc))
{
    // Do something with the stream....
}
Robocide
źródło
Thomas, dlaczego głosować w dół? enc = enc ?? Encoding.UTF8 pozwala mi konkretnie zapytać strumień z konkretnym kodowaniem lub domyślnym UTF8, a ponieważ w .net (o ile go używam .net 4.0) nie można podać typu referencyjnego innego niż string domyślna wartość w funkcji podpis ten wiersz jest konieczny, czy to ma sens?
Robocide
wspomnienie, że musisz umieścić to w osobnej klasie (ogólna klasa statyczna?) jest również pomocne i zmniejsza liczbę głosów negatywnych.
Ali
13

Korzystamy z metod rozszerzenia wymienionych poniżej. Myślę, że powinieneś skłonić programistę do podjęcia decyzji o kodowaniu, aby w grę wchodziło mniej magii.

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 ?? ""));
    }
}
Shaun Bowe
źródło
1
Wolałbym wdrożyć pierwszą metodę jako return ToStream(s, Encoding.UTF8);. W obecnej implementacji ( return s.ToStream(Encoding.UTF8);deweloper jest zmuszony do s == nullNullReferenceException
większego
10

Proszę bardzo:

private Stream GenerateStreamFromString(String p)
{
    Byte[] bytes = UTF8Encoding.GetBytes(p);
    MemoryStream strm = new MemoryStream();
    strm.Write(bytes, 0, bytes.Length);
    return strm;
}
cjk
źródło
1
Pozycja musi zostać zresetowana po zapisaniu. Lepiej używać konstruktora, jak w odpowiedzi Joelneta.
Jim Balter,
10

Zmodernizowana i nieznacznie zmodyfikowana wersja metod rozszerzenia dla 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));

Modyfikacja sugerowana w komentarzu @ Palec do odpowiedzi @Shaun Bowe.

Nick N.
źródło
3

Jeśli potrzebujesz zmienić kodowanie, głosuję na rozwiązanie @ShaunBowe . Ale każda odpowiedź tutaj co najmniej raz kopiuje cały ciąg do pamięci. Odpowiedzi z kombinacją ToCharArray+ BlockCopyzrobić to dwa razy.

Jeśli to ma znaczenie, to proste Streamopakowanie na nieprzetworzony ciąg UTF-16. W przypadku użycia z StreamReaderwyborem 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; // ;)     
}

A oto bardziej kompletne rozwiązanie z niezbędnymi kontrolami powiązanymi (pochodzące z MemoryStreamtak ma ToArrayi WriteTometod).

György Kőszeg
źródło
-2

Dobra kombinacja rozszerzeń 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
źródło