C# Language
バイナリシリアル化
サーチ…
備考
バイナリシリアル化エンジンは.NETフレームワークの一部ですが、ここで示した例はC#固有の例です。 .NETフレームワークに組み込まれている他のシリアライゼーションエンジンと比較すると、バイナリシリアライザは高速かつ効率的であり、通常は動作させるために余分なコードを必要としません。ただし、コード変更にはあまり寛容ではありません。つまり、オブジェクトをシリアル化してオブジェクトの定義を少し変更すると、正しくデシリアライズされません。
オブジェクトをシリアライズ可能にする
[Serializable]
属性を追加して、オブジェクト全体をバイナリシリアル化にマークします。
[Serializable]
public class Vector
{
public int X;
public int Y;
public int Z;
[NonSerialized]
public decimal DontSerializeThis;
[OptionalField]
public string Name;
}
[NonSerialized]
属性を使用して明示的にオプトアウトしない限り、すべてのメンバーがシリアル化されます。この例では、 X
、 Y
、 Z
、およびName
はすべてシリアル化されています。
[NonSerialized]
または[OptionalField]
マークが付いていない限り、すべてのメンバーは非直列化で存在する必要があります。この例では、 X
、 Y
、およびZ
はすべて必須であり、ストリーム内に存在しない場合はデシリアライズに失敗します。 DontSerializeThis
は常にdefault(decimal)
(0)に設定されdefault(decimal)
。 Name
がストリームに存在する場合、その値に設定されます。そうでない場合、 default(string)
(null)に設定されdefault(string)
。 [OptionalField]
の目的は、少しのバージョン許容差を提供することです。
属性による直列化動作の制御
[NonSerialized]
属性を使用すると、オブジェクト自体で行われた初期化に関係なく、デシリアライズ後のデフォルト値( int
場合は0、 string
場合はnull、 bool
場合はfalseなど)は常にそのメンバーになります(コンストラクタ、宣言など)。これを補うために、 [OnDeserializing]
(単にBEFOREデシリアライズと呼ぶ)と[OnDeserialized]
(直後のデシリアライズと呼ばれる)を対応する[OnSerializing]
と[OnSerialized]
提供します。
Vectorに「Rating」を追加し、値が常に1から始まることを確認したいとします。下に書き込まれる方法は、デシリアライズ後に0になります。
[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;
}
}
この問題を解決するには、クラスの内部に次のメソッドを追加して1に設定するだけです。
[OnDeserializing]
void OnDeserializing(StreamingContext context)
{
Rating = 1M;
}
または、計算された値に設定する場合は、デシリアライズが完了してから設定するまで待つことができます。
[OnDeserialized]
void OnDeserialized(StreamingContext context)
{
Rating = 1 + ((X+Y+Z)/3);
}
同様に、 [OnSerializing]
と[OnSerialized]
を使って、物の書き方を制御することができます。
ISerializableを実装してより多くのコントロールを追加する
それは直列化のより多くの制御、型の保存と読み込みの方法を得るでしょう
ISerializableインターフェイスを実装し、コンパイルするための空のコンストラクタを作成する
[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));
}
}
データのシリアル化では、希望する名前と希望のタイプを指定できます
info.AddValue("_name", _name, typeof(string));
データがデシリアライズされると、目的のタイプを読み取ることができます
_name = (string)info.GetValue("_name", typeof(string));
直列化サロゲート(ISerializationSurrogateの実装)
あるオブジェクトが別のオブジェクトの直列化と逆シリアル化を実行できるようにする直列化代用セレクタを実装します。
同様に、それ自体は直列化できないクラスを適切に直列化または逆直列化することができます
ISerializationSurrogateインターフェイスを実装する
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;
}
}
次に、SurrogateSelectorを定義して初期化し、それをあなたのIFormatterに割り当てることによって、あなたのIFormatterに代理について知らせる必要があります
var surrogateSelector = new SurrogateSelector();
surrogateSelector.AddSurrogate(typeof(Item), new StreamingContext(StreamingContextStates.All), new ItemSurrogate());
var binaryFormatter = new BinaryFormatter
{
SurrogateSelector = surrogateSelector
};
クラスが直列化可能とマークされていなくても。
//this class is not serializable
public class Item
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
完全なソリューション
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);
}
}
}
シリアライゼーションバインダ
バインダーを使用すると、アプリケーションドメインにロードされているタイプを調べることができます
SerializationBinderから継承したクラスを作成する
class MyBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
if (typeName.Equals("BinarySerializationExample.Item"))
return typeof(Item);
return null;
}
}
これで、どのタイプがロードされているのかを確認し、実際に受け取ってほしいものを決定することができます
バインダーを使用するには、バインダーをBinaryFormatterに追加する必要があります。
object DeserializeData(byte[] bytes)
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Binder = new MyBinder();
using (var memoryStream = new MemoryStream(bytes))
return binaryFormatter.Deserialize(memoryStream);
}
完全なソリューション
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);
}
}
}
下位互換性の問題
この小さな例は、これについて事前に気をつけなければ、プログラムの下位互換性を失う可能性を示しています。シリアライズプロセスをより詳細に制御する方法
最初に、プログラムの最初のバージョンの例を書きます:
バージョン1
[Serializable]
class Data
{
[OptionalField]
private int _version;
public int Version
{
get { return _version; }
set { _version = value; }
}
}
そして今、プログラムの2番目のバージョンで新しいクラスを追加したとしましょう。そして、配列に格納する必要があります。
これでコードは次のようになります:
バージョン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; }
}
}
シリアライズとデシリアライズのコード
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);
}
そして、v2のプログラムでデータをシリアライズし、v1のプログラムでデシリアライズしようとするとどうなりますか?
あなたには例外があります:
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()
どうして?
ObjectManagerには、配列と参照型と値型の依存関係を解決するための別のロジックがあります。アセンブリには存在しない新しい参照型の配列を追加しました。
ObjectManagerが依存関係を解決しようとすると、グラフが作成されます。配列を見ると直ちにそれを修正することができないので、ダミー参照を作成して後で配列を修正します。
このタイプはアセンブリに含まれていないため、依存関係を修正することはできません。何らかの理由で、フィックスの要素のリストから配列を削除せず、最後に "IncorrectNumberOfFixups"例外がスローされます。
シリアライゼーションの過程では、いくつかの「落とし穴」があります。何らかの理由で、新しい参照型の配列に対してのみ正しく動作しません。
A Note:
Similar code will work correctly if you do not use arrays with new classes
それを修正して互換性を維持する最初の方法は?
- クラスではなく新しい構造のコレクションを使用するか、ディクショナリ(可能なクラス)を使用します。ディクショナリはkeyvalueペア(構造)のコレクションなので、
- 古いコードを変更できない場合は、ISerializableを使用してください