C# Language
Binär serialisering
Sök…
Anmärkningar
Den binära serialiseringsmotorn är en del av .NET-ramverket, men exemplen som ges här är specifika för C #. Jämfört med andra serielliseringsmotorer som är inbyggda i .NET-ramverket är den binära serialiseraren snabb och effektiv och kräver vanligtvis mycket lite extra kod för att få den att fungera. Men det är också mindre tolerant mot kodändringar; det vill säga, om du serialiserar ett objekt och sedan gör en liten ändring av objektets definition, kommer det sannolikt inte att deserialisera korrekt.
Att göra ett objekt seriellt
Lägg till attributet [Serializable]
att markera ett helt objekt för binär serialisering:
[Serializable]
public class Vector
{
public int X;
public int Y;
public int Z;
[NonSerialized]
public decimal DontSerializeThis;
[OptionalField]
public string Name;
}
Alla medlemmar kommer att serialiseras om vi inte uttryckligen väljer att använda attributet [NonSerialized]
. I vårt exempel är X
, Y
, Z
och Name
serienummerade.
Alla medlemmar måste vara närvarande vid deserialisering såvida de inte är markerade med [NonSerialized]
eller [OptionalField]
. I vårt exempel krävs alla X
, Y
och Z
och deserialisering kommer att misslyckas om de inte finns i strömmen. DontSerializeThis
kommer alltid att ställas in som default(decimal)
(vilket är 0). Om Name
finns i strömmen kommer det att ställas in på det värdet, annars kommer det att ställas in till default(string)
(som är noll). Syftet med [OptionalField]
är att ge lite versionstolerans.
Kontrollera serialiseringsbeteende med attribut
Om du använder attributet [NonSerialized]
kommer den medlemmen alltid att ha sitt standardvärde efter deserialisering (ex. 0 för en int
, null för string
, falsk för en bool
, etc.), oavsett vilken initialisering som gjorts i själva objektet. (konstruktörer, förklaringar etc.). För att kompensera [OnDeserializing]
attributen [OnDeserializing]
(kallas bara FÖR deserialisering) och [OnDeserialized]
(kallas just EFTER deserializing) tillsammans med sina motsvarigheter, [OnSerializing]
och [OnSerialized]
.
Antag att vi vill lägga till ett "Betyg" i vår vektor och vi vill se till att värdet alltid börjar på 1. Såsom det skrivs nedan kommer det att vara 0 efter att ha deserialiserats:
[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;
}
}
För att lösa problemet kan vi helt enkelt lägga till följande metod i klassen för att ställa in det till 1:
[OnDeserializing]
void OnDeserializing(StreamingContext context)
{
Rating = 1M;
}
Eller, om vi vill ställa in det på ett beräknat värde, kan vi vänta på att det är slut på att deserialisera och sedan ställa in det:
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
Rating = 1 + ((X+Y+Z)/3);
}
På liknande sätt kan vi kontrollera hur saker skrivs ut med [OnSerializing]
och [OnSerialized]
.
Lägga till mer kontroll genom att implementera ISerializable
Det skulle få mer kontroll över serialisering, hur man sparar och laddar typer
Implementera ISerializable gränssnitt och skapa en tom konstruktör att kompilera
[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));
}
}
För dataserialisering kan du ange önskat namn och önskad typ
info.AddValue("_name", _name, typeof(string));
När informationen deserialiseras kan du läsa önskad typ
_name = (string)info.GetValue("_name", typeof(string));
Serialiseringssurrogat (Implementing ISerializationSurrogate)
Implementerar en serialiseringssurrogatväljare som gör att ett objekt kan utföra serialisering och deserialisering av ett annat
Gör det också möjligt att ordentligt serialisera eller deserialisera en klass som inte själv kan serialiseras
Implementera ISerializationSurrogate-gränssnitt
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;
}
}
Sedan måste du låta din IFormatter veta om surrogaten genom att definiera och initiera en SurrogateSelector och tilldela den till din IFormatter
var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());
var binaryFormatter = new BinaryFormatter
{
SurrogateSelector = surrogateSelector
};
Även om klassen inte är markerad seriellt.
//this class is not serializable
public class Item
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
Den kompletta lösningen
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);
}
}
}
Serialiseringsbindemedel
Bindemedlet ger dig en möjlighet att undersöka vilka typer som laddas i din applikationsdomän
Skapa en klass som ärvs från SerializationBinder
class MyBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.Equals("BinarySerializationExample.Item"))
return typeof(Item);
return null;
}
}
Nu kan vi kontrollera vilka typer som laddas och på denna grundval bestämma vad vi verkligen vill få
För att använda ett bindemedel måste du lägga till det i BinaryFormatter.
object DeserializeData(byte[] bytes)
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Binder = new MyBinder();
using (var memoryStream = new MemoryStream(bytes))
return binaryFormatter.Deserialize(memoryStream);
}
Den kompletta lösningen
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);
}
}
}
Vissa fickchas i bakåtkompatibilitet
Detta lilla exempel visar hur du kan tappa bakåtkompatibilitet i dina program om du inte tar hand om detta i förväg. Och sätt att få mer kontroll över serialiseringsprocessen
Först kommer vi att skriva ett exempel på den första versionen av programmet:
Version 1
[Serializable]
class Data
{
[OptionalField]
private int _version;
public int Version
{
get { return _version; }
set { _version = value; }
}
}
Och nu, låt oss anta att i den andra versionen av programmet läggs till en ny klass. Och vi måste lagra det i en matris.
Nu kommer koden att se ut så här:
Version 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; }
}
}
Och kod för serialisering och deserialisering
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);
}
Och så, vad skulle hända när du serialiserar data i programmet för v2 och försöker deserialisera dem i programmet för v1?
Du får ett undantag:
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()
Varför?
ObjectManager har en annan logik för att lösa beroenden för matriser och för referens- och värdetyper. Vi har lagt till en rad nya referenstyper som saknas i vår montering.
När ObjectManager försöker lösa beroenden bygger det diagrammet. När den ser matrisen kan den inte fixa den omedelbart, så att den skapar en dummy-referens och fixar sedan matrisen senare.
Och eftersom denna typ inte finns i monteringen och beroenden inte kan fixas. Av någon anledning tar det inte bort matrisen från listan över element för korrigeringarna och i slutet kastar det ett undantag “IncorrectNumberOfFixups”.
Det är några "gotchas" i serien. Av någon anledning fungerar det inte bara för matriser av nya referenstyper.
A Note:
Similar code will work correctly if you do not use arrays with new classes
Och det första sättet att fixa det och bibehålla kompatibiliteten?
- Använd en samling av nya strukturer snarare än klasser eller använd en ordlista (möjliga klasser), eftersom en ordbok det är en samling av keyvaluepair (dess struktur)
- Använd ISerializable om du inte kan ändra den gamla koden