DataContractSerializerクラス
もう一つのXML形式シリアライザ
XML形式でのシリアライズについてXmlSerializerクラスを説明しましたが、DataContractSerializerというシリアライザを使用することもできます。
XmlSerializerクラスは以下のような制限があります。
- シリアライズ対象のクラスはpublicな領域に定義し、クラス自体もpublicで定義しなければならない
- publicメンバ(フィールド、プロパティ)しかシリアライズ対象にできない
- デフォルトコンストラクター(引数なしコンストラクター)が必要
- Dictionaryクラス、Hashtableクラスのシリアライズはできない
DataContractSerializerクラスはこれらの制限をクリアできます。
特にDictionaryクラスやHashtableクラスをシリアライズ対象に含めたい場合はDataContractSerializerクラスを使用するのが簡単です。
ただし、XmlSerializerクラスでは出力されるXMLをわりと細かく制御できましたが、DataContractSerializerクラスはそこまで細かい指定はできません。
DataContractSerializerクラスはSystem.Runtime.Serialization
名前空間にあるので、usingディレクティブに追加しておきます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//↓を追加
using System.Runtime.Serialization;
using System.IO;
DataContractSerializerクラスは、シリアライズ対象のクラスがpublicな空間にあり、引数なしコンストラクタを持つ場合、そのpublicなメンバ(フィールド、プロパティ)をシリアライズすることができます。
この場合、復元時には引数なしコンストラクタが呼ばれます。
(XmlSerializerクラスと同じ動作)
publicメンバにIgnoreDataMemberAttribute
属性をつけるとシリアライズの対象から除かれます。
引数なしコンストラクタを持たないクラスをシリアライズするには、クラスにDataContractAttribute
属性を適用します。
そして、シリアライズに含めるメンバにDataMemberAttribute
属性を適用します。
DataMemberAttribute
属性を適用しない場合、publicメンバであってもシリアライズ対象になりません。
今回は以下のクラスをシリアライズしてみます。
class Program
{
[DataContract]
public class SimpleClass
{
[DataMember]
public int Num;
[DataMember]
private string Str; //private
//引数なしコンストラクタを持たないクラス
public SimpleClass(int n, string s)
{
Num = n;
Str = s;
}
//確認用
public void Print()
{
Console.WriteLine(Num);
Console.WriteLine(Str);
}
}
static void Main(string[] args)
{
}
}
シリアライズ
実際にシリアライズするには、まずDataContractSerializerクラスのインスタンスを生成します。
コンストラクタにはtypeof
演算子を使用して目的のクラスのTypeオブジェクトを指定します。
このDataContractSerializerインスタンスはコンストラクタに指定したクラス専用のシリアライザとなります。
シリアライズにはWriteObject
メソッドを使用します。
このメソッドはオーバーロードがいくつかありますが、まずは最も簡単なStream派生クラスを使用する方法を説明します。
第一引数はStream派生クラスのインスタンス、第二引数はシリアライズする実際のオブジェクト(クラスインスタンス)を指定します。
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass(1, "abc");
using (FileStream fs = new FileStream("test.xml", FileMode.Create))
{
DataContractSerializer ds =
new DataContractSerializer(typeof(SimpleClass));
ds.WriteObject(fs, sc);
}
}
出力されるXMLは以下のようになります。
<Program.SimpleClass xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Num>1</Num>
<Str>abc</Str>
</Program.SimpleClass>
このサイト上では、見やすいようにXMLデータに改行とインデント(字下げ、タブ文字)を挿入していますが、実際は改行もインデントもなしの一行で出力されます。
データ量が多くなると人が直接読み書きするには不便なので、そのような用途にはXmlSerializerクラスを使用するか、後述するXmlWriterクラスを併用したほうが良いです。
これ以降のXML出力データは、特に説明がない限り当サイト側で改行やインデントを挿入しています。
その場合、実際に出力されるのは一行のデータです。
デシリアライズ
シリアライズされたXMLをデシリアライズ(復元)するにはReadObject
メソッドを使用します。
using (FileStream fs = new FileStream("test.xml", FileMode.Open))
{
DataContractSerializer ds =
new DataContractSerializer(typeof(SimpleClass));
SimpleClass sc2 = (SimpleClass)ds.ReadObject(fs);
sc2.Print();
}
1 abc
引数は対象のXMLデータを読み取るStream派生クラスを指定します。
戻り値はデシリアライズされたクラスのインスタンスですが、object型で返されるのでキャストが必要です。
コンストラクタは呼ばれない
DataContractAttribute
属性が付いているクラスに対しては、DataContractSerializerクラスは復元時にコンストラクタを呼び出しません。
復元するデータ内に値が存在しないメンバは既定値で初期化されます。
[DataContract]
public class SimpleClass
{
[DataMember]
public int Num;
//DataMember属性のないフィールドは
//シリアライズの対象にならない
public string Str;
//引数なしコンストラクタ
public SimpleClass()
{
Num = 99;
Str = "xyz";
}
public SimpleClass(int n, string s)
{
Num = n;
Str = s;
}
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass(1, "abc");
using (FileStream fs = new FileStream("test.xml", FileMode.Create))
{
DataContractSerializer ds =
new DataContractSerializer(typeof(SimpleClass));
ds.WriteObject(fs, sc);
}
using (FileStream fs = new FileStream("test.xml", FileMode.Open))
{
DataContractSerializer ds =
new DataContractSerializer(typeof(SimpleClass));
SimpleClass sc2 = (SimpleClass)ds.ReadObject(fs);
Console.WriteLine(sc2.Num);
Console.WriteLine(sc2.Str == null ? "(null)" : sc2.Str);
}
}
1 (null)
このクラスのフィールドStr
は、通常であれば引数なしコンストラクタもしくは引数ありコンストラクタで指定した値で初期化されます。
しかしDataContractSerializerクラスはコンストラクタを呼ばないので、DataMemberAttribute
属性のないフィールドStr
はシリアライズの対象から外れ、デシリアライズ時にはXMLにデータがないため既定値であるnullで初期化されます。
名前空間とクラス構造の指定(省略)
先ほど作成したXMLデータには、C#の名前空間とクラスの構造が保存されています。
(見やすいようにタグの属性の手前に改行を挿入しています)
<Program.SimpleClass
xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1"
xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Num>1</Num>
<Str>abc</Str>
</Program.SimpleClass>
タグ名のProgram.SimpleClass
がC#コード上のクラスの構造です。
(Programクラス内のSimpleClassクラス)
xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1"
のConsoleApplication1
がC#名前空間です。
つまり以下のような構造が保存されています。
namespace ConsoleApplication1
{
class Program
{
public class SimpleClass {}
}
}
このXMLデータは、同じ名前空間、同じ構造で書かれたコード以外からはDataContractSerializerクラスで復元することができません。
コードの変更や他アプリケーションとのデータのやり取りには不都合なので、これらの情報は出力しないようにしておいたほうが良いでしょう。
名前空間とタグ名を変更するには、DataContractAttribute
属性の名前付き引数を使用します。
[DataContract(Namespace = "", Name = "simpleClass")]
public class SimpleClass
{
[DataMember]
public int Num;
[DataMember]
public string Str;
}
Namespace
が名前空間の指定、Name
がタグ名の指定です。
名前空間には空文字を指定しているので、名前空間は読み書きから無視されます。
このクラスは以下のようなXMLを出力します。
<simpleClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Num>1</Num>
<Str>abc</Str>
</simpleClass>
クラス名は「SimpleClass」ですが、タグ名は「simpleClass」と先頭が小文字になっていることに注目してください。
名前付き引数Name
を指定すると、クラス名には依存せずここで指定した名前のXMLタグを読み書きするようになります。
また、XML名前空間(「xmlns:i=~」というやつ)が出力されていますが、復元時はこれがなくても正常に読み取り可能です。
つまり以下のようなごくシンプルなXMLをデシリアライズ可能になります。
<simpleClass>
<Num>1</Num>
<Str>abc</Str>
</simpleClass>
XMLタグの並び順とタグ名
DataContractSerializerクラスは、対象のクラス内の(シリアライズ対象の)メンバを名前順(アルファベット順)で出力します。
復元時もそれに従いデータを復元しようとします。
[DataContract(Namespace = "", Name = "simpleClass")]
public class SimpleClass
{
[DataMember]
public int Number;
[DataMember]
public string Name;
}
このクラスは以下のXMLを出力します。
<simpleClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Name>John</Name>
<Number>1</Number>
</simpleClass>
改行やインデントはサイト側で挿入していますが、これらは入っていても復元可能です。
しかしXMLを編集してタグの順序を変えてしまうと正常に復元できなくなります。
<simpleClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Number>1</Number>
<Name>John</Name>
</simpleClass>
この場合、先にNumber
タグが読み取られるため、それよりも前のメンバ(名前順で先になるメンバ)は存在しないものとされます。
そのためName
メンバは復元できずに既定値で初期化されます。
なお、XMLタグ名と並び順はDataMemberAttribute
属性の名前付き引数で変更することができます。
[DataContract(Namespace = "", Name = "simpleClass")]
public class SimpleClass
{
[DataMember(Name = "number", Order = 0)]
public int Number;
[DataMember(Name = "name", Order = 1)]
public string Name;
}
<simpleClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<number>1</number>
<name>John</name>
</simpleClass>
引数Name
がタグ名、引数Order
が順序の指定です。
順序は数値が小さいほど先になります。
順序を属性で指定しても、読み取るXMLデータの順序が外部で変更されると復元できなくなるのは変わらないので注意してください。
XmlSerializerクラスは順序に関係なく復元可能なので、順序が変わってしまう可能性がある場合はこちらを使用したほうが良いでしょう。
出力されるXMLタグの並び順は正確には以下のようになります。
- クラスが継承されている場合、継承元のクラスのメンバ
- Orderが指定されていないメンバ(名前順)
- Orderが指定されているメンバ。Orderの値が同じ場合は名前順
なお、XmlSerializerクラスは順序に関係なく復元できると書きましたが、クラスが継承されている場合は継承元のメンバが先に記述されていないと復元できません。
XmlWriterとXmlReader
先ほどはシリアライズ/デシリアライズ時にStream派生クラス(FileStreamクラス)を使用していましたが、XmlWriter
クラスとXmlReader
クラスを使用することもできます。
[DataContract(Namespace = "", Name = "simpleClass")]
public class SimpleClass
{
[DataMember]
public int Num;
[DataMember]
public string Str;
public SimpleClass(int n, string s)
{
Num = n;
Str = s;
}
public void Print()
{
Console.WriteLine(Num);
Console.WriteLine(Str);
}
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass(1, "abc");
XmlWriterSettings xws = new XmlWriterSettings();
xws.Encoding = new System.Text.UTF8Encoding(false);
xws.Indent = true;
using(XmlWriter xw = XmlWriter.Create("test.xml", xws))
{
DataContractSerializer ds =
new DataContractSerializer(typeof(SimpleClass));
ds.WriteObject(xw, sc);
}
using (XmlReader xr = XmlReader.Create("test.xml"))
{
DataContractSerializer ds =
new DataContractSerializer(typeof(SimpleClass));
SimpleClass sc2 = (SimpleClass)ds.ReadObject(xr);
sc2.Print();
}
}
1 abc
出力されるXMLは以下のようになります。
<?xml version="1.0" encoding="utf-8"?>
<simpleClass xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Num>1</Num>
<Str>abc</Str>
</simpleClass>
XmlWriterSettingsクラス
XmlWriterクラスはXmlWriterSettingsクラスと併用します。
XmlWriterSettingsクラスは名前の通り、XML書き込み時の設定を保存するクラスです。
Encoding
プロパティは文字エンコーディングの指定です。
デフォルトはUTF-8、BOM付きで、今回はBOMなしのUTF-8を指定しています。
Indent
プロパティはXMLデータにインデントと改行を挿入します。
デフォルトはfalse
なので、指定しない場合は一行のXMLが出力されます。
出力されたXMLを見ると、先頭にXML宣言が挿入されています。
(<?xml version="1.0" encoding="utf-8"?>
というやつ)
XML宣言はなくても問題はありませんが、文字エンコーディングにUTF-8またはUTF-16以外を使用している場合は指定しなければならないそうです。
XML宣言は、XmlWriterSettingsクラスのOmitXmlDeclaration
プロパティにfalse
を指定すると出力されなくなります。
文字エンコーディングについてはEncodingクラスやStreamReader/Writerクラス#BOMなしで書き込むを参照してください)
シリアライズとデシリアライズ
XmlWriterクラスはXmlWriter.Create
静的メソッドでインスタンスを生成します。
第一引数は出力するファイルパス、第二引数は先ほどのXmlWriterSettingsクラスのインスタンスを指定します。
シリアライズを行うDataContractSerializerクラスのWriteObject
メソッドの第一引数にはXmlWriterクラスのインスタンスを指定します。
第二引数はシリアライズするクラスのインスタンスを指定します。
デシリアライズのほうはStream派生クラスの時とほぼ同じで、XmlReader.Create
静的メソッドでXmlReaderクラスのインスタンスを指定し、ReadObject
メソッドの引数に指定します。
これらはFileStreamと同じくusingステートメントでオブジェクト(インスタンス)を自動的に破棄でき、Close
メソッドで明示的に破棄することもできます。
XmlWriter.Create
静的メソッドの第一引数はStream派生クラスを指定することもできます。
この場合、文字エンコーディングはそのStream派生クラスのものが使用され、XmlWriterSettingsクラスのEncoding
プロパティは無視されます。
XmlSerializerクラスでの使用
XmlWriterクラスはXmlSerializerクラスによるシリアライズにも使用することができます。
XmlWriterSettingsクラスによるカスタマイズも有効なので、改行を無くしたりXML宣言を省いたりすることができます。
[DataContract(Namespace = "", Name = "simpleClass")]
public class SimpleClass
{
[DataMember(Name = "number", Order = 0)]
public int Number;
[DataMember(Name = "name", Order = 1)]
public string Name;
public SimpleClass() {} //XmlSerializerは引数なしコンストラクタが必要
public SimpleClass(int n, string s)
{
Number = n;
Name = s;
}
public void Print()
{
Console.WriteLine(Number);
Console.WriteLine(Name);
}
}
static void Main(string[] args)
{
SimpleClass sc = new SimpleClass(1, "abc");
XmlWriterSettings xws = new XmlWriterSettings();
xws.Encoding = new System.Text.UTF8Encoding(false);
xws.OmitXmlDeclaration = true;
xws.Indent = true;
xws.NewLineOnAttributes = true;
using (XmlWriter xw = XmlWriter.Create("test.xml", xws))
{
XmlSerializer xs =
new XmlSerializer(typeof(SimpleClass));
xs.Serialize(xw, sc);
xw.Dispose();
}
using (XmlReader xr = XmlReader.Create("test.xml"))
{
XmlSerializer xs =
new XmlSerializer(typeof(SimpleClass));
SimpleClass sc2 = (SimpleClass)xs.Deserialize(xr);
sc2.Print();
}
}
1 abc
XMLは以下のようになります。
<SimpleClass
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Num>1</Num>
<Str>abc</Str>
</SimpleClass>
クラスにDataContractAttribute
属性やDataMemberAttribute
属性が付いていますが、これらはDataContractSerializerクラスで使用される属性なのでXmlSerializerクラスでは無視されます。
XmlWriterSettingsクラスの設定は、OmitXmlDeclaration
プロパティにtrue
を指定してXML宣言の出力を省略しています。
また、NewLineOnAttributes
というプロパティにtrue
を指定していますが、これはタグの属性の手前に改行を挿入する設定です。
この設定はIndent
プロパティがfalse
の場合は無視されます。