シリアライズ(serialize)とはクラスを連続化することです。連続化とは元々はバイトストリームにすることをいいます。バイトストリームにできれば、保存したり、データを移動することができます。しかし現在ではXMLに変換することもシリアライズというようです。
.NETではアセンブリのメタデータにクラスのすべての情報が保存されていて、それを利用してシリアライズする仕組みが用意されています。プログラマはシリアライズのコードを書かなくてもシリアライズできます。
この属性をクラスに付けると、そのクラスはシリアライズ対象クラス(シリアライズしてもいいクラス)であることを宣言します。
#using <System.Runtime.Serialization.Formatters.Soap.dll> using namespace System; [Serializable] ref class C { char c1; Char c2; int i; double d; String^ s; System::Collections::Hashtable^ h; [NonSerialized] char* p; public: C() {} void setdata() { c1 = 'a'; c2 = L'b'; i = 123; d = 456.789; s = "XYZ"; h = gcnew System::Collections::Hashtable; h["sunday"] = "日曜日"; h["monday"] = "月曜日"; h[1] = "one"; h[2] = "two"; h["this"] = this; h["h"] = h; h["h[1]"] = h[1]; } virtual bool Equals(Object^ o) override { if(!o) return false; if(o->GetType() != this->GetType()) return false; C^ c = (C^)o; bool ret = c1 == c->c1 && c2 == c->c2 && i == c->i && d == c->d && s == c->s; if(!ret) return false; if(h->Count != c->h->Count) return false; return h["sunday"]->Equals(c->h["sunday"]) && h[1]->Equals(c->h[1]); } void check() { Console::WriteLine(this==h["this"] && h["h"]->Equals(h) && h["h[1]"]->Equals(h[1])); } }; int main() { C myobj; myobj.setdata(); { System::IO::FileStream fs("C:/TEST/BinaryData.dat", System::IO::FileMode::Create); System::Runtime::Serialization::Formatters::Binary::BinaryFormatter bf; bf.Serialize(%fs, %myobj); } { System::IO::FileStream fs("C:/TEST/SoapData.txt", System::IO::FileMode::Create); System::Runtime::Serialization::Formatters::Soap::SoapFormatter sf; sf.Serialize(%fs, %myobj); } // read { System::IO::FileStream fs("C:/TEST/BinaryData.dat", System::IO::FileMode::Open); System::Runtime::Serialization::Formatters::Binary::BinaryFormatter bf; C^ c = (C^)bf.Deserialize(%fs); Console::WriteLine(myobj.Equals(c)); c->check(); } { System::IO::FileStream fs("C:/TEST/SoapData.txt", System::IO::FileMode::Open); System::Runtime::Serialization::Formatters::Soap::SoapFormatter sf; C^ c = (C^)sf.Deserialize(%fs); Console::WriteLine(myobj.Equals(c)); c->check(); } return 0; }
[NoSerialized]を付けたメンバーはシリアル化されません。ポインターはそのアドレスがそのままシリアライズされるだけなのでシリアル化してもほとんどの場合意味はありません。
またシリアル化は互換性がほとんどなくなります。クラスを変更した場合古いデータが読めなかったり、新しいデータが古いコードで読めなかったりするので、ファイルに保存という形で多用するのはこわいです。
シリアル化、デシリアル化をする直前、直後に呼ばれる関数を定義することができます。シリアル化直前に呼ばれる関数を定義するにはクラスに以下のコードを記述します。この関数はリフレクションによって呼ばれるのでprivate:でかまいません。
[OnSerializing] void OnSerializing(StreamingContext context) { }
StreamingContextはFormatterのコンストラクタに渡した物がここに渡されます。
シリアル化の直後に呼ばれる関数は同様に以下のように定義します。
[OnSerialized] void OnSerialized(StreamingContext context) { }
デシリアル化の場合も同じなので省略します。
前回の[Serializing]などのカスタマイズはシリアライズの前後に実行されるもので、シリアライズそのもののカスタマイズではありませんでした。シリアライズ自体のカスタマイズを行うにはISerializableを実装します。ISerializableは以下のように宣言されています。
public interface ISerializable { void GetObjectData (SerializationInfo^ info, StreamingContext context); }
このインターフェースを実装したクラスはシリアライズの際、GetObjectDataが呼ばれます。
デシリアライズの際にはクラスのコンストラクタが呼ばれます、コンストラクタは以下のように定義されていなければなりません。
C(SerializationInfo^ info, StreamingContext context) { }
ISerializableにSetObjectDataが宣言されていないのは、C++/CLIにはinitonlyで宣言がある場合、コンストラクタ以外では変更することができないからです。このコンストラクタが定義されていない場合、実行時に例外が発生します。このコンストラクタはリフレクションを使って呼ばれるのでprivateでかまいません。
#using <System.Runtime.Serialization.Formatters.Soap.dll> using namespace System; using namespace System::Runtime::Serialization; [Serializable] ref class C : ISerializable { initonly int i; initonly int j; public: C() { i = 1; j = 2; } protected: virtual void GetObjectData (SerializationInfo^ info, StreamingContext context) = ISerializable::GetObjectData { info->AddValue(L"i", i); info->AddValue(L"j", j); } private: C(SerializationInfo^ info, StreamingContext context) { i = (int)info->GetValue(L"i", int::typeid); j = (int)info->GetValue(L"j", int::typeid); } }; int main() { C c; { System::IO::FileStream fs("C:/TEST/SoapData.txt", System::IO::FileMode::Create); System::Runtime::Serialization::Formatters::Soap::SoapFormatter sf; sf.Serialize(%fs, %c); } { System::IO::FileStream fs("C:/TEST/SoapData.txt", System::IO::FileMode::Open); System::Runtime::Serialization::Formatters::Soap::SoapFormatter sf; C^ c = (C^)sf.Deserialize(%fs); } return 0; }
XmlSerializerを使ったシリアライズはいままでのシリアライズの話とはまったくべつの機構です。XMLを扱う機能の一つとして存在します。XmlSerializerはまずpublicフィールドのみシリアライズします。また[Serializable]属性がなくても動きます。