シリアライズ(serialize、永続化、シリアル化)とは実行中のアプリケーションにおけるオブジェクトの値(状態)を、バイナリ形式やXML形式などに変換して保存することを言います。 シリアライズすることによって、オブジェクトをファイルとして永続的に保存したり、ネットワーク経由で送信して別の場所で復元したりするといったことが出来るようになります。 シリアライズしたものをオブジェクトの形に復元することをデシリアライズ(deserialize、逆シリアル化)と言います。

.NET FrameworkのSystem.Runtime.Serialization名前空間およびSystem.Xml.Serialization名前空間には数種類のシリアライザと、その動作を制御するインターフェイス・属性が用意されています。 ここではシリアライズデシリアライズという用語を用いて、.NET Frameworkにおけるオブジェクトの永続化・復元について解説します。

なお、サンプルコードの一部でオブジェクト初期化子自動実装プロパティなど言語のバージョンによってはサポートされていない機能を使っている箇所があります。 必要に応じて読み替え・書き換えを行ってください。

§1 シリアライザを使わないシリアライズ

.NET Frameworkにはシリアライズを行うためのクラス群が用意さていますが、まずはシリアライザを使わずにシリアライズを行う場合について見てみます。 簡単な例として、BinaryWriter/BinaryReaderを使ってオブジェクトをファイルにシリアライズし、再びデシリアライズしてみます。

using System;
using System.IO;

class Account {
  public int ID;
  public string Name;

  public override string ToString()
  {
    return string.Format("{0}:{1}", ID, Name);
  }

  // シリアライズを行うメソッド
  public void Serialize(Stream stream)
  {
    BinaryWriter writer = new BinaryWriter(stream);

    writer.Write(ID);
    writer.Write(Name);
  }

  // デシリアライズを行うメソッド
  public void Deserialize(Stream stream)
  {
    BinaryReader reader = new BinaryReader(stream);

    ID = reader.ReadInt32();
    Name = reader.ReadString();
  }
}

class Sample {
  static void Main()
  {
    // シリアライズするオブジェクトを作成
    Account alice = new Account();

    alice.ID = 2;
    alice.Name = "Alice";

    Console.WriteLine(alice);

    // オブジェクトをファイルalice.binとしてシリアライズする
    using (Stream stream = File.OpenWrite("alice.bin")) {
      alice.Serialize(stream);
    }

    // ファイルの内容を元にデシリアライズする
    Account deserialized = new Account();

    using (Stream stream = File.OpenRead("alice.bin")) {
      deserialized.Deserialize(stream);
    }

    Console.WriteLine(deserialized);
  }
}
実行結果
2:Alice
2:Alice

この例のように、シリアライズする際の形式さえ定義すれば.NET Frameworkのシリアライザを使わなくてもシリアライズ・デシリアライズを行うことは可能です。 しかしこの方法では、シリアライズを行いたいクラス毎にシリアライズの形式を定義して実装する必要がある、多数のメンバ変数を含むオブジェクトのシリアライズを実装するのは面倒であるという問題があります。



§2 シリアライザを用いたシリアライズ

続いて、.NET Frameworkで用意されているシリアライザを使ってシリアライズする場合について見てみます。 次の例は、先の例と同じクラスを、BinaryFormatterを使ってファイルにシリアライズし、再びデシリアライズする例です。 (BinaryFormatterについてはBinaryFormatterで解説します)

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
class Account {
  public int ID;
  public string Name;

  public override string ToString()
  {
    return string.Format("{0}:{1}", ID, Name);
  }
}

class Sample {
  static void Main()
  {
    // シリアライズするオブジェクトを作成
    Account alice = new Account();

    alice.ID = 2;
    alice.Name = "Alice";

    Console.WriteLine(alice);

    // オブジェクトをファイルalice.binとしてシリアライズ
    using (Stream stream = File.OpenWrite("alice.bin")) {
      BinaryFormatter formatter = new BinaryFormatter();

      formatter.Serialize(stream, alice);
    }

    // ファイルの内容を元にデシリアライズする
    Account deserialized = null;

    using (Stream stream = File.OpenRead("alice.bin")) {
      BinaryFormatter formatter = new BinaryFormatter();

      deserialized = (Account)formatter.Deserialize(stream);
    }

    Console.WriteLine(deserialized);
  }
}
実行結果
2:Alice
2:Alice

この例において、シリアライズされるクラスにはSerializableという属性が付与されている以外、シリアライズ・デシリアライズに関するコードが記述されていない点に注目してください。 .NET Frameworkのシリアライザではリフレクションによってメンバ変数を自動的に探してシリアライズするようになっています。 そのため、シリアライズを行いたいクラス毎にシリアライズの形式を決める必要が無く、クラスの構造によらず同じ手順でシリアライズすることが出来るようになっています。

§3 シリアライズの種類とシリアライザ

ここでは、いくつかのシリアライズの種類とシリアライザの使い方について簡単に見ていきます。 シリアライザの種類によっていくつかの制約があり、その違いを表にまとめると次のようになります。

シリアライザの種類と制約
BinaryFormatter
SoapFormatter
XmlSerializer
SerializableAttribute 必須 付与されていなくてもよい
シリアライズするクラス パブリックでなくてもよい バブリックでなければならない
デフォルトコンストラクタ
(既定のコンストラクタ)
無くてもよい 必須
シリアライズされるフィールド・プロパティ すべてのフィールド・プロパティ パブリックなフィールド・プロパティのみ
型情報(アセンブリ名・型名) シリアライズされる シリアライズされない

なお、BinaryFormatterおよびSoapFormatterは、シリアライズされるオブジェクトが自動実装プロパティ(C#VB)を含んでいる場合でもバッキングフィールドの値を適切にシリアライズします。

§3.1 BinaryFormatter

BinaryFormatter(System.Runtime.Serialization.Formatters.Binary名前空間)は、その名のとおりバイナリ形式でのシリアライズ・デシリアライズを行うシリアライザです。 Serializeメソッドで指定されたオブジェクトを任意のStreamにシリアライズ、DeserializeメソッドでStreamからオブジェクトにデシリアライズすることが出来ます。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

[Serializable]
class Account {
  private int id;
  public string Name;

  public string ContactAddress {
    get; set;
  }

  public Account(int id)
  {
    this.id = id;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1} ({2})", id, Name, ContactAddress);
  }
}

class Sample {
  static void Main()
  {
    // シリアライズするオブジェクトを作成
    Account alice = new Account(2);

    alice.Name = "Alice";
    alice.ContactAddress = "alice@mail.example.net";

    Console.WriteLine(alice);

    // オブジェクトをファイルalice.binとしてシリアライズ
    using (Stream stream = File.OpenWrite("alice.bin")) {
      BinaryFormatter formatter = new BinaryFormatter();

      formatter.Serialize(stream, alice);
    }

    // ファイルの内容を元にデシリアライズする
    Account deserialized = null;

    using (Stream stream = File.OpenRead("alice.bin")) {
      BinaryFormatter formatter = new BinaryFormatter();

      deserialized = (Account)formatter.Deserialize(stream);
    }

    Console.WriteLine(deserialized);
  }
}
実行結果
2:Alice (alice@mail.example.net)
2:Alice (alice@mail.example.net)

以下は、上記のコードで生成されるファイルの内容をダンプしたものです。 このファイルを生成した実行ファイル名はser.exeです。

alice.binの内容
0000000  \0 001  \0  \0  \0 377 377 377 377 001  \0  \0  \0  \0  \0  \0
0000020  \0  \f 002  \0  \0  \0   :   s   e   r   ,       V   e   r   s
0000040   i   o   n   =   0   .   0   .   0   .   0   ,       C   u   l
0000060   t   u   r   e   =   n   e   u   t   r   a   l   ,       P   u
0000100   b   l   i   c   K   e   y   T   o   k   e   n   =   n   u   l
0000120   l 005 001  \0  \0  \0  \a   A   c   c   o   u   n   t 003  \0
0000140  \0  \0 002   i   d 004   N   a   m   e 037   <   C   o   n   t
0000160   a   c   t   A   d   d   r   e   s   s   >   k   _   _   B   a
0000200   c   k   i   n   g   F   i   e   l   d  \0 001 001  \b 002  \0
0000220  \0  \0 002  \0  \0  \0 006 003  \0  \0  \0 005   A   l   i   c
0000240   e 006 004  \0  \0  \0 026   a   l   i   c   e   @   m   a   i
0000260   l   .   e   x   a   m   p   l   e   .   n   e   t  \v
0000276

BinaryFormatterによるシリアライズ時の動作を制御する方法など、BinaryFormatterに関して詳しくはBinaryFormatter・SoapFormatterで解説しています。

§3.2 SoapFormatter

SoapFormatter(System.Runtime.Serialization.Formatters.Soap名前空間)は、SOAP形式でのシリアライズ・デシリアライズを行うシリアライザです。 SOAPは通信プロトコルの一種で、XMLをベースとしたリモートプロシージャコールを行うためのプロトコルです。 なお、SoapFormatterを使用する場合はアセンブリSystem.Runtime.Serialization.Formatters.Soap.dllを参照する必要があります。

BinaryFormatterと同様、Serializeメソッドでシリアライズ、Deserializeメソッドでデシリアライズすることが出来ます。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

[Serializable]
class Account {
  private int id;
  public string Name;

  public string ContactAddress {
    get; set;
  }

  public Account(int id)
  {
    this.id = id;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1} ({2})", id, Name, ContactAddress);
  }
}

class Sample {
  static void Main()
  {
    // シリアライズするオブジェクトを作成
    Account alice = new Account(2);

    alice.Name = "Alice";
    alice.ContactAddress = "alice@mail.example.net";

    Console.WriteLine(alice);

    // オブジェクトをファイルalice.soap.xmlとしてシリアライズ
    using (Stream stream = File.OpenWrite("alice.soap.xml")) {
      SoapFormatter formatter = new SoapFormatter();

      formatter.Serialize(stream, alice);
    }

    // ファイルの内容を元にデシリアライズする
    Account deserialized = null;

    using (Stream stream = File.OpenRead("alice.soap.xml")) {
      SoapFormatter formatter = new SoapFormatter();

      deserialized = (Account)formatter.Deserialize(stream);
    }

    Console.WriteLine(deserialized);
  }
}
実行結果
2:Alice (alice@mail.example.net)
2:Alice (alice@mail.example.net)

以下は、上記のコードで生成されるファイルの内容をダンプしたものです。 このファイルを生成した実行ファイル名はser.exeです。

alice.soap.xmlの内容
<SOAP-ENV:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:clr="http://schemas.microsoft.com/soap/encoding/clr/1.0" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<a1:Account id="ref-1" xmlns:a1="http://schemas.microsoft.com/clr/assem/ser%2C%20Version%3D0.0.0.0%2C%20Culture%3Dneutral%2C%20PublicKeyToken%3Dnull">
<id>2</id>
<Name id="ref-3">Alice</Name>
<_x003C_ContactAddress_x003E_k__BackingField id="ref-4">alice@mail.example.net</_x003C_ContactAddress_x003E_k__BackingField>
</a1:Account>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

SoapFormatterによるシリアライズ時の動作を制御する方法など、SoapFormatterに関して詳しくはBinaryFormatter・SoapFormatterで解説しています。

§3.3 XmlSerializer

XmlSerializer(System.Xml.Serialization名前空間)は、XML Schemaに準拠したXML形式でのシリアライズ・デシリアライズを行うシリアライザです。 BinaryFormatterやSoapFormatterとは異なり、XmlSerializerのコンストラクタでシリアライズ・デシリアライズするオブジェクトの型を指定する必要があります。

シリアライズ・デシリアライズはSerializeメソッドDeserializeメソッドで行いますが、BinaryFormatterやSoapFormatterとは異なり、StreamだけでなくXmlWriter/XmlReaderを使って行うことも出来ます。

using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class Account {
  private int id;
  public string Name;

  public string ContactAddress {
    get; set;
  }

  // デフォルトコンストラクタ
  public Account() {}

  public Account(int id)
  {
    this.id = id;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1} ({2})", id, Name, ContactAddress);
  }
}

class Sample {
  static void Main()
  {
    // シリアライズするオブジェクトを作成
    Account alice = new Account(2);

    alice.Name = "Alice";
    alice.ContactAddress = "alice@mail.example.net";

    Console.WriteLine(alice);

    // オブジェクトをファイルalice.xmlとしてシリアライズ
    using (Stream stream = File.OpenWrite("alice.xml")) {
      XmlSerializer serializer = new XmlSerializer(typeof(Account));

      serializer.Serialize(stream, alice);
    }

    // ファイルの内容を元にデシリアライズする
    Account deserialized = null;

    using (Stream stream = File.OpenRead("alice.xml")) {
      XmlSerializer serializer = new XmlSerializer(typeof(Account));

      deserialized = (Account)serializer.Deserialize(stream);
    }

    Console.WriteLine(deserialized);
  }
}
実行結果
2:Alice (alice@mail.example.net)
0:Alice (alice@mail.example.net)

実行結果において、フィールドidの値が復元されていない点に注目してください。 以下は、上記のコードで生成されるファイルの内容をダンプしたものです。

alice.xmlの内容
<?xml version="1.0"?>
<Account xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Name>Alice</Name>
  <ContactAddress>alice@mail.example.net</ContactAddress>
</Account>

XmlSerializerによるシリアライズ時の動作を制御する方法など、XmlSerializerに関して詳しくはXmlSerializerで解説しています。

§4 配列・コレクションのシリアライズ

XmlSerializerやBinaryFormatterでは単純なオブジェクトだけでなく、配列やコレクションをシリアライズすることも出来ます。 ただし、独自のコレクションクラスをシリアライズ・デシリアライズする場合は、以下の制限に注意する必要があります。

  1. IEnumerableを実装している場合、デシリアライズの際に要素を追加するためのパブリックなメソッドAddも用意している必要がある
  2. ICollectionを実装している場合は、上記に加え、シリアライズの際に要素を参照するためのインデクサ(Itemプロパティ)と、要素の数を取得するためのCountプロパティも用意している必要がある
  3. IEnumerable、ICollectionが返す値の型と、Addメソッドの引数の型は互換性がある型である必要がある

また当然ながら、配列・コレクションに格納されている個々の要素もシリアライズ可能でなければなりません。 以下の例は、XmlSerializerで配列およびListをシリアライズする例です。

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

public class Account {
  public int ID;
  public string Name;

  // デフォルトコンストラクタ
  public Account() {}

  public Account(int id, string name)
  {
    this.ID = id;
    this.Name = name;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1}", ID, Name);
  }
}

class Sample {
  static void Main()
  {
    Account[] arr = new Account[] {
      new Account(0, "Charlie"),
      new Account(1, "Eve"),
      new Account(2, "Alice"),
    };
    List<Account> list = new List<Account>(arr);

    // Account[]をシリアライズ
    using (Stream stream = File.OpenWrite("arr.xml")) {
      XmlSerializer serializer = new XmlSerializer(arr.GetType());

      serializer.Serialize(stream, arr);
    }

    // List<Account>をシリアライズ
    using (Stream stream = File.OpenWrite("list.xml")) {
      XmlSerializer serializer = new XmlSerializer(list.GetType());

      serializer.Serialize(stream, list);
    }

    // Account[]をデシリアライズ
    using (Stream stream = File.OpenRead("arr.xml")) {
      XmlSerializer serializer = new XmlSerializer(typeof(Account[]));

      Account[] deserialized = (Account[])serializer.Deserialize(stream);

      Console.WriteLine("Account[]");

      foreach (Account e in deserialized) {
        Console.WriteLine(e);
      }
    }

    // List<Account>をデシリアライズ
    using (Stream stream = File.OpenRead("list.xml")) {
      XmlSerializer serializer = new XmlSerializer(typeof(List<Account>));

      List<Account> deserialized = (List<Account>)serializer.Deserialize(stream);

      Console.WriteLine("List<Account>");

      foreach (Account e in deserialized) {
        Console.WriteLine(e);
      }
    }
  }
}
実行結果
Account[]
0:Charlie
1:Eve
2:Alice
List<Account>
0:Charlie
1:Eve
2:Alice

以下は、上記のコードで生成されるファイルの内容をダンプしたものです。

arr.xml
<?xml version="1.0"?>
<ArrayOfAccount xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Account>
    <ID>0</ID>
    <Name>Charlie</Name>
  </Account>
  <Account>
    <ID>1</ID>
    <Name>Eve</Name>
  </Account>
  <Account>
    <ID>2</ID>
    <Name>Alice</Name>
  </Account>
</ArrayOfAccount>
list.xml
<?xml version="1.0"?>
<ArrayOfAccount xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Account>
    <ID>0</ID>
    <Name>Charlie</Name>
  </Account>
  <Account>
    <ID>1</ID>
    <Name>Eve</Name>
  </Account>
  <Account>
    <ID>2</ID>
    <Name>Alice</Name>
  </Account>
</ArrayOfAccount>

なお、SoapFormatterではジェネリックな型をシリアライズすることは出来ず、List<T>などをシリアライズしようとすると以下のようにSerializationExceptionがスローされます。 そのため、List<T>はToArrayメソッドなどを使って一旦配列などの形にしてからシリアライズする必要があります。

SoapFormatterでList<T>をシリアライズしようとした場合にスローされる例外
ハンドルされていない例外: System.Runtime.Serialization.SerializationException: Soap シリアライザでは、ジェネリック タイプ System.Collections.Generic.List`1[Account] のシリアル化がサポートされていません。
   場所 System.Runtime.Serialization.Formatters.Soap.ObjectWriter.Write(WriteObjectInfo objectInfo, NameInfo memberNameInfo, NameInfo typeNameInfo)
   場所 System.Runtime.Serialization.Formatters.Soap.ObjectWriter.Serialize(Object graph, Header[] inHeaders, SoapWriter serWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers)
   場所 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph)
   場所 Sample.Main()

§5 クラスの継承とシリアライズ

SerializableAttributeはInheritedフィールドがfalseとなっているため、SerializableAttributeを付与したクラスを継承する場合は派生クラスでもSerializableAttributeを付与しないとシリアライズ可能とはなりません。

以下の例は、SoapFormatterを使ってSerializableAttributeを付与した派生クラスをシリアライズ・デシリアライズする例です。

using System;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Soap;

[Serializable]
abstract class AccountBase {
  protected int ID;

  protected AccountBase(int id)
  {
    this.ID = id;
  }
}

[Serializable]
class Account : AccountBase {
  public string Name;

  public Account(int id, string name)
    : base(id)
  {
    this.Name = name;
  }

  public override string ToString()
  {
    return string.Format("{0}:{1}", ID, Name);
  }
}

class Sample {
  static void Main()
  {
    Account alice = new Account(2, "Alice");

    Console.WriteLine(alice);

    // シリアライズ
    using (Stream stream = File.OpenWrite("alice.soap.xml")) {
      SoapFormatter formatter = new SoapFormatter();

      formatter.Serialize(stream, alice);
    }

    // デシリアライズ
    using (Stream stream = File.OpenRead("alice.soap.xml")) {
      SoapFormatter formatter = new SoapFormatter();

      Account deserialized = (Account)formatter.Deserialize(stream);

      Console.WriteLine(deserialized);
    }
  }
}
実行結果
2:Alice
2:Alice

この例で、基底クラス・派生クラスどちらか一方でもSerializableAttributeが付与されていなければ、以下のようにSerializationExceptionがスローされます。

AccountクラスにSerializableAttributeが付与されていない場合
ハンドルされていない例外: System.Runtime.Serialization.SerializationException:
アセンブリ 'ser, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' の型 'Account' はシリアル化可能として設定されていません。
   場所 System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
   場所 System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
   場所 System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.InitMemberInfo()
   場所 System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SoapAttributeInfo attributeInfo, ObjectWriter objectWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SoapAttributeInfo attributeInfo, ObjectWriter objectWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.ObjectWriter.Serialize(Object graph, Header[] inHeaders, SoapWriter serWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers)
   場所 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph)
   場所 Sample.Main()
AccountBaseクラスにSerializableAttributeが付与されていない場合
ハンドルされていない例外: System.Runtime.Serialization.SerializationException:
アセンブリ 'ser, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' の型 'AccountBase' はシリアル化可能として設定されていません。
   場所 System.Runtime.Serialization.FormatterServices.InternalGetSerializableMembers(RuntimeType type)
   場所 System.Runtime.Serialization.FormatterServices.GetSerializableMembers(Type type, StreamingContext context)
   場所 System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.InitMemberInfo()
   場所 System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.InitSerialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SoapAttributeInfo attributeInfo, ObjectWriter objectWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.WriteObjectInfo.Serialize(Object obj, ISurrogateSelector surrogateSelector, StreamingContext context, SerObjectInfoInit serObjectInfoInit, IFormatterConverter converter, SoapAttributeInfo attributeInfo, ObjectWriter objectWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.ObjectWriter.Serialize(Object graph, Header[] inHeaders, SoapWriter serWriter)
   場所 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph, Header[] headers)
   場所 System.Runtime.Serialization.Formatters.Soap.SoapFormatter.Serialize(Stream serializationStream, Object graph)
   場所 Sample.Main()

XmlSerializerはSerializableAttributeをチェックしないので、基底クラス・派生クラスともにSerializableAttributeは不要です。