Laden...

Dekomprimieren mit GZipStream funktioniert im Debugger aber ohne Debugger nicht

Erstellt von TiloS vor 10 Jahren Letzter Beitrag vor 10 Jahren 1.780 Views
T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren
Dekomprimieren mit GZipStream funktioniert im Debugger aber ohne Debugger nicht

Hallo,

ich möchte gzip-komprimierte Daten dekomprimieren, habe dabei aber einige merkwürdige Phänomäne bei folgendem Code:


byte[] cmpData = Convert.FromBase64String(cmpBase64String);
            
string outString = "";

using (MemoryStream decomStream = new MemoryStream(cmpData))
{
    using (GZipStream gzipStream = new GZipStream(decomStream, compressionMode.Decompress))
    {
        using (StreamReader sr = new StreamReader(gzipStream))
        {
            outString = sr.ReadToEnd();
        }
    }
}

Der outString ist leer. Wenn ich allerdings im Debug-Modus auf der StreamReader-Zuweisung stoppe und dabei in das gzipStream-Objekt hineinschaue, dann sehe ich dass er darin Daten hat. Gehe ich dann einen Schritt weiter, hat auch der StreamReader Daten und der outString wird gefüllt. Schaue ich beim Debuggen nicht in das Objekt, wird der outString bzw. der StreamReader nicht gefüllt.
Wer wüsste dafür eine Erklärung?

Gruß
Tilo

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo TiloS,

bist du dir sicher, dass es ohne Debugger nicht geht? Hast du also insbesondere direkt nach der Zuweisung an outString mal dessen Wert ausgegeben?

herbivore

C
2.121 Beiträge seit 2010
vor 10 Jahren

Schaue ich beim Debuggen nicht in das Objekt

Das heißt du debuggst zwar, aber du lässt dir den Inhalt nicht anzeigen?
Dann machst du noch irgendwas mit den Streams falsch. Hast du dir ein Beispiel gesucht wie man das GZip handhabt? Möglicherweise fehlt noch ein Flush oder Close, das dann automatisch passiert sobald der Stream mit ToString() seinen Inhalt anzeigt.
Probier mal sowas aus.

T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

Hallo,

danke für die Antworten.

Ich habe mal ein paar Screenshots zusammengefügt. Ich muss direkt vor dem auslesen in den StreamReader hineinschauen, ansonsten wird der outString nicht gefüllt, und und ist auch bei einer Ausgabe nicht gefüllt.

Ich vermute auch, dass es irgendwas mit der Handhabung des GZipStream ist, habe aber noch nicht herausgefunden was.

Gruß
Tilo

16.830 Beiträge seit 2008
vor 10 Jahren

Ich weiß nicht wieso, ich habs auch nicht genauer angeschaut.....
Aber alle Beispiele, die ich so in Google kurzerhand finde, machen es mehr oder weniger einheitlich folgendermaßen:

public static string Decompress( string cmpBase64String )
{
    var byteArray = Convert.FromBase64String( cmpBase64String );
    System.Text.StringBuilder sB;

    using ( var sr = new System.IO.Compression.GZipStream( new System.IO.MemoryStream( byteArray ), System.IO.Compression.CompressionMode.Decompress ) )
    {
        // Reset
        byteArray = new byte[ byteArray.Length ];

        // Decompressing
        var rByte = sr.Read( byteArray, 0, byteArray.Length );

        // Transform to String
        sB = new System.Text.StringBuilder( rByte );

        for ( var i = 0 ; i < rByte ; i++ )
        {
            sB.Append( ( char ) byteArray[ i ] );
        }
    }

    return sB.ToString( );
}

Du könntest Dir mit den teilweise doppelten usings selbst in die Suppe spucken.
Würdest Du FxCop darüber laufen lassen, würde er es auch als kritisch markieren.

S
417 Beiträge seit 2008
vor 10 Jahren

Hallo,

ich kann leider auch nicht sagen was da schief läuft, aber folgendes funktioniert einwandfrei (egal ob Debug oder nicht): (Compress/Uncompress Methoden entnommen von String compression using GZipStream)

[TestClass]
public class UnitTest1
{
	[TestMethod]
	public void Test_Success()
	{
		var s = Compress("Hello World");
		Assert.AreEqual("Hello World", Decompress(s));
	}

	public static byte[] Compress(string text)
	{
		using (var ms = new MemoryStream())
		{
			using (var zip = new GZipStream(ms, CompressionMode.Compress))
			using (var writer = new StreamWriter(zip))
				writer.Write(text);
			
			return ms.ToArray();
		}
	}

	public static string Decompress(byte[] compressed)
	{
		using (var ms = new MemoryStream(compressed))
		using (var zip = new GZipStream(ms, CompressionMode.Decompress))
		using (var sr = new StreamReader(zip))
		{
			return sr.ReadToEnd();
		}
	}

}
T
TiloS Themenstarter:in
35 Beiträge seit 2012
vor 10 Jahren

So jetzt hab ich's.

Es lag nicht an der Dekomprimierung, sondern an der Komprimierung. Dort wurden 2 Zeichen am Ende abgeschnitten und damit hatte die Dekomprimierung Schwierigkeiten. Dort stand der Reader beim ReadToEnd schon an der Endposition. Beim direkten nochmaligen ReadToEnd war der outString dann gefüllt.

Die Lösung war aber die Komprimierungsmethode anzupassen. Mit der von "Sarc" hat es dann funktioniert.

Danke.
Gruß
Tilo

16.830 Beiträge seit 2008
vor 10 Jahren

Aber auch für Sarc's Snippet gilt: CA2202: Objekte nicht mehrmals verwerfen

S
417 Beiträge seit 2008
vor 10 Jahren

Also manchmal sollte man bestimmte Warnung auch ignorieren. Obige gehört für mich dazu.
Der modifizierte Code würde meiner Meinung nach die Lesbarkeit/Verständlichkeit des Codes verschlechtern. Siehe dazu auch als Beispiel auch CA2202, how to solve this case.

It's really bad to create ugly code just to comply with a warning that should be suppressed.

16.830 Beiträge seit 2008
vor 10 Jahren

Die Frage ist, ob hier die Lesbarkeit oder die Ausführungssicherheit des Codes höher gewertet wird.
Eine ObjectDisposedException, die nachher die Anwendung zum Absturz bringt, ist sicherlich das größere Übel. .NET-Klassen fangen dies in der Regel ab; gerade bei eigenen IDisposable-Implementierungen, die zu 90% nicht sauber sind, krachts dann aber..

Das muss letzten Endes jeder selbst für sich wissen.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo Abt,

die Begründung für die Regel CA2202 ist:

Eine ordnungsgemäß implementierte Dispose-Methode kann mehrmals aufgerufen werden, ohne dass eine Ausnahme ausgelöst wird. Da dies jedoch nicht gewährleistet ist, sollten Sie Dispose nicht mehr als einmal für ein Objekt aufrufen, um zu vermeiden, dass System.ObjectDisposedException generiert wird.

In dem kritisierten Code werden ausschließlich Objekte von Streams-Klassen des .NET-Frameworks disposed. Da kann man mit Fug und Recht davon ausgehen, dass diese korrekt implementiert sind. Ein mehrfaches Dispose ist hier unkritisch.

Gerade bei Strömen, die in anderen Strömen enthalten sind, und daher implizit dem mit umgebenden Strom mitgeschlossen werden, kann es - wie der Link von Sarc zeigt - mitunter recht aufwändig werden, den Code so zu schreiben, dass ein mehrfaches Dispose vermieden wird. Das würde ich unter premature optimization rechnen, hier mal nicht aus Performance-Gründen, sondern zum Vermeiden einer ObjectDisposedException, die sowieso nicht geworfen wird.

Ich bin ja sehr für defensives Programmieren, aber nicht, wenn die Komplexität des Codes dadurch so sehr steigt, dass die Wahrscheinlichkeit eines Fehlers letztendlich steigt statt sinkt - und gleichzeitig die Wartbarkeit sinkt.

Wann die negativen Effekte die positiven überwiegen - da sind wir wieder einer Meinung -, muss jeder für sich selbst wissen.

herbivore