「DataContractSerializerを使って、オブジェクトのXMLシリアル化、逆シリアル化を行う」では、DataContractSerializerを使ってオブジェクトをXMLファイルに保存、復元する方法を紹介しました。例えばこの方法でアプリケーションの設定を保存する場合、各々の設定をプロパティとして持っているクラス(以下、「設定クラス」と呼ぶ)を作成して、そのインスタンスをシリアル化することになるでしょう。アプリケーションのバージョンが上がって設定が増えても(つまり、設定クラスのプロパティが増えても)、正常に保存、復元ができるでしょうか?
バージョンが上がって設定が増えた時、新しいバージョンで古い設定を読み込むと、新たに加えられたプロパティはその型の初期値になります。また、古いバージョンで新しい設定を読み込むと、新たに加えられたプロパティは無視されます。そのため、新たに設定が追加されても大きな問題はありません。
しかし、新しいバージョンの設定を古いバージョンのアプリケーションで読み込み、古いアプリケーションで設定を保存すると、新しいバージョンで追加された設定は消えてしまいます。これはごくあたり前のことですが、このあたり前のことを防ぐ方法があります。その方法は、設定クラスにIExtensibleDataObjectインターフェイスを実装し、ラウンドトリップを有効にすることです。
IExtensibleDataObjectインターフェイスを実装するのは簡単です。以下のように、ExtensionDataObject型のExtensionDataプロパティを作成するだけです。
Imports System.Runtime.Serialization 'IExtensibleDataObjectを実装したクラス <DataContract()> _ Public Class SampleSettings Implements IExtensibleDataObject 'シリアル化するメンバ <DataMember()> _ Public Message As String Private _extensionData As ExtensionDataObject Public Property ExtensionData1 As ExtensionDataObject _ Implements IExtensibleDataObject.ExtensionData Get Return _extensionData End Get Set(ByVal value As ExtensionDataObject) _extensionData = value End Set End Property End Class
using System.Runtime.Serialization; //IExtensibleDataObjectを実装したクラス [DataContract] public class SampleSettings : IExtensibleDataObject { //シリアル化するメンバ [DataMember] public string Message; private ExtensionDataObject _extensionData; public ExtensionDataObject ExtensionData { get { return _extensionData; } set { _extensionData = value; } } }
IExtensibleDataObjectを実装したクラスを使うとどうなるか、以下のサンプルで実際に試してみましょう。
Imports System.Xml Imports System.Runtime.Serialization 'バージョン1の設定クラス <DataContract(Name:="SampleSettings")> _ Public Class SampleSettingsV1 Implements IExtensibleDataObject <DataMember> _ Public Message As String Private _extensionData As ExtensionDataObject Public Property ExtensionData() As ExtensionDataObject _ Implements IExtensibleDataObject.ExtensionData Get Return _extensionData End Get Set(value As ExtensionDataObject) _extensionData = value End Set End Property End Class 'バージョン2の設定クラス <DataContract(Name:="SampleSettings")> _ Public Class SampleSettingsV2 Implements IExtensibleDataObject <DataMember> _ Public Message As String '追加された設定 <DataMember(Order:=2)> _ Public ID As Integer Private _extensionData As ExtensionDataObject Public Property ExtensionData() As ExtensionDataObject _ Implements IExtensibleDataObject.ExtensionData Get Return _extensionData End Get Set(value As ExtensionDataObject) _extensionData = value End Set End Property End Class Public Class Program ''' <summary> ''' 設定を保存する ''' </summary> ''' <typeparam name="T">設定クラスの型</typeparam> ''' <param name="fileName">保存先のファイル名</param> ''' <param name="settings">設定が格納されたオブジェクト</param> Public Shared Sub SaveSettings(Of T)(fileName As String, settings As T) Dim serializer As New DataContractSerializer(GetType(T)) Dim setting As New XmlWriterSettings() setting.Encoding = New System.Text.UTF8Encoding() Using xw As XmlWriter = XmlWriter.Create(fileName, setting) serializer.WriteObject(xw, settings) End Using End Sub ''' <summary> ''' 設定を読み込む ''' </summary> ''' <typeparam name="T">設定クラスの型</typeparam> ''' <param name="fileName">読み込むファイル名</param> ''' <returns>設定を格納したオブジェクト</returns> Public Shared Function LoadSettings(Of T)(fileName As String) As T Dim serializer As New DataContractSerializer(GetType(T)) Using xr As XmlReader = XmlReader.Create(fileName) Return DirectCast(serializer.ReadObject(xr), T) End Using End Function 'エントリポイント Public Shared Sub Main(args As String()) Dim fileNameV1 As String = "C:\test\V1.xml" Dim fileNameV2 As String = "C:\test\V2.xml" '保存する設定を作成する Dim settingsV2 As New SampleSettingsV2() settingsV2.ID = 2 settingsV2.Message = "こんにちは。" 'バージョン2の設定を書き込む SaveSettings(fileNameV2, settingsV2) 'バージョン2の設定をバージョン1で読み込む Dim settingsV1 As SampleSettingsV1 = _ LoadSettings(Of SampleSettingsV1)(fileNameV2) 'バージョン1のまま書き込む SaveSettings(fileNameV1, settingsV1) 'バージョン2でバージョン1の設定を読み込む Dim settingsV2_2 As SampleSettingsV2 = _ LoadSettings(Of SampleSettingsV2)(fileNameV1) 'バージョン2にしかないIDプロパティが復元されていることを確認する Console.WriteLine(settingsV2_2.ID) End Sub End Class
using System; using System.Xml; using System.Runtime.Serialization; //バージョン1の設定クラス [DataContract(Name = "SampleSettings")] public class SampleSettingsV1 : IExtensibleDataObject { [DataMember] public string Message; private ExtensionDataObject _extensionData; public ExtensionDataObject ExtensionData { get { return _extensionData; } set { _extensionData = value; } } } //バージョン2の設定クラス [DataContract(Name = "SampleSettings")] public class SampleSettingsV2 : IExtensibleDataObject { [DataMember] public string Message; //追加された設定 [DataMember(Order = 2)] public int ID; private ExtensionDataObject _extensionData; public ExtensionDataObject ExtensionData { get { return _extensionData; } set { _extensionData = value; } } } public class Program { /// <summary> /// 設定を保存する /// </summary> /// <typeparam name="T">設定クラスの型</typeparam> /// <param name="fileName">保存先のファイル名</param> /// <param name="settings">設定が格納されたオブジェクト</param> public static void SaveSettings<T>(string fileName, T settings) { DataContractSerializer serializer = new DataContractSerializer(typeof(T)); XmlWriterSettings setting = new XmlWriterSettings(); setting.Encoding = new System.Text.UTF8Encoding(); using (XmlWriter xw = XmlWriter.Create(fileName,setting)) { serializer.WriteObject(xw, settings); } } /// <summary> /// 設定を読み込む /// </summary> /// <typeparam name="T">設定クラスの型</typeparam> /// <param name="fileName">読み込むファイル名</param> /// <returns>設定を格納したオブジェクト</returns> public static T LoadSettings<T>(string fileName) { DataContractSerializer serializer = new DataContractSerializer(typeof(T)); using (XmlReader xr = XmlReader.Create(fileName)) { return (T)serializer.ReadObject(xr); } } //エントリポイント public static void Main(string[] args) { string fileNameV1 = @"C:\test\V1.xml"; string fileNameV2 = @"C:\test\V2.xml"; //保存する設定を作成する SampleSettingsV2 settingsV2 = new SampleSettingsV2(); settingsV2.ID = 2; settingsV2.Message = "こんにちは。"; //バージョン2の設定を書き込む SaveSettings(fileNameV2, settingsV2); //バージョン2の設定をバージョン1で読み込む SampleSettingsV1 settingsV1 = LoadSettings<SampleSettingsV1>(fileNameV2); //バージョン1のまま書き込む SaveSettings(fileNameV1, settingsV1); //バージョン2でバージョン1の設定を読み込む SampleSettingsV2 settingsV2_2 = LoadSettings<SampleSettingsV2>(fileNameV1); //バージョン2にしかないIDプロパティが復元されていることを確認する Console.WriteLine(settingsV2_2.ID); } }
このサンプルでは、バージョン1の設定クラスを想定した「SampleSettingsV1」と、バージョン2の設定クラスを想定した「SampleSettingsV2」の2つのクラスを用意しています。SampleSettingsV2にはSampleSettingsV1にはない「IDプロパティ」が追加されています。ここではテストのために2つの設定クラスを作っただけで、実際は1つの設定クラス「SampleSettings」の2つのバージョンだと思ってください。2つの設定クラスが同じものとして扱われるように、DataContractAttributeのNameプロパティを双方とも「SampleSettings」にしています。
SampleSettingsV2クラスのIDプロパティでは、DataMemberAttribute属性のOrderプロパティに値を設定しています。これは、「ベスト プラクティス : データ コントラクトのバージョン管理」にあるガイドラインに従った処置です。このガイドラインによると、新しく追加されたメンバはOrderプロパティを使用して既存のデータメンバの後に配置されるようにすべきとされています。Orderプロパティについては、「DataContractSerializerを使って、オブジェクトのXMLシリアル化、逆シリアル化を行う」で説明しています。
このコードを実行すると、まず新規に作成したSampleSettingsV2オブジェクトをXMLシリアル化して、ファイル「V2.xml」に保存します。次に「V2.xml」を読み込んで、SampleSettingsV1オブジェクトに逆シリアル化しています。通常ならばこの時SampleSettingsV2クラスにしかないIDプロパティの値は失われます。
IDプロパティの値が失われていないことを確認するために、SampleSettingsV1オブジェクトを「V1.xml」に保存し、「V1.xml」からSampleSettingsV2を復元します。すると元の値に復元されていることが確認できます。
V2.xmlとV1.xmlの中身を確認してみましょう。V2.xmlは次のようになります(見やすいように改行を入れています)。
<?xml version="1.0" encoding="utf-8"?> <SampleSettings xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/"> <Message>こんにちは。</Message> <ID>2</ID> </SampleSettings>
V1.xmlは以下のようになります。V2.xmlと全く同じ内容で、IDプロパティの値も保存されていることが確認できます。
<?xml version="1.0" encoding="utf-8"?> <SampleSettings xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/"> <Message>こんにちは。</Message> <ID>2</ID> </SampleSettings>