2009-08-05 4 views
44

Ich benutze DataContractJsonSerializer, die gerne auf einen Stream ausgeben. Ich möchte die Ausgänge des Serialisierers nach oben und nach hinten drehen, also benutzte ich einen StreamWriter, um abwechselnd die zusätzlichen Bits zu schreiben, die ich brauchte.Schreiben, um dann aus einem MemoryStream zu lesen

var ser = new DataContractJsonSerializer(typeof (TValue)); 

using (var stream = new MemoryStream()) 
{ 
    using (var sw = new StreamWriter(stream)) 
    { 
     sw.Write("{"); 

     foreach (var kvp in keysAndValues) 
     { 
      sw.Write("'{0}':", kvp.Key); 
      ser.WriteObject(stream, kvp.Value); 
     } 

     sw.Write("}"); 
    } 

    using (var streamReader = new StreamReader(stream)) 
    { 
     return streamReader.ReadToEnd(); 
    } 
} 

Als ich das bekomme ich einen ArgumentException „Strom nicht lesbar war“.

Ich mache wahrscheinlich alle möglichen Fehler hier, also alle Antworten willkommen. Vielen Dank.

Antwort

86

Drei Dinge:

  • die StreamWriter nicht schließen. Das wird die MemoryStream schließen. Sie müssen den Schreiber jedoch löschen.
  • Setzen Sie die Position des Datenstroms vor dem Lesen zurück.
  • Wenn Sie direkt in den Stream schreiben, müssen Sie zuerst den Writer leeren.

So:

using (var stream = new MemoryStream()) 
{ 
    var sw = new StreamWriter(stream); 
    sw.Write("{"); 

    foreach (var kvp in keysAndValues) 
    { 
     sw.Write("'{0}':", kvp.Key); 
     sw.Flush(); 
     ser.WriteObject(stream, kvp.Value); 
    }  
    sw.Write("}");    
    sw.Flush(); 
    stream.Position = 0; 

    using (var streamReader = new StreamReader(stream)) 
    { 
     return streamReader.ReadToEnd(); 
    } 
} 

Es gibt eine andere einfachere Alternative though. Alles, was Sie mit dem Stream machen, wenn Sie ihn lesen, wandelt ihn in eine Zeichenkette um. Sie können einfach, dass mehr:

return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int) stream.Length); 

Leider MemoryStream.Length wird ausgelöst, wenn der Strom geschlossen wurde, so dass Sie wahrscheinlich den StreamWriter Konstruktor nennen würde wollen, die nicht den zugrunde liegenden Stream nicht schließt, oder einfach nicht Schließen Sie die StreamWriter.

Ich bin besorgt, dass Sie direkt in den Stream schreiben - was ist ser? Ist es ein XML-Serializer oder ein binärer? Wenn es binär ist, ist Ihr Modell etwas fehlerhaft - Sie sollten Binär- und Textdaten nicht mischen, ohne dabei sehr vorsichtig zu sein. Wenn es sich um XML handelt, können Sie feststellen, dass Sie in der Mitte des Strings Byte-Order-Marken haben, was problematisch sein könnte.

+0

Danke Jon. Es ist ein DataContractJsonSerializer. Ich habe den Code jetzt bearbeitet, um das zu zeigen. Macht das, was ich mache, OK? – Gaz

+1

dachte nicht nur an den Puffer greifen - netter – ShuggyCoUk

+0

Gaz: Ich vermute, dass wird in Ordnung sein, obwohl ich mir nicht ganz sicher bin. Vielleicht möchten Sie überlegen, was passiert, wenn das XML eine schließende geschweifte Klammer enthält ... –

6

Einstellung der Speicher Streams Position an den Anfang könnte helfen.

Aber das Kernproblem ist, dass der StreamWriter Ihren Speicher-Stream schließt, wenn es geschlossen ist.

Einfach den Strom spülen, wo Sie den Verwendungsblock dafür beenden und nur davon nach Sie haben die Daten aus dem Speicher-Stream gelesen haben wird dies für Sie lösen.

Sie können auch ein StringWriter stattdessen betrachten möchten, mit ...

using (var writer = new StringWriter()) 
{ 
    using (var sw = new StreamWriter(stream)) 
    { 
     sw.Write("{"); 

     foreach (var kvp in keysAndValues) 
     { 
      sw.Write("'{0}':", kvp.Key); 
      ser.WriteObject(writer, kvp.Value); 
     } 
     sw.Write("}"); 
    } 

    return writer.ToString(); 
} 

Dies würde Ihre Serialisierung Writeobject Aufruf benötigen einen Textwriter anstelle eines Strom aufnehmen können.

+0

Ja sorry Ich habe die Erklärung von ser jetzt hinzugefügt. Es akzeptiert nur einen XmlWriter, der mir nicht wirklich hilft, da ich dann nicht nur '' '' und was auch immer hinzufügen kann. Ich denke, es verwendet XmlSerializer unter, aber ich möchte jetzt nicht darüber nachdenken müssen. Die Einstellung der Position hat keine Auswirkung. – Gaz

1

Nur eine wilde Vermutung: Vielleicht müssen Sie den Streamwriter spülen? Möglicherweise sieht das System, dass Schreibvorgänge "ausstehend" sind. Durch das Löschen können Sie sicher sein, dass der Stream alle geschriebenen Zeichen enthält und lesbar ist.

3

Um den Inhalt eines Memory zugreifen, nachdem es die ToArray() oder GetBuffer() Methoden geschlossen wurde. Der folgende Code veranschaulicht, wie der Inhalt des Speicherpuffers als eine UTF8-codierte Zeichenfolge abgerufen wird.

byte[] buff = stream.ToArray(); 
return Encoding.UTF8.GetString(buff,0,buff.Length); 

Hinweis: ToArray() ist einfacher zu benutzen als GetBuffer() weil ToArray() kehrt die genaue Länge des Stroms, anstatt die Puffergröße (der größer als der Strominhalt sein könnte). ToArray() erstellt eine Kopie der Bytes.

Hinweis: GetBuffer() ist performanter als ToArray(), da es keine Kopie der Bytes erstellt. Sie müssen am Ende des Puffers auf mögliche nicht definierte abschließende Bytes achten, indem Sie die Streamlänge und nicht die Puffergröße berücksichtigen. Es wird dringend empfohlen, GetBuffer() zu verwenden, wenn die Streamgröße größer als 80000 Byte ist, da die ToArray-Kopie auf dem Large Object Heap zugewiesen wird, wo die Lebensdauer problematisch werden kann.

Es ist auch möglich, den ursprünglichen MemoryStream wie folgt zu klonen, um den Zugriff über einen StreamReader z.

using (MemoryStream readStream = new MemoryStream(stream.ToArray())) 
{ 
... 
} 

Die ideale Lösung ist, wenn möglich, auf den ursprünglichen MemoryStream zuzugreifen, bevor dieser geschlossen wurde.

+1

"Hinweis: ToArray() ist besser als GetBuffer(), da ToArray() die genaue Länge des Streams und nicht die Puffergröße (die größer als der Stream-Inhalt sein kann) zurückgibt." Aber warum kümmert es dich? Es gibt keinen Nachteil darin, ein übergroßes Array an 'Encoding.GetString' zu übergeben - wir wissen genau, wie viele Bytes es dekodieren soll. Die Tatsache, dass 'GetBuffer' * keine Kopie erstellt, ist genau der Grund, warum Sie es verwenden würden - es vermeidet die Erstellung der sinnlosen Kopie, die 'ToArray' erstellt. –

+0

Guter Punkt @JonSkeet Ich werde meine Antwort aktualisieren, um die Leistungsaspekte widerzuspiegeln. Wenn jemand interessiert ist, gibt es eine Stapel-Austausch-Frage über die Vorzüge von MemoryStream.GetBuffer hier https://StackOverflow.com/Questions/13053739/When-is-getbuffer-on-memorystream-ever-ususeful –

Verwandte Themen