Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 13 Min.

Serialisieren und deserialisieren im Eiltempo

MemoryPack bietet als Serialisierungslösung viele Funktionen und hohe Geschwindigkeit – allerdings nicht für alle .NET-Versionen.
© dotnetpro
Die Serialisierung und Deserialisierung von Daten ist ein zentraler Aspekt moderner Softwareentwicklung. Dieser Prozess, bei dem Datenstrukturen in ein Format umgewandelt werden, das für Speicherung, Übertragung und Wiederverwendung geeignet ist, spielt eine Schlüsselrolle in der effizienten Datenhandhabung und -kommunikation.Neben den Vorteilen wie der Vereinfachung des Datenaustauschs zwischen unterschiedlichen Systemen und der Erleichterung der Datenpersistenz bringen die Serialisierungstechnologien auch eine Reihe von Herausforderungen mit sich. Diese reichen von Leistungs- und Effizienzfragen über Sicherheits- und Datenschutzbedenken bis hin zu Problemen der Kompatibilität und Versionierung.Der Fokus dieser 126. Episode von Frameworks und mehr liegt auf der Vorstellung und Beschreibung der Bibliothek MemoryPack. Diese Serialisierungsbibliothek für .NET wirbt mit einer einfachen Nutzung, zahlreichen Funktionen und einer ungeschlagenen Geschwindigkeit. Der Artikel zeigt mit zahlreichen Beispielen und einem Blick unter die Haube, wie MemoryPack implementiert ist.

Herausforderungen und Lösungen bei der Serialisierung

Die Serialisierung komplexer oder umfangreicher Datenstrukturen kostet Leistung der betroffenen Software. Dies wird insbesondere in Umgebungen mit hohem Datenaufkommen oder in Echtzeitanwendungen zu einem Problem. Zur Lösung dieses Problems werden hoch optimierte Bibliotheken und Algorithmen für die Serialisierung eingesetzt.Diese bieten eine schnellere Verarbeitung und reduzieren den Speicherverbrauch, indem sie effiziente Datenkompressionsmethoden und optimierte Datenstrukturen verwenden. Zusätzlich kann die Anwendung von Lazy-Loading-Techniken dazu beitragen, nur die unmittelbar benötigten Daten zu laden, wodurch die Systemressourcen geschont werden.In der heutigen Zeit, in der Datenschutz und Datensicherheit eine zentrale Rolle spielen, ist es entscheidend, die Sicherheit bei der Serialisierung und Deserialisierung zu gewährleisten. Sensible Daten, die serialisiert werden, könnten anfällig für unbefugten Zugriff oder Manipulation sein. Eine gängige Lösung ist die Implementierung von Verschlüsselungsmechanismen, die sicherstellen, dass Daten während der Übertragung und Speicherung geschützt sind.Darüber hinaus ist es wichtig, Best Practices für den Datenschutz anzuwenden, wie die Anonymisierung oder Pseudonymisierung von Daten, um die Einhaltung von Datenschutzvorschriften wie der DSGVO zu gewährleisten. Zudem ist darauf zu achten, dass Schwachstellen in der Deserialisierung nicht zu Remote-Code-Executions führen können.Eine mögliche Lösung dafür besteht darin, keine serialisierten Objekte aus nicht vertrauenswürdigen Quellen anzunehmen oder nur serialisierte Datenstrukturen zu akzeptieren, die ausschließlich einfache Datentypen erlauben. Darüber hinaus lässt sich die Manipulation von Daten durch das digitale Signieren von serialisierten Objekten verhindern.Eine weitere Herausforderung ist die Aufrechterhaltung der Kompatibilität zwischen verschiedenen Versionen serialisierter Daten. Änderungen in Datenstrukturen oder Formaten können zu Inkompatibilitäten führen. Eine wirksame Lösung besteht darin, Versionierungskonzepte in den Serialisierungsprozess zu integrieren. Dies ermöglicht es, verschiedene Versionen von Datenobjekten zu verwalten und gleichzeitig Rückwärtskompatibilität zu gewährleisten. Strategien wie der Tolerant-Reader-Ansatz und Schema Evolution helfen dabei, flexibel auf Änderungen zu reagieren und die Integrität von Daten über Systemversionen hinweg zu sichern.

Was ist MemoryPack?

MemoryPack ist eine Serialisierungsbibliothek für .NET, die speziell für maximale Geschwindigkeit und Speicheroptimierung entwickelt wurde. Sie bietet schnelle Serialisierung und Deserialisierung durch die Verwendung eines binären Formats und ist dadurch besonders geeignet für Anwendungsfälle, in denen große Datenmengen oder komplexe Objektstrukturen verarbeitet werden müssen.MemoryPack unterstützt eine Vielzahl von .NET-Datentypen und ermöglicht Anpassungen für spezifische Anforderungen bei der Serialisierung. Durch die Vermeidung von Reflection und Dynamic Code Generation bietet es eine Alternative zu traditionellen Serialisierungsmethoden. Typische Anwendungsfälle für MemoryPack finden sich in Echtzeitsystemen, Cloud-basierten Diensten, Microservices-Architekturen und bei der Verarbeitung von Big Data.Die MemoryPack-Bibliothek ist ein Produkt von Cysharp [1] und wird auf GitHub [2] gehostet. Cysharp ist ein alter Bekannter, da von dem Unternehmen Cygames, zu dem Cysharp gehört, schon einige Projekte im Rahmen dieser Kolumne vorgestellt wurden – zum Beispiel ZString und ZLogger.MemoryPack ist beim Schreiben dieses Artikels in der Version 1.10.0 vom November 2023 verfügbar und lässt sich beispielsweise über NuGet zu einem Projekt hinzufügen. Die Bibliothek funktioniert mit C# und Unity und ist für die Verwendung mit .NET 7 optimiert, unterstützt aber auch .NET Standard 2.1 als Mindestanforderung.Für MemoryPack erscheinen regelmäßige Updates für neue Funktionen und Fehlerbehebungen, beispielsweise bezogen auf Sicherheitslücken und Leistungsprobleme. Die Entwickler von MemoryPack legen großen Wert darauf, die Bibliothek mit den neuesten .NET-Framework-Updates kompatibel zu halten, was für Entwickler, die auf dem neuesten Stand der Technologie bleiben wollen, von großer Bedeutung ist. Darüber hinaus ermöglicht die aktive Community um MemoryPack ein rasches Feedback zu Problemen und Verbesserungsvorschlägen, was zu einer dynamischen und reaktiven Update-Politik beiträgt.

Installation und erste Schritte

Die Installation erfolgt durch das Hinzufügen des MemoryPack-Pakets zu einem Projekt, entweder über die Kommandozeile oder direkt über die NuGet-Paketverwaltung in Ihrer Entwicklungsumgebung:

dotnet add <span class="hljs-keyword">package</span> <span class="hljs-title">MemoryPack</span> 
Da MemoryPack nur für bestimmte Versionen des .NET Frameworks entwickelt wurde, gilt es zunächst sicherzustellen, dass Ihr Projekt eine kompatible .NET-Version nutzt. Für die beste Leistung wird empfohlen, .NET 7 zu verwenden, wobei die Mindestanforderung .NET Standard 2.1 ist. Darüber hi­­naus ist zu prüfen, ob Konflikte mit anderen Bibliotheken für die Serialisierung auftreten und ob vorhandene Datenstrukturen mit MemoryPack kompatibel sind. Beispielsweise sind einige Probleme bei der Verwendung von MemoryPack in externen DLL-Bibliotheken bekannt, insbesondere bei der Verwendung von Polymorphismus in C# 8.0 [3].Die initiale Konfiguration für den Einsatz in einem Projekt ist denkbar einfach. Nachdem das Paket hinzugefügt ist, braucht es lediglich ein Struct oder eine Klasse, die mit dem Attribut [MemoryPackable] und dem Schlüsselwort partial ausgestattet ist. Der folgende Code zeigt eine solche Klasse:

<span class="hljs-keyword">using</span> MemoryPack;
[MemoryPackable]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MeinObjekt</span>
{
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Id { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
} 
Anschließend lässt sich ein Objekt dieser Klasse serialisieren und wieder deserialisieren:

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Program</span>
{
  <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Main</span>(<span class="hljs-params"><span class="hljs-keyword">string</span>[] args</span>)</span>
<span class="hljs-function">  </span>{
    <span class="hljs-keyword">var</span> meinObjekt = 
      <span class="hljs-keyword">new</span> MeinObjekt { Id = <span class="hljs-number">1</span>, Name = <span class="hljs-string">"Beispiel"</span> };
    <span class="hljs-comment">// Serialisierung</span>
<span class="hljs-comment">    byte[] serializedData = </span>
<span class="hljs-comment">      MemoryPackSerializer.Serialize(meinObjekt);</span>
<span class="hljs-comment">    // Deserialisierung</span>
<span class="hljs-comment">    MeinObjekt deserializedObject = </span>
<span class="hljs-comment">      MemoryPackSerializer</span>
<span class="hljs-comment">        .Deserialize&lt;MeinObjekt&gt;(serializedData);</span>
<span class="hljs-comment">  }</span>
<span class="hljs-comment">}</span> 
Der Code für die Serialisierung wird mit dem C#-Quellcodegenerator erzeugt, der die Schnittstelle IMemoryPackable<T> implementiert.In Visual Studio lässt sich dieser generierte Code über die Tastenkombination [Strg] + [K], [R] anzeigen. Klicken Sie anschließend auf den Klassennamen und wählen Sie den Eintrag *.MemoryPackFormatter.g.cs aus.Die Methode Serialize von MemoryPack unterstützt den Rückgabetyp byte[] und lässt sich in IBufferWriter<byte> oder ein Stream-Objekt serialisieren. Die Methode Deserialize unterstützt ReadOnlySpan<byte>, ReadOnlySequence<byte> und Stream. Zudem existieren nichtgenerische Versionen dieser Methoden und Rückgaben.

Kernmerkmale von MemoryPack

Neben schneller Serialisierung und Deserialisierung von Daten, der Unterstützung vielfältiger Datentypen und der Anpassungsfähigkeit durch Erweiterungen bietet die Bibliothek eine Reihe weiterer Funktionen an. Dazu gehört die Unterstützung für moderne I/O-APIs wie zum Beispiel IBufferWriter<byte>, ReadOnlySpan<byte>, ReadOnlySequence<byte>.Zudem verwendet MemoryPack einen Source Generator für die Codegenerierung, was Kompatibilität mit Native AOT (Ahead-of-Time Compilation) bietet. Damit wird Dynamic Code Generation vermieden, die in bestimmten Umgebungen eingeschränkt sein könnte. Darüber hinaus verzichtet das API von MemoryPack auf Reflection und bietet nichtgenerische Schnittstellen.Die Bibliothek ermöglicht es, Daten direkt in eine bereits existierende Instanz eines Objekts zu deserialisieren, was die Notwendigkeit, neue Instanzen zu erstellen, eliminiert und somit die Performance verbessert. Die Unterstützung für die Serialisierung von Polymorphismus ermöglicht es MemoryPack, Objekte zu serialisieren und zu deserialisieren, die verschiedenen Typen angehören, aber von einer gemeinsamen Basisklasse oder Schnittstelle abgeleitet sind.MemoryPack bietet sowohl eine eingeschränkte als auch eine vollständige Unterstützung für Versionstoleranz, was die Kompatibilität zwischen verschiedenen Versionen von serialisierten Daten ermöglicht. Auch zirkuläre Referenzen lassen sich mit der Bibliothek serialisieren, was besonders nützlich bei der Arbeit mit komplexen Objektstrukturen ist, bei denen Objekte auf sich selbst oder aufeinander verweisen.Zusätzlich ermöglicht die Unterstützung für PipeWriter und PipeReader Streaming-Serialisierungen. Das kommt einer Echtzeitverarbeitung zugute. MemoryPack bietet auch Unterstützung für TypeScript sowie einen speziellen Formatter für ASP.NET Core. Für die Entwicklung mit Unity bietet MemoryPack zu guter Letzt die Unterstützung für IL2CPP (Intermediate Language to C++), was eine effiziente Integration in Spiele- und VR/AR-Projekte ermöglicht.

Das Attribut [MemoryPackable]

Das Attribut [MemoryPackable] lässt sich als Annotation bei allen Klassen, Structs, Records, Record Structs und Schnittstellen nutzen. Im Standard serialisiert [MemoryPackable] öffentliche Instanzeigenschaften oder -felder. Das Attribut [MemoryPackIgnore] steht zur Verfügung, um das Serialisierungsziel zu entfernen, [MemoryPackInclude] macht ein privates Mitglied dagegen zum Serialisierungsziel, sodass MemoryPack es mit serialisiert.

Architektur und Implementierung

Das Besondere an MemoryPack ist die Nutzung von Source Generators. Diese Generatoren erzeugen im Voraus den benötigten Code zur Serialisierung und Deserialisierung, was zur Laufzeit eine erhebliche Leistungssteigerung bedeutet.Bei der Serialisierung wandelt MemoryPack Objekte
in ein kompaktes binäres Format um. Dieser Vorgang ist stark optimiert, um die Geschwindigkeit zu maximieren und den Speicherbedarf zu minimieren. Gleiches gilt für die
Deserialisierung.MemoryPack bietet Unterstützung für eine breite Palette von Datentypen:
  • primitive Datentypen wie Integer, Float, Double und Boolean,
  • komplexe Typen wie Strings, Arrays und generische Listen,
  • spezialisierte Sammlungen wie Dictionaries und HashSets,
  • benutzerdefinierte Klassen und Strukturen, einschließlich der Unterstützung für Polymorphismus,
  • Datenstrukturen mit zirkulären Referenzen.
Zudem setzt MemoryPack auf das Zero-Encoding, was da­rauf abzielt, so viel C#-Speicher wie möglich zu kopieren, ohne zusätzliche Codierungsoperationen durchzuführen. Im Gegensatz zu anderen Bibliotheken zur Serialisierung, die viele Operationen wie VarInt-Codierung, Tagging, String-Codierung und dergleichen durchführen, verwendet das MemoryPack-Format ein Zero-Encoding-Design. Dieses Design ist ähnlich wie bei FlatBuffers, benötigt aber keinen speziellen Typ, da das Ziel der Serialisierung von MemoryPack Plain Old CLR Objects (POCOs) sind.

Serialisierung und Deserialisierung in Dateien

Listing 1 greift die zu serialisierende Klasse aus dem vorherigen Beispiel auf und zeigt, wie sich die serialisierten Daten speichern und für die Deserialisierung wieder laden lassen. Der folgende Code zeigt dazu eine Klasse mit einem unterstützten Dictionary, das von MemoryPack problemlos verarbeiten wird:
Listing 1: Das Speichern und Laden von serialisierten Daten
&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; data = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt;&lt;span class="hljs-type"&gt;&lt;/span&gt; MeinObjekt { Id = &lt;span class="hljs-number"&gt;1&lt;/span&gt;, Name = &lt;span class="hljs-string"&gt;"Test"&lt;/span&gt; };&lt;br/&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; stream = &lt;br/&gt;    &lt;span class="hljs-keyword"&gt;new&lt;/span&gt;&lt;span class="hljs-type"&gt;&lt;/span&gt; FileStream(&lt;span class="hljs-string"&gt;"Memory.bin"&lt;/span&gt;, FileMode.Create))&lt;br/&gt;{&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; bytes = MemoryPackSerializer.Serialize(data);&lt;br/&gt;  stream.Write(bytes);&lt;br/&gt;}&lt;br/&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; (&lt;span class="hljs-keyword"&gt;var&lt;/span&gt; stream = &lt;br/&gt;    &lt;span class="hljs-keyword"&gt;new&lt;/span&gt;&lt;span class="hljs-type"&gt;&lt;/span&gt; FileStream(&lt;span class="hljs-string"&gt;"Memory.bin"&lt;/span&gt;, FileMode.Open))&lt;br/&gt;{&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; bytes = &lt;span class="hljs-keyword"&gt;new&lt;/span&gt;&lt;span class="hljs-type"&gt;&lt;/span&gt; byte[stream.Length];&lt;br/&gt;  stream.Read(bytes);&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;var&lt;/span&gt; deserializedData = &lt;br/&gt;    MemoryPackSerializer&lt;br/&gt;      .Deserialize&amp;lt;MeinObjekt&amp;gt;(bytes);&lt;br/&gt;} 

[MemoryPackable]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">MyData</span>
{
  <span class="hljs-keyword">public</span> Dictionary&lt;<span class="hljs-keyword">int</span>, <span class="hljs-keyword">string</span>&gt; Data { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
} 
Ist die Nutzung von nicht unterstützten Datentypen wie beispielsweise Dictionary<int, object> notwendig, ist dazu ein Formatter hinzuzufügen. Wie ein solcher eigener Formatter implementiert wird, zeigt ein Beispiel im weiteren Verlauf des Artikels.

Einfache Versionstoleranz (Tolerant-Reader-Ansatz)

Die Versionstoleranz ist ein wichtiger Aspekt beim Entwerfen langlebiger Software, insbesondere wenn es um die Se­rialisierung und Deserialisierung von Daten geht. MemoryPack bietet Mechanismen, um mit Änderungen in den Datenstrukturen umzugehen, ohne die Kompatibilität zu verlieren.Angenommen, es existiert eine Klasse Namens Person, die serialisiert und deserialisiert werden soll.

[MemoryPackable]
<span class="hljs-keyword">public</span> <span class="hljs-keyword">partial</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Person</span>
{
  [MemoryPackOrder(<span class="hljs-number">0</span>)]
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> Name { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
    
  [MemoryPackOrder(<span class="hljs-number">1</span>)]
  <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Alter { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; }
} 
In dieser ersten Version besitzt die Klasse Person zwei Felder, nämlich Name und Alter. Im Lauf des Lebens der Anwendung erhält diese Klasse zusätzliche Felder. In der Version zwei kommt das Feld Lieblingsfarbe hinzu, zu sehen in Listing 2.
Listing 2: Die zweite Version einer Klasse (einfache Versionstoleranz)
[MemoryPackable]&lt;br/&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;partial&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Person&lt;/span&gt;&lt;br/&gt;{&lt;br/&gt;  [MemoryPackOrder(&lt;span class="hljs-number"&gt;0&lt;/span&gt;)]&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;string&lt;/span&gt; Name { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }&lt;br/&gt;    &lt;br/&gt;  [MemoryPackOrder(&lt;span class="hljs-number"&gt;1&lt;/span&gt;)]&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;int&lt;/span&gt; Alter { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }&lt;br/&gt;  [MemoryPackOrder(&lt;span class="hljs-number"&gt;2&lt;/span&gt;)]&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;string&lt;/span&gt; Lieblingsfarbe { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; } &lt;br/&gt;  &lt;span class="hljs-comment"&gt;// Neues Feld in Version 2&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;}&lt;/span&gt; 
Wenn wir nun versuchen, eine serialisierte Person der Version eins zu deserialisieren, die kein Lieblingsfarbe-Feld enthält, wird MemoryPack dieses Feld auf seinen Standardwert setzen (in diesem Fall null), da es in der ursprünglichen Datenstruktur nicht vorhanden war. Der Code dafür lautet:

var <span class="hljs-attr">personV1</span> = 
  new Person { <span class="hljs-attr">Name</span> = <span class="hljs-string">"Max Mustermann"</span>, <span class="hljs-attr">Alter</span> = <span class="hljs-number">30</span> };
var <span class="hljs-attr">serializedV1</span> = MemoryPackSerializer
  .Serialize(personV1);
var <span class="hljs-attr">personV2</span> = MemoryPackSerializer
  .Deserialize&lt;Person&gt;(serializedV1);
Console.WriteLine(personV2.Lieblingsfarbe); 
// Gibt '<span class="hljs-literal">null</span>' aus, da es <span class="hljs-keyword">in</span> Version eins 
// nicht vorhanden war 
Das ist die Standardeinstellung über GenerateType.Object, in der MemoryPack eine begrenzte Schemaentwicklung unterstützt. Member von Klassen lassen sich dabei hinzufügen, aber nicht löschen. Namen von Feldern können sich ändern, die Reihenfolge aber nicht. Auch der Typ des Members lässt sich nicht ändern.Im Anwendungsfall ist das Speichern alter Daten, beispielsweise in eine Datei, und das Lesen in ein neues Schema kein Problem. Im RPC-Szenario existiert das Schema sowohl auf der Client- als auch auf der Serverseite. Hierbei ist der Client zeitlich vor dem Server zu aktualisieren. Ein aktualisierter Client hat kein Problem, sich mit dem älteren Server zu verbinden, aber ein alter Client kann sich nicht mit einem neuen Server verbinden.

Erweiterte Versionstoleranz (Schema Evolution)

MemoryPack bietet durch das Attribut GenerateType.Ver­sionTolerant eine erweiterte Funktion zur Versionstoleranz. Dieses Attribut ermöglicht es, mit Änderungen in der Klassenstruktur umzugehen, auch wenn Felder beispielsweise entfernt oder geändert werden.Gehen wir wieder von der ursprünglichen Klasse Person mit den beiden Feldern Name und Alter aus. Nun erfährt diese Klasse aber Änderungen (siehe Listing 3): Das Feld Lieblingsfarbe ist neu dazugekommen und das Feld Alter wurde entfernt. Die Schemaänderungen sind somit gravierender als bei den Beispielen zuvor.
Listing 3: Die zweite Version einer Klasse (erweiterte Versionstoleranz)
[MemoryPackable(GenerateType.VersionTolerant)]&lt;br/&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;partial&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; &lt;span class="hljs-title"&gt;Person&lt;/span&gt;&lt;br/&gt;{&lt;br/&gt;  [MemoryPackOrder(&lt;span class="hljs-number"&gt;0&lt;/span&gt;)]&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;string&lt;/span&gt; Name { &lt;span class="hljs-keyword"&gt;get&lt;/span&gt;; &lt;span class="hljs-keyword"&gt;set&lt;/span&gt;; }&lt;br/&gt;    &lt;br/&gt;  &lt;span class="hljs-comment"&gt;// Das Feld Alter wurde entfernt&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  [MemoryPackOrder(2)]&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  public string Lieblingsfarbe { get; set; } &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  // Neues Feld in der erweiterten Version&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;}&lt;/span&gt; 
In diesem Szenario erlaubt GenerateType.VersionTolerant, dass die erweiterte Version der Klasse Person ein älteres Serialisierungsformat verarbeiten kann. Das entfernte Feld Alter ignoriert MemoryPack hierbei, und das neue Feld Lieblingsfarbe wird durch die Bibliothek wie gehabt auf den Standardwert null gesetzt. Diese Versionstoleranz ist besonders nützlich in Situationen, in denen Softwareversionen über einen längeren Zeitraum hinweg entwickelt und gepflegt werden müssen. Es ist jedoch zu beachten, dass die Verwendung von GenerateType.VersionTolerant zusätzlichen Speicherplatz für die Serialisierung benötigen kann, da Informationen über die Feldstruktur beibehalten werden müssen.

Anpassungen und Erweiterbarkeit

Ein weiterer großer Vorteil von MemoryPack ist seine Anpassungsfähigkeit und Erweiterbarkeit. MemoryPack bietet verschiedene Optionen zur Anpassung. Es lassen sich benutzerdefinierte Serialisierer und Deserialisierer für spezifische Datentypen erstellen, um den Umgang mit komplexen oder unkonventionellen Datenstrukturen zu optimieren. Darüber hinaus erlaubt MemoryPack die Feinabstimmung von Serialisierungseinstellungen wie die Behandlung von Nullwerten oder die Wahl des Serialisierungsformats, was eine gezielte Kontrolle über den Serialisierungsvorgang ermöglicht.Ein Beispiel für die Erweiterbarkeit von MemoryPack in C# könnte die Implementierung eines benutzerdefinierten Serialisierers für einen speziellen Datentyp sein. Angenommen es existiert eine Klasse SpeziellesObjekt, die auf eine besondere Weise serialisiert werden soll. Listing 4 zeigt ein einfaches Beispiel, wie ein solcher benutzerdefinierte Serialisierer implementiert werden kann. Dieser benutzerdefinierte Serialisierer SpeziellesObjektSerializer erweitert die Schnittstelle IMemoryPackFormatter<T>. Die Klasse definiert die zwei Methoden Serialize und Deserialize, die jeweils die benutzerdefinierte Logik für die Serialisierung beziehungsweise Deserialisierung von SpeziellesObjekt enthalten.
Listing 4: Spezielles Objekt und eigenes Serializer-Objekt zur Verarbeitung des Objekts
[MemoryPackable]&lt;br/&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; SpeziellesObjekt&lt;br/&gt;{&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;int&lt;/span&gt; WichtigesFeld { &lt;span class="hljs-built_in"&gt;get&lt;/span&gt;; set; }&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;string&lt;/span&gt; BesonderesFeld { &lt;span class="hljs-built_in"&gt;get&lt;/span&gt;; set; }&lt;br/&gt;}&lt;br/&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; System.Buffers;&lt;br/&gt;&lt;span class="hljs-keyword"&gt;using&lt;/span&gt; MemoryPack;&lt;br/&gt;&lt;span class="hljs-keyword"&gt;namespace&lt;/span&gt; Twainsoft.Articles.Dnp.MemoryPackDemo;&lt;br/&gt;&lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;class&lt;/span&gt; SpeziellesObjektSerializer : &lt;br/&gt;    IMemoryPackFormatter&amp;lt;SpeziellesObjekt&amp;gt;&lt;br/&gt;{&lt;br/&gt;  &lt;span class="hljs-keyword"&gt;public&lt;/span&gt; &lt;span class="hljs-keyword"&gt;void&lt;/span&gt; Serialize&amp;lt;TBufferWriter&amp;gt;(&lt;br/&gt;      ref MemoryPackWriter&amp;lt;TBufferWriter&amp;gt; writer, &lt;br/&gt;      scoped ref SpeziellesObjekt? value) &lt;br/&gt;      where TBufferWriter : IBufferWriter&amp;lt;&lt;span class="hljs-keyword"&gt;byte&lt;/span&gt;&amp;gt;&lt;br/&gt;  {&lt;br/&gt;    &lt;span class="hljs-comment"&gt;// Benutzerdefinierte Logik zur Serialisierung&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    writer.WriteVarInt(value.WichtigesFeld);&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    writer.WriteString(value.BesonderesFeld);&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  public void Deserialize(ref MemoryPackReader reader, &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      scoped ref SpeziellesObjekt? value)&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  {&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    // Benutzerdefinierte Logik zur Deserialisierung&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    var wichtigesFeld = reader.ReadVarIntInt32();&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    var besonderesFeld = reader.ReadString();&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;        &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;    value = new SpeziellesObjekt { &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      WichtigesFeld = wichtigesFeld, &lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;      BesonderesFeld = besonderesFeld };&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;  }&lt;/span&gt;&lt;br/&gt;&lt;span class="hljs-comment"&gt;}&lt;/span&gt; 
Durch diese Art von Erweiterungen lassen sich die Prozesse rund um die Serialisierung und Deserialisierung in MemoryPack genau steuern und anpassen, um spezifische Anforderungen zu erfüllen.

Leistungsbewertung und Benchmarks

Leistungstests für MemoryPack zeigen signifikante Geschwindigkeitsvorteile gegenüber herkömmlichen Serialisierungsbibliotheken wie JSON.NET oder Protobuf. Benchmarks, die auf verschiedenen Datenstrukturen und Datengrößen durchgeführt wurden, illustrieren, dass MemoryPack insbesondere bei der Verarbeitung großer und komplexer Datenmengen eine deutlich höhere Geschwindigkeit erreicht. Diese Tests umfassen Szenarien wie die Serialisierung von komplexen Objektnetzwerken, großen Listen und Arrays.Während Bibliotheken wie JSON.NET aufgrund ihrer Flexibilität und Einfachheit beliebt sind, übertrifft MemoryPack diese in puncto Geschwindigkeit und Speichereffizienz.Ähnlich verhält es sich im Vergleich zu Protobuf, einer weiteren weit verbreiteten Bibliothek, die zwar für ihre Kompaktheit bekannt ist, jedoch in Leistungstests oft hinter MemoryPack zurückbleibt. Zu diesen Daten existieren zahlreiche Benchmarks in der Community, die in Blogposts wie [4] und [5] geteilt werden. Diese stellen die Ergebnisse dar, visualisieren sie zum Teil und sorgen in einer Diskussion dafür, die Ergebnisse besser einzuordnen. Zudem führt die Community diese Benchmarks regelmäßig durch, sodass sich Veränderungen auch über die Zeit beobachten lassen.

Vergleich mit anderen Serialisierungs­bibliotheken

Den Vergleich mit anderen Serialisierungsbibliotheken muss MemoryPack nicht scheuen. In puncto Geschwindigkeit ist sie den nachfolgend genannten überlegen. Allerdings liegen deren Stärken in anderen Bereichen:JSON.NET ist eine der beliebtesten Bibliotheken für JSON-Serialisierung in .NET. JSON.NET glänzt durch seine Flexibilität und einfache Integration in Webanwendungen. Es ist ideal für webbasierte Anwendungen und RESTful-APIs, wo JSON das bevorzugte Datenformat ist.Protobuf, entwickelt von Google, ist bekannt für seine schnelle und kompakte Serialisierung. Protobuf ist eine gute Wahl für plattformübergreifende Anwendungen und Microservices, während MemoryPack für .NET-spezifische Anwendungen mit Schwerpunkt auf Leistung bevorzugt wird.MessagePack ist eine effiziente binäre Serialisierungsbibliothek, die ähnlich wie MemoryPack auf Geschwindigkeit und Speichereffizienz ausgelegt ist. Während MessagePack eine gute Balance zwischen Leistung und Flexibilität bietet, liegt MemoryPack in puncto Geschwindigkeit vorne, insbesondere bei .NET-spezifischen Optimierungen.XmlSerializer ist die Standardlösung für XML-Serialisierung in .NET. Es ist ideal für Szenarien, in denen XML-Datenformate benötigt werden, jedoch ist XmlSerializer im Vergleich zu MemoryPack langsamer und weniger effizient im Umgang mit Speicher.BinaryFormatter bietet eine schnelle binäre Serialisierung, hat aber Sicherheitsbedenken aufgeworfen und wird nicht mehr empfohlen.DataContractSerializer ist eine vielseitige Serialisierungsbi­bliothek, die für WCF-Dienste entwickelt wurde. Sie bietet ­eine gute Balance zwischen Flexibilität und Leistung.

Fazit

MemoryPack hat sich als eine Serialisierungsbibliothek für .NET-Anwendungen etabliert. Mit seinen herausragenden Funktionen wie der schnellen Serialisierung und Deserialisierung, der effizienten Speicherverwaltung und der breiten Unterstützung verschiedener Datentypen stellt MemoryPack eine optimale Lösung für eine Vielzahl von Anwendungsfällen dar. Besonders hervorzuheben ist seine Leistungsfähigkeit im Vergleich zu anderen gängigen Bibliotheken wie JSON.NET, Protobuf und MessagePack.Die einfache Integration und Anpassbarkeit von MemoryPack ermöglichen eine flexible Handhabung und Anwendung in verschiedenen Softwareprojekten. Trotz einiger Einschränkungen, wie der eingeschränkten Flexibilität in bestimmten Szenarien und der Komplexität bei speziellen Anpassungen, überwiegen die Vorteile. Die aktive Entwicklung und Community-Unterstützung tragen dazu bei, dass MemoryPack kontinuierlich verbessert wird.MemoryPack verdient sich durch die zahlreichen Funktionen, gute Dokumentation und aktualisierte Versionen ein „Sehr gut“ inklusive Empfehlung. Wer in seinen Projekten viel mit Serialisierung und Deserialisierung zu tun hat, sollte einen Blick auf die Bibliothek werfen.

Fussnoten

  1. Das Cysharp-Team auf GitHub, https://github.com/Cysharp
  2. Das Projekt zu MemoryPack auf GitHub, http://www.dotnetpro.de/SL2402Frameworks1
  3. Die Fehlerbeschreibung zur Nutzung von Polymorphismus in C# 8, http://www.dotnetpro.de/SL2402Frameworks2
  4. Blogpost zu Benchmarks von MemoryPack, http://www.dotnetpro.de/SL2402Frameworks3
  5. Blogpost zu Benchmarks und Vergleichen zu MemoryPack, http://www.dotnetpro.de/SL2402Frameworks4

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Miscellaneous

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Evolutionäres Prototyping von Business-Apps - Low Code/No Code und KI mit Power Apps
Microsoft baut Power Apps zunehmend mit Features aus, um die Low-Code-/No-Code-Welt mit der KI und der professionellen Programmierung zu verbinden.
19 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige