Konwersja Stream na String iz powrotem… czego nam brakuje?

162

Chcę serializować obiekty do łańcuchów iz powrotem.

Używamy protobuf-net, aby pomyślnie zamienić obiekt w strumień iz powrotem.

Jednak Stream to string iz powrotem ... nie jest tak skuteczny. Po przejściu przez StreamToStringi StringToStream, nowy Streamnie jest deserializowany przez protobuf-net; rodzi Arithmetic Operation resulted in an Overflowwyjątek. Jeśli zdeserializujemy oryginalny strumień, to zadziała.

Nasze metody:

public static string StreamToString(Stream stream)
{
    stream.Position = 0;
    using (StreamReader reader = new StreamReader(stream, Encoding.UTF8))
    {
        return reader.ReadToEnd();
    }
}

public static Stream StringToStream(string src)
{
    byte[] byteArray = Encoding.UTF8.GetBytes(src);
    return new MemoryStream(byteArray);
}

Nasz przykładowy kod wykorzystujący te dwa:

MemoryStream stream = new MemoryStream();
Serializer.Serialize<SuperExample>(stream, test);
stream.Position = 0;
string strout = StreamToString(stream);
MemoryStream result = (MemoryStream)StringToStream(strout);
var other = Serializer.Deserialize<SuperExample>(result);
flipuhdelphia
źródło
1
czy Stream nie powinien być MemoryStrea?
Ehsan

Odpowiedzi:

60

To jest tak powszechne, ale tak głęboko błędne. Dane protobuf nie są danymi ciągów. Z pewnością nie jest to ASCII. Używasz kodowania wstecz . Transfery kodowania tekstu:

  • dowolny ciąg do sformatowanych bajtów
  • sformatowane bajty do oryginalnego ciągu

Nie masz „sformatowanych bajtów”. Masz dowolne bajty . Musisz użyć czegoś takiego jak kodowanie base-n (zwykle: base-64). To przenosi

  • dowolne bajty do sformatowanego ciągu
  • sformatowany ciąg do oryginalnych bajtów

spójrz na Convert.ToBase64String i Convert. FromBase64String

Marc Gravell
źródło
1
czy mógłbyś użyć BinaryFormatterpodobnego do tego dziwnego przykładu ?
drzaus
@drzaus hm ... może nie:> "Wszelkie niesparowane znaki zastępcze są tracone podczas serializacji binarnej"
drzaus
210

Właśnie to przetestowałem i działa dobrze.

string test = "Testing 1-2-3";

// convert string to stream
byte[] byteArray = Encoding.ASCII.GetBytes(test);
MemoryStream stream = new MemoryStream(byteArray);

// convert stream to string
StreamReader reader = new StreamReader(stream);
string text = reader.ReadToEnd();

Jeśli streamzostała już napisana, możesz zechcieć odszukać początek przed pierwszym, zanim przeczytasz tekst:stream.Seek(0, SeekOrigin.Begin);

Ehsan
źródło
I nie zapomnij o bloku using wokół StreamReader reader = new StreamReader (stream);
PRMan
7

konwersja UTF8 MemoryStream na String:

var res = Encoding.UTF8.GetString(stream.GetBuffer(), 0 , (int)stream.Length)
Wolfgang Grinfeld
źródło
2
Zamiast tego użyj ToArray (). Bufor może być większy niż rozmiar używanych danych. ToArray () zwraca kopię danych o prawidłowym rozmiarze. var array = stream.ToArray(); var str = Encoding.UTF8.GetString(array, 0, array.Length);. Zobacz także msdn.microsoft.com/en-us/library/…
Mortennobel
1
@Mortennobel ToArray()przydziela nową tablicę w pamięci i kopiuje dane z bufora, co może mieć poważne konsekwencje, jeśli masz do czynienia z dużą ilością danych.
Levi Botelho
Zwróć uwagę na użycie stream.Length zamiast stream.GetBuffer (). Length. Levi poprawnie zanotował powód nieużywania ToArray ().
Wolfgang Grinfeld
5

Podczas testowania spróbuj użyć UTF8strumienia Encode, jak poniżej

var stream = new MemoryStream();
var streamWriter = new StreamWriter(stream, System.Text.Encoding.UTF8);
Serializer.Serialize<SuperExample>(streamWriter, test);
Damith
źródło
5

Spróbuj tego.

string output1 = Encoding.ASCII.GetString(byteArray, 0, byteArray.Length)
user3588327
źródło
2

Napisałem użyteczną metodę do wywoływania dowolnej akcji, która przyjmuje a StreamWriteri zapisuje ją w postaci ciągu znaków. Metoda jest taka;

static void SendStreamToString(Action<StreamWriter> action, out string destination)
{
    using (var stream = new MemoryStream())
    using (var writer = new StreamWriter(stream, Encoding.Unicode))
    {
        action(writer);
        writer.Flush();
        stream.Position = 0;
        destination = Encoding.Unicode.GetString(stream.GetBuffer(), 0, (int)stream.Length);
    }
}

I możesz tego używać w ten sposób;

string myString;

SendStreamToString(writer =>
{
    var ints = new List<int> {1, 2, 3};
    writer.WriteLine("My ints");
    foreach (var integer in ints)
    {
        writer.WriteLine(integer);
    }
}, out myString);

Wiem, że można to zrobić o wiele łatwiej za pomocą a StringBuilder, chodzi o to, że możesz wywołać każdą metodę, która używa StreamWriter.

Steztric
źródło
1

Chcę serializować obiekty do łańcuchów iz powrotem.

Różni się od innych odpowiedzi, ale najprostszym sposobem na zrobienie tego dokładnie dla większości typów obiektów jest XmlSerializer:

        Subject subject = new Subject();
        XmlSerializer serializer = new XmlSerializer(typeof(Subject));
        using (Stream stream = new MemoryStream())
        {
            serializer.Serialize(stream, subject);
            // do something with stream
            Subject subject2 = (Subject)serializer.Deserialize(stream);
            // do something with subject2
        }

Wszystkie właściwości publiczne obsługiwanych typów zostaną zserializowane. Obsługiwane są nawet niektóre struktury kolekcji, które będą tunelować do właściwości podobiektów. Możesz kontrolować, jak serializacja działa z atrybutami we właściwościach.

Nie działa to ze wszystkimi typami obiektów, niektóre typy danych nie są obsługiwane w przypadku serializacji, ale ogólnie jest dość potężne i nie musisz się martwić o kodowanie.

Denise Skidmore
źródło
0

W przypadku, gdy chcesz serializować / deserializować POCO, biblioteka JSON Newtonsoft jest naprawdę dobra. Używam go do utrwalania POCO w SQL Server jako ciągi JSON w polu nvarchar. Uwaga: ponieważ nie jest to prawdziwa de / serializacja, nie zachowa prywatnych / chronionych członków i hierarchii klas.

MD Luffy
źródło