C Sharpの基礎 - シリアライズ

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動

概要

クラスの内容をバイナリファイルに保存する簡単な方法として、BinaryFormatterクラスを使う方法がある。
BinaryFormatterクラスを使用してクラスをシリアル化して、FileStreamでファイルに書き込む。

BinaryFormatterクラスの代わりにSoapFormatterクラスを使って、XMLファイルに保存することもできる。
SoapFormatterクラスの使い方は、ここで記載している方法とほぼ同様である。


バイナリファイル

シリアル化

以下に示すTestClassクラスを、BinaryFormatterクラスを使用してバイナリファイルに保存する。

 public class TestClass
 {
    private string _message;
    private int _number;
 
    public string Message
    {
       get {return _message;}
       set {_message = value;}
    }
 
    public int Number
    {
       get {return _number;}
       set {_number = value;}
    }

    public TestClass(string str, int num)
    {
       _message = str;
       _number  = num;
    }
 }


まず、以下のようにTestClassオブジェクトの先頭にSerializableAttribute属性を付加する。

 [Serializable()]
 public class TestClass
 {
    // ...省略
 }


SaveToBinaryFileメソッドを使用して、バイナリファイルとしてシリアル化する。
また、LoadFromBinaryFileメソッドを使用して、バイナリファイルから逆シリアル化する。

 using System;
 using System.IO;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization.Formatters.Binary;
 
 public class MainClass
 {
    public static void Main()
    {
       // 保存先のファイル名
       string fileName = @"C:\test.obj";
 
       // TestClassオブジェクトを作成
       TestClass obj1 = new TestClass("テスト", 123);
 
       // オブジェクトの内容をファイルに保存する
       SaveToBinaryFile(obj1, fileName);
 
       // オブジェクトの内容をファイルから読み込み復元する
       TestClass obj2 = (TestClass)LoadFromBinaryFile(fileName);
 
       // 読み込んだオブジェクトの内容を表示
       Console.WriteLine(obj2.Message);
       Console.WriteLine(obj2.Number);
 
       Console.ReadLine();
    }
 
    /// <summary>
    /// オブジェクトの内容をファイルから読み込み復元する
    /// </summary>
    /// <param name="path">読み込むファイル名</param>
    /// <returns>復元されたオブジェクト</returns>
    public static object LoadFromBinaryFile(string path)
    {
       FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);
       BinaryFormatter f = new BinaryFormatter();
       // 読み込んで逆シリアル化する
       object obj = f.Deserialize(fs);
       fs.Close();
 
       return obj;
    }
 
    /// <summary>
    /// オブジェクトの内容をファイルに保存する
    /// </summary>
    /// <param name="obj">保存するオブジェクト</param>
    /// <param name="path">保存先のファイル名</param>
    public static void SaveToBinaryFile(object obj, string path)
    {
       FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
       BinaryFormatter bf = new BinaryFormatter();
       // シリアル化して書き込む
       bf.Serialize(fs, obj);
       fs.Close();
    }
 }


シリアル化しないフィールドを指定する

上記のように、BinaryFormatterクラスを使用してシリアル化できるものは、クラスのフィールドである。
この時、publicフィールド、protectedフィールド、privateフィールドも保存される。

保存しないフィールドには、NonSerializedAttribute属性を付加する。

以下の例では、TestClassの_numberフィールドをシリアル化の対象から除外し、保存されないようにしている。

 [Serializable()]
 public class TestClass
 {
    private string _message;
 
    [NonSerialized()]
    private int _number;
    // (省略)
 }



XMLファイル

シリアル化できるもの

  • 対象となるクラスの条件
    • publicである
      publicではないクラスをシリアル化しようとすると、以下に示すInvalidOperationException例外が発生する。
      "保護レベルの設定が原因で 'ProjectName.ClassName' にアクセスできません。パブリックの型のみ処理できます。"
    • publicな既定のコンストラクタを有する
      publicな既定のコンストラクタを持たないクラスをシリアル化しようとすると、以下に示すInvalidOperationException例外が発生する。
      "ProjectName.ClassName にはパラメーターを持たないコンストラクターが含まれていないため、これをシリアル化することはできません。"

  • 対象となる要素
    • publicフィールド
    • getアクセサおよびsetアクセサを有するpublicプロパティ


XMLの書式の変更

シリアル化の対象となるクラスおよびクラスのメンバに属性を付加することにより、保存するXMLファイルの書式を変更することができる。

XMLシリアル化を制御する属性
属性 機能 適用前 適用後
XmlAttribute メンバをXMLの属性とする
<ClassName>
   <field>256</field>
</ClassName>
<ClassName field="256" />
XmlArray 配列の名前を変更する
<MemberName>
   <TypeName>
      <field>256</field>
   </TypeName>
</MemberName>
<OriginalName>
   <TypeName>
      <field>256</field>
   </TypeName>
</OriginalName>
XmlArrayItem 配列の要素名を、型名から任意の名前に変更する。
<MemberName>
   <TypeName>
      <field>256</field>
   </TypeName>
</MemberName>
<MemberName>
   <OriginalName>
      <field>256</field>
   </TypeName>
</MemberName>


シリアル化

以下のクラスを、XMLファイルに保存する手順を記載する。

 public class TestClass
 {
    private string _message;
    private int _number;
 
    public string Message
    {
       get {return _message;}
       set {_message = value;}
    }
 
    public int Number
    {
       get {return _number;}
       set {_number = value;}
    }

    public TestClass(string str, int num)
    {
       _message = str;
       _number  = num;
    }
 }


まず、次のようにTestClassオブジェクトにSerializableAttribute属性を付加する。

 [Serializable()]
 public class TestClass
 {
    // (省略)
 }


 class Program
 {
    static void Main()
    {
       string fileName = "$HOME/Serialize.xml";
 
       var myClass = new TestClass();
       myClass.publicField = 1;
       myClass.Property    = 2;
 
       using(var writer = new System.IO.StreamWriter(fileName))
       {
          // シリアル化の対象となるクラスの型を指定して XmlSerializerを作成する
          var serializer = new System.Xml.Serialization.XmlSerializer(typeof(TestClass));
 
          // 指定のオブジェクトをシリアル化する
          serializer.Serialize(writer, TestClass);
       }
    }
 }


上記の例を実行する時、以下のようなXMLファイルが作成される。

 <?xml version="1.0" encoding="utf-8"?>
 <ClassName xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <publicField>1</publicField>
 <Property>2</Property>
 </ClassName>


上記の例では、XmlSerializerクラスのコンストラクタにTypeクラスを指定しているが、これ以外にも下表に示すコンストラクタがある。

XmlSerializerクラスのコンストラクタ
XmlSerializer(Type)
XmlSerializer(Type, String)
XmlSerializer(Type, Type[])
XmlSerializer(Type, XmlAttributeOverrides)
XmlSerializer(Type, XmlAttributeOverrides, Type[], XmlRootAttribute, String)
XmlSerializer(Type, XmlAttributeOverrides, Type[], XmlRootAttribute, String, String)
XmlSerializer(Type, XmlRootAttribute)
XmlSerializer(XmlTypeMapping)

XmlSerializerクラス (System.Xml.Serialization) | MSDN


※注意
XmlSerializerクラスのインスタンスを生成する時、以下に示すFileNotFoundException例外が発生することがある。

これは、XmlSerializerクラスのアセンブリが用意されていないことが原因であり、sgen.exeファイルを実行して、このファイルを作成することで解決できる。
もしくは、このファイルが存在しない場合は自動的に作成されるため、この例外を無視しても問題ない。

ファイルまたはアセンブリ 'mscorlib.XmlSerializers, Version=x.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'、またはその依存関係の 1 つが読み込めませんでした。指定されたファイルが見つかりません。


配列のシリアル化

配列やListクラスの場合も、同様にシリアル化できる。

 // int型配列
 int[] array = new int[]{1, 2, 3};
 var serializer = new XmlSerializer(typeof(int[]));
 
 // Listクラス
 List<int> list = new List<int>(new int[]{1, 2, 3});
 var serializer = new XmlSerializer(typeof(List<int>));


これらは、以下のように出力される。

 <?xml version="1.0" encoding="utf-8"?>
 <ArrayOfInt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <int>1</int>
    <int>2</int>
    <int>3</int>
 </ArrayOfInt>


しかし、ArrayListクラスのようなObject型を返すクラスでは型を特定できないため、以下に示すInvalidOperationException例外が発生する。

型 ProjectName.ClassName は指定されていません。スタティックに使用できない型を指定するには XmlInclude または SoapInclude 属性を使ってください。


この場合、XmlSerializerクラスのコンストラクタにおいて、Object型に含まれる可能性のある型を全て指定する。
XmlSerializerクラスのコンストラクタ (Type, Type[]) (System.Xml.Serialization) | MSDN

 var obj = new ArrayList(new int[]{1, 2, 3});
 var serializer = new XmlSerializer(typeof(ArrayList), new Type[]{typeof(int)});


 <?xml version="1.0" encoding="utf-8"?>
 <ArrayOfAnyType xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <anyType xsi:type="xsd:int">1</anyType>
    <anyType xsi:type="xsd:int">2</anyType>
    <anyType xsi:type="xsd:int">3</anyType>
 </ArrayOfAnyType>


逆シリアル化

作成されたXMLファイルは、逆シリアル化によりオブジェクトに復元できる。

以下の例では、XMLファイルをストリームから読み込み、オブジェクトに変換している。

Windowsの場合、逆シリアル化を行うには、環境変数TEMPにより定義される一時フォルダに書き込むため、アクセス権が必要となる場合がある。

 class Program
 {
    static void Main()
    {
       string fileName = "$HOME/Serialize.xml";
       TestClass myClass;
 
       using(var reader = new System.IO.StreamReader(fileName))
       {
          var serializer = new System.Xml.Serialization.XmlSerializer(typeof(TestClass));
 
          myClass = (TestClass)serializer.Deserialize(reader);
       }
    }
 }