C# Language
Binaire serialisatie
Zoeken…
Opmerkingen
De binaire serialisatie-engine maakt deel uit van het .NET-framework, maar de hier gegeven voorbeelden zijn specifiek voor C #. In vergelijking met andere serialisatie-engines die in het .NET-framework zijn ingebouwd, is de binaire serializer snel en efficiënt en vereist meestal weinig extra code om het te laten werken. Het is echter ook minder tolerant voor codewijzigingen; dat wil zeggen, als u een object serialiseert en vervolgens een kleine wijziging aanbrengt in de definitie van het object, zal het waarschijnlijk niet correct deserialiseren.
Een object serialiseerbaar maken
Voeg het kenmerk [Serializable]
toe om een heel object voor binaire serialisatie te markeren:
[Serializable]
public class Vector
{
public int X;
public int Y;
public int Z;
[NonSerialized]
public decimal DontSerializeThis;
[OptionalField]
public string Name;
}
Alle leden worden geserialiseerd, tenzij we ons expliciet afmelden voor het kenmerk [NonSerialized]
. In ons voorbeeld zijn X
, Y
, Z
en Name
allemaal geserialiseerd.
Alle leden moeten aanwezig zijn bij deserialisatie, tenzij gemarkeerd met [NonSerialized]
of [OptionalField]
[NonSerialized]
[OptionalField]
. In ons voorbeeld zijn X
, Y
en Z
allemaal vereist en zal deserialisatie mislukken als ze niet in de stream aanwezig zijn. DontSerializeThis
wordt altijd ingesteld op default(decimal)
(wat 0 is). Als Name
in de stream aanwezig is, wordt deze ingesteld op die waarde, anders wordt deze ingesteld op default(string)
(die null is). Het doel van [OptionalField]
is om een beetje versie-tolerantie te bieden.
Serialiseringsgedrag besturen met attributen
Als u het kenmerk [NonSerialized]
, heeft dat lid altijd de standaardwaarde na deserialisatie (bijv. 0 voor een int
, null voor string
, false voor een bool
, enz.), Ongeacht eventuele initialisatie in het object zelf (constructeurs, verklaringen, enz.). Ter compensatie worden de attributen [OnDeserializing]
(net genoemd VOORDAT u wordt gedeserialiseerd) en [OnDeserialized]
(alleen NA AFDESERIALISEREN genoemd) samen met hun tegenhangers, [OnSerializing]
en [OnSerialized]
gegeven.
Stel dat we een "Rating" aan onze Vector willen toevoegen en dat we er zeker van willen zijn dat de waarde altijd begint bij 1. De manier waarop deze hieronder is geschreven, wordt 0 na te zijn gedeserialiseerd:
[Serializable]
public class Vector
{
public int X;
public int Y;
public int Z;
[NonSerialized]
public decimal Rating = 1M;
public Vector()
{
Rating = 1M;
}
public Vector(decimal initialRating)
{
Rating = initialRating;
}
}
Om dit probleem op te lossen, kunnen we eenvoudig de volgende methode binnen de klasse toevoegen om het op 1 te zetten:
[OnDeserializing]
void OnDeserializing(StreamingContext context)
{
Rating = 1M;
}
Of, als we het op een berekende waarde willen instellen, kunnen we wachten tot het klaar is met het deserialiseren en vervolgens instellen:
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
Rating = 1 + ((X+Y+Z)/3);
}
Op dezelfde manier kunnen we bepalen hoe dingen worden weggeschreven met behulp van [OnSerializing]
en [OnSerialized]
.
Meer controle toevoegen door ISerializable te implementeren
Dat zou meer controle krijgen over serialisatie, hoe je types kunt opslaan en laden
Implementeer ISerializable interface en maak een lege constructor om te compileren
[Serializable]
public class Item : ISerializable
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public Item ()
{
}
protected Item (SerializationInfo info, StreamingContext context)
{
_name = (string)info.GetValue("_name", typeof(string));
}
public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("_name", _name, typeof(string));
}
}
Voor gegevensserialisatie kunt u de gewenste naam en het gewenste type opgeven
info.AddValue("_name", _name, typeof(string));
Wanneer de gegevens gedeserialiseerd zijn, kunt u het gewenste type lezen
_name = (string)info.GetValue("_name", typeof(string));
Serialisatie surrogaten (ISerializationSurrogate implementeren)
Implementeert een serialisatie vervangende selector waarmee een object serialisatie en deserialisatie van een ander object kan uitvoeren
Staat het ook toe om een klasse op de juiste manier te serialiseren of te deserialiseren die zelf niet serialiseerbaar is
ISerialisatieSurrogate-interface implementeren
public class ItemSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var item = (Item)obj;
info.AddValue("_name", item.Name);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
var item = (Item)obj;
item.Name = (string)info.GetValue("_name", typeof(string));
return item;
}
}
Vervolgens moet u uw IFormatter op de hoogte stellen van de surrogaten door een SurrogateSelector te definiëren en te initialiseren en deze aan uw IFormatter toe te wijzen
var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());
var binaryFormatter = new BinaryFormatter
{
SurrogateSelector = surrogateSelector
};
Zelfs als de klasse niet als serienummerbaar is gemarkeerd.
//this class is not serializable
public class Item
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
De complete oplossing
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace BinarySerializationExample
{
class Item
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
class ItemSurrogate : ISerializationSurrogate
{
public void GetObjectData(object obj, SerializationInfo info, StreamingContext context)
{
var item = (Item)obj;
info.AddValue("_name", item.Name);
}
public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector)
{
var item = (Item)obj;
item.Name = (string)info.GetValue("_name", typeof(string));
return item;
}
}
class Program
{
static void Main(string[] args)
{
var item = new Item
{
Name = "Orange"
};
var bytes = SerializeData(item);
var deserializedData = (Item)DeserializeData(bytes);
}
private static byte[] SerializeData(object obj)
{
var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());
var binaryFormatter = new BinaryFormatter
{
SurrogateSelector = surrogateSelector
};
using (var memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, obj);
return memoryStream.ToArray();
}
}
private static object DeserializeData(byte[] bytes)
{
var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());
var binaryFormatter = new BinaryFormatter
{
SurrogateSelector = surrogateSelector
};
using (var memoryStream = new MemoryStream(bytes))
return binaryFormatter.Deserialize(memoryStream);
}
}
}
Serialisatiebinder
De map geeft u de mogelijkheid om te inspecteren welke typen in uw toepassingsdomein worden geladen
Maak een klasse die is overgenomen van SerializationBinder
class MyBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.Equals("BinarySerializationExample.Item"))
return typeof(Item);
return null;
}
}
Nu kunnen we controleren welke typen worden geladen en op basis hiervan beslissen wat we echt willen ontvangen
Voor het gebruik van een binder moet u deze toevoegen aan de BinaryFormatter.
object DeserializeData(byte[] bytes)
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Binder = new MyBinder();
using (var memoryStream = new MemoryStream(bytes))
return binaryFormatter.Deserialize(memoryStream);
}
De complete oplossing
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace BinarySerializationExample
{
class MyBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.Equals("BinarySerializationExample.Item"))
return typeof(Item);
return null;
}
}
[Serializable]
public class Item
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
class Program
{
static void Main(string[] args)
{
var item = new Item
{
Name = "Orange"
};
var bytes = SerializeData(item);
var deserializedData = (Item)DeserializeData(bytes);
}
private static byte[] SerializeData(object obj)
{
var binaryFormatter = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, obj);
return memoryStream.ToArray();
}
}
private static object DeserializeData(byte[] bytes)
{
var binaryFormatter = new BinaryFormatter
{
Binder = new MyBinder()
};
using (var memoryStream = new MemoryStream(bytes))
return binaryFormatter.Deserialize(memoryStream);
}
}
}
Sommige problemen met achterwaartse compatibiliteit
Dit kleine voorbeeld laat zien hoe u achterwaartse compatibiliteit in uw programma's kunt verliezen als u hier niet van tevoren op let. En manieren om meer controle over het serialisatieproces te krijgen
Eerst zullen we een voorbeeld schrijven van de eerste versie van het programma:
Versie 1
[Serializable]
class Data
{
[OptionalField]
private int _version;
public int Version
{
get { return _version; }
set { _version = value; }
}
}
En laten we nu aannemen dat in de tweede versie van het programma een nieuwe klasse is toegevoegd. En we moeten het in een array opslaan.
Nu ziet de code er zo uit:
Versie 2
[Serializable]
class NewItem
{
[OptionalField]
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
[Serializable]
class Data
{
[OptionalField]
private int _version;
public int Version
{
get { return _version; }
set { _version = value; }
}
[OptionalField]
private List<NewItem> _newItems;
public List<NewItem> NewItems
{
get { return _newItems; }
set { _newItems = value; }
}
}
En code voor serialiseren en deserialiseren
private static byte[] SerializeData(object obj)
{
var binaryFormatter = new BinaryFormatter();
using (var memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, obj);
return memoryStream.ToArray();
}
}
private static object DeserializeData(byte[] bytes)
{
var binaryFormatter = new BinaryFormatter();
using (var memoryStream = new MemoryStream(bytes))
return binaryFormatter.Deserialize(memoryStream);
}
En dus, wat zou er gebeuren als u de gegevens in het programma van v2 serialiseert en ze probeert te deserialiseren in het programma van v1?
Je krijgt een uitzondering:
System.Runtime.Serialization.SerializationException was unhandled
Message=The ObjectManager found an invalid number of fixups. This usually indicates a problem in the Formatter.Source=mscorlib
StackTrace:
at System.Runtime.Serialization.ObjectManager.DoFixups()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at Microsoft.Samples.TestV1.Main(String[] args) in c:\Users\andrew\Documents\Visual Studio 2013\Projects\vts\CS\V1 Application\TestV1Part2\TestV1Part2.cs:line 29
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Waarom?
De ObjectManager heeft een andere logica om afhankelijkheden voor arrays en voor referentie- en waardetypen op te lossen. We hebben een reeks nieuwe referentietypes toegevoegd die niet aanwezig zijn in onze assemblage.
Wanneer ObjectManager probeert afhankelijkheden op te lossen, wordt de grafiek opgebouwd. Wanneer het de array ziet, kan het deze niet onmiddellijk repareren, zodat het een dummy-referentie maakt en de array later corrigeert.
En omdat dit type niet in de assembly zit en afhankelijkheden niet kunnen worden opgelost. Om de een of andere reden wordt de array niet uit de lijst met elementen voor de fixes verwijderd en wordt aan het einde een uitzondering "IncorrectNumberOfFixups" gegenereerd.
Het zijn enkele 'gotcha's' in het proces van serialisatie. Om de een of andere reden werkt het niet correct alleen voor arrays van nieuwe referentietypen.
A Note:
Similar code will work correctly if you do not use arrays with new classes
En de eerste manier om het te repareren en compatibiliteit te behouden?
- Gebruik een verzameling nieuwe structuren in plaats van klassen of gebruik een woordenboek (mogelijke klassen), want een woordenboek is een verzameling keyvaluepair (structuur)
- Gebruik ISerializable als u de oude code niet kunt wijzigen