2 Stimmen

Protobuf-net und das Serialisieren/Versenden von Objekten

Ich überprüfe, ob protobuf-net ein Ersatz für DataContracts sein kann. Neben der ausgezeichneten Leistung ist es wirklich eine ordentliche Bibliothek. Das einzige Problem, das ich habe, ist, dass die .NET-Serialisierer keine Annahmen darüber treffen, was sie gerade de/serialisieren. Besonders Objekte, die einen Verweis auf das getippte Objekt enthalten, sind ein Problem.

[DataMember(Order = 3)]
public object Tag1 // Der DataContract enthielt ein Objekt, das jetzt zu einem SimulatedObject wird
{
    get;
    set;
}

Ich habe versucht, ein Objekt mit Protocol Buffers nachzubilden, mit einem kleinen generischen Helfer, der für jeden möglichen Typ in einem separaten stark typisierten Feld speichert.

Ist dies ein empfohlener Ansatz, um mit Feldern umzugehen, die in eine Reihe von unterschiedlichen nicht verwandten Typen de/serialisieren?

Im Folgenden finden Sie den Beispielcode für ein SimulatedObject, das bis zu 10 verschiedene Typen speichern kann.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using ProtoBuf;
using System.Diagnostics;

[DataContract]
public class SimulatedObject
{
    [DataMember(Order = 20)]
    byte FieldHasValue; // Die Nummer zeigt an, welches Feld tatsächlich einen Wert hat

    [DataMember(Order = 1)]
    T1 I1;

    [DataMember(Order = 2)]
    T2 I2;

    [DataMember(Order = 3)]
    T3 I3;

    [DataMember(Order = 4)]
    T4 I4;

    [DataMember(Order = 5)]
    T5 I5;

    [DataMember(Order = 6)]
    T6 I6;

    [DataMember(Order = 7)]
    T7 I7;

    [DataMember(Order = 8)]
    T8 I8;

    [DataMember(Order = 9)]
    T9 I9;

    [DataMember(Order = 10)]
    T10 I10;

    public object Data
    {
        get
        {
            switch(FieldHasValue)
            {
                case 0: return null;
                case 1: return I1;
                case 2: return I2;
                case 3: return I3;
                case 4: return I4;
                case 5: return I5;
                case 6: return I6;
                case 7: return I7;
                case 8: return I8;
                case 9: return I9;
                case 10: return I10;
                default:
                    throw new NotSupportedException(String.Format("Das Feld FieldHasValue hat einen ungültigen Wert {0}. Dies deutet auf beschädigte Daten oder inkompatible Datenlayoutänderungen hin", FieldHasValue));
            }
        }
        set
        {
            I1 = default(T1);
            I2 = default(T2);
            I3 = default(T3);
            I4 = default(T4);
            I5 = default(T5);
            I6 = default(T6);
            I7 = default(T7);
            I8 = default(T8);
            I9 = default(T9);
            I10 = default(T10);

            if (value != null)
            {
                Type t = value.GetType();
                if (t == typeof(T1))
                {
                    FieldHasValue = 1;
                    I1 = (T1) value;
                }
                else if (t == typeof(T2))
                {
                    FieldHasValue = 2;
                    I2 = (T2) value;
                }
                else if (t == typeof(T3))
                {
                    FieldHasValue = 3;
                    I3 = (T3) value;
                }
                else if (t == typeof(T4))
                {
                    FieldHasValue = 4;
                    I4 = (T4) value;
                }
                else if (t == typeof(T5))
                {
                    FieldHasValue = 5;
                    I5 = (T5) value;
                }
                else if (t == typeof(T6))
                {
                    FieldHasValue = 6;
                    I6 = (T6) value;
                }
                else if (t == typeof(T7))
                {
                    FieldHasValue = 7;
                    I7 = (T7) value;
                }
                else if (t == typeof(T8))
                {
                    FieldHasValue = 8;
                    I8 = (T8) value;
                }
                else if (t == typeof(T9))
                {
                    FieldHasValue = 9;
                    I9 = (T9) value;
                }
                else if (t == typeof(T10))
                {
                    FieldHasValue = 10;
                    I10 = (T10) value;
                }
                else
                {
                    throw new NotSupportedException(String.Format("Der Typ {0} wird nicht für die Serialisierung unterstützt. Fügen Sie bitte den Typ zur Liste der generischen Argumente von SimulatedObject hinzu.", t.FullName));
                }
            }
        }
    }
}

[DataContract]
class Customer
{
    /* 
    [DataMember(Order = 3)]
    public object Tag1 // Der DataContract enthielt ein Objekt, das jetzt zu einem SimulatedObject wird
    {
        get;
        set;
    }
    */

    [DataMember(Order = 3)]
    public SimulatedObject Tag1 // Kann bis zu 10 verschiedene Typen enthalten
    {
        get;
        set;
    }

    [DataMember(Order = 4)]
    public List Strings
    {
        get;
        set;
    }
}

[DataContract]
public class Other
{
    [DataMember(Order = 1)]
    public string OtherData
    {
        get;
        set;
    }
}

[DataContract]
public class SomethingDifferent
{
    [DataMember(Order = 1)]
    public string OtherData
    {
        get;
        set;
    }

}

class Program
{
    static void Main(string[] args)
    {
        Customer c = new Customer
        {
            Strings = new List { "Erste", "Zweite", "Dritte" },
            Tag1 = new SimulatedObject
                    {
                        Data = new Other { OtherData = "Stringwert "}
                    }
        };

        const int Läufe = 1000 * 1000;
        var stream = new MemoryStream();

        var sw = Stopwatch.StartNew();

        Serializer.Serialize(stream, c);
        sw = Stopwatch.StartNew();
        for (int i = 0; i < Läufe; i++)
        {
            stream.Position = 0;
            stream.SetLength(0);
            Serializer.Serialize(stream, c);
        }
        sw.Stop();
        Console.WriteLine("Datengröße mit dem Protocol Buffer-Serializer: {0}, {1} Objekte dauerten {2}s", stream.ToArray().Length, Läufe, sw.Elapsed.TotalSeconds);

        stream.Position = 0;
        var newCustw = Serializer.Deserialize(stream);

        sw = Stopwatch.StartNew();
        for (int i = 0; i < Läufe; i++)
        {
            stream.Position = 0;
            var newCust = Serializer.Deserialize(stream);
        }
        sw.Stop();
        Console.WriteLine("Objekt mit dem Protocol Buffer-Deserializer lesen: {0} Objekte dauerten {1}s", Läufe, sw.Elapsed.TotalSeconds);

    }
}

2voto

lstern Punkte 1589

Nein, diese Lösung ist auf lange Sicht schwer zu pflegen.

Ich empfehle, dass Sie den vollständigen Namen des serialisierten Typs dem serialisierten Daten im Serialisierungsprozess voranstellen und den Typnamen am Anfang des Deserialisierungsprozesses lesen (keine Notwendigkeit, den protobuf-Quellcode zu ändern)

Als Nebenbemerkung sollten Sie versuchen, Objekttypen im Deserialisierungsprozess nicht zu vermischen. Ich gehe davon aus, dass Sie eine bestehende .net-Anwendung aktualisieren und sie nicht neu entwerfen können.

Aktualisierung: Beispielscode

public byte[] Serialize(object meinObjekt)
{
    using (var ms = new MemoryStream())
    {
        Type typ = meinObjekt.GetType();
        var id = System.Text.ASCIIEncoding.ASCII.GetBytes(typ.FullName + '|');
        ms.Write(id, 0, id.Length);
        Serializer.Serialize(ms, meinObjekt);
        var bytes = ms.ToArray();
        return bytes;
    }
}

public object Deserialize(byte[] serialisierteDaten)
{
    StringBuilder sb = new StringBuilder();
    using (var ms = new MemoryStream(serialisierteDaten))
    {
        while (true)
        {
            var aktuellesZeichen = (char)ms.ReadByte();
            if (aktuellesZeichen == '|')
            {
                break;
            }

            sb.Append(aktuellesZeichen);
        }

        string typName = sb.ToString();

        // unter der Annahme, dass das aufrufende Assembly den gewünschten Typ enthält.
        // Sie können zusätzliche Assembly-Informationen einschließen, wenn erforderlich
        Type deserialisierungsTyp = Assembly.GetCallingAssembly().GetType(typName);

        MethodInfo mi = typeof(Serializer).GetMethod("Deserialize");
        MethodInfo generischeMethode = mi.MakeGenericMethod(new[] { deserialisierungsTyp });
        return generischeMethode.Invoke(null, new[] { ms });
    }
}

0voto

Ich arbeite gerade an etwas Ähnlichem und habe bereits die erste Version der Lib bereitgestellt: http://bitcare.codeplex.com/

Die aktuelle Version unterstützt noch keine Generics, aber ich plane, es in nächster Zeit hinzuzufügen. Ich habe bisher nur den Quellcode dort hochgeladen - wenn ich bereit mit Generics bin, werde ich auch eine Binärversion erstellen...

Es wird davon ausgegangen, dass beide Seiten (Client und Server) wissen, was sie serialisieren/deserialisieren, sodass es keinen Grund gibt, dort vollständige Metadateninformationen zu integrieren. Aufgrund dessen sind die Serialisierungsergebnisse sehr klein und die generierten Serialisierer arbeiten sehr schnell. Es hat Datenwörterbücher, verwendet intelligente Datenspeicherung (speichert nur wichtige Teile in Kürze) und führt bei Bedarf eine finale Komprimierung durch. Wenn Sie es benötigen, probieren Sie es aus, ob es Ihr Problem löst.

Die Lizenz ist GPL, aber ich werde sie bald in eine weniger restriktive ändern (kostenlos für den kommerziellen Gebrauch auch, aber auf eigene Gefahr wie bei GPL)

-1voto

Die Version, die ich auf Codeplex hochgeladen habe, funktioniert mit einigen meiner Produkte. Sie wurde natürlich mit verschiedenen Einheitstests getestet. Diese sind dort nicht hochgeladen, weil ich sie auf vs2012 und .net 4.5 übertragen habe und beschlossen habe, neue Testfälle für das kommende Release zu erstellen.

Ich beschäftige mich nicht mit abstrakten (sogenannten offenen) Generics. Ich verarbeite parametrisierte Generics. Aus Sicht des Datenvertrags sind parametrisierte Generics nur spezialisierte Klassen, so dass ich sie wie gewohnt verarbeiten kann (wie andere Klassen auch) - der Unterschied liegt nur im Objektaufbau und in Speicheroptimierungen.

Wenn ich Informationen über einen Nullwert auf Nullable<> speichere, benötigt es im Speicherstream nur ein Bit. Wenn es kein Nullwert ist, führe ich die Serialisierung gemäß des als Generics-Parameter bereitgestellten Typs durch (also führe ich z.B. die Serialisierung von DateTime durch, die von einem Bit für den sogenannten Standardwert bis zu einigen Bytes reichen kann). Das Ziel war es, den Serialisierungscode gemäß des aktuellen Wissens über Datenverträge auf Klassen zu generieren, anstatt es vor Ort zu tun und Speicher und Rechenleistung zu verschwenden. Wenn ich das Eigenschaft in einer Klasse basierend auf einem bestimmten Generics während der Codegenerierung sehe, kenne ich alle Eigenschaften dieses Generics und ich kenne den Typ jeder Eigenschaft :) Aus dieser Perspektive ist es eine konkrete Klasse.

Ich werde die Lizenz bald ändern. Ich muss erst noch herausfinden, wie ich das mache, weil ich sehe, dass es möglich ist, aus einer Liste der bereitgestellten Lizenztypen zu wählen, aber ich kann meinen eigenen Lizenztext nicht bereitstellen. Ich sehe, dass die Lizenz von Newtonsoft.Json das ist, was ich auch hätte, aber ich weiß noch nicht, wie ich sie ändern soll...

Die Dokumentation wurde dort noch nicht bereitgestellt, aber kurz gesagt ist es einfach, Ihre eigenen Serialisierungstests vorzubereiten. Sie müssen das Assembly mit den Typen kompilieren, die Sie effektiv speichern/serialisieren möchten, dann erstellen Sie *.tt-Dateien in Ihrer Serialisierungsbibliothek (zum Beispiel für die Klasse Person - es überprüft Abhängigkeiten und generiert auch Code für andere abhängige Klassen) und speichern die Dateien (wenn Sie sie speichern, generiert sie den gesamten Code für die Zusammenarbeit mit der Serialisierungsbibliothek). Sie können auch die Aufgabe in Ihrer Build-Konfiguration erstellen, um den Quellcode aus den tt-Dateien jedes Mal neu zu generieren, wenn Sie die Lösung erstellen (wahrscheinlich generiert das Entity Framework den Code ähnlich während des Builds). Sie können Ihre Serialisierungsbibliothek jetzt kompilieren und die Leistung sowie die Größe der Ergebnisse messen.

Ich benötige diese Serialisierungsbibliothek für mein Framework für die effektive Verwendung von Entitäten mit Azure Table und Blob-Speicher, daher plane ich, das erste Release bald abzuschließen...

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X