忙しい人向け ⇒ 3章 まとめ
1. 基本
1.1. 超概要
XmlSerializer
クラスを使うと、
[Serializable]
public class なんちゃら
{
public 型名 かんちゃら;
}
みたいな自作クラスのインスタンスの値を下記のようにXMLに保存したり、復元(XMLファイルの値に基づいて自作クラスのメンバに値を代入済みのインスタンスを生成)したりできます。
<?xml version="1.0" encoding="utf-8"?>
<なんちゃら xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<かんちゃら>ほげ</かんちゃら>
</なんちゃら>
1.2. やりかた(参考サイト)
丸投げ・・・
- オブジェクトの内容をXMLファイルに保存、復元する - .NET Tips (VB.NET,C#...)
- XmlSerializerを使ってシリアライズ/デシリアライズするには?[C#/VB]:.NET TIPS - @IT
- 「xmlserializer」の検索結果 - Qiita
1.3. XMLに保存される対象
public
なフィールドと、get/setともpublic
なプロパティが保存対象のようです。
XmlSerializer.Serialize Method (System.Xml.Serialization) | Microsoft Docs より
The Serialize method converts the public fields and read/write properties of an object into XML. It does not convert methods, indexers, private fields, or read-only properties. To serialize all an object's fields and properties, both public and private, use the BinaryFormatter.
In the xmlWriter parameter, specify an object that derives from the abstract XmlWriter class. The XmlTextWriter derives from the XmlWriter.
Note
The XmlSerializer cannot serialize the following: arrays of ArrayList and arrays of List.
Google翻訳結果:
Serializeメソッドは、オブジェクトのパブリックフィールドと読み取り/書き込みプロパティをXMLに変換します。メソッド、インデクサー、プライベートフィールド、または読み取り専用プロパティは変換されません。パブリックとプライベートの両方のすべてのオブジェクトのフィールドとプロパティをシリアル化するには、BinaryFormatterを使用します。
xmlWriterパラメーターで、抽象XmlWriterクラスから派生するオブジェクトを指定します。 XmlTextWriterはXmlWriterから派生しています。
注意
XmlSerializerは、ArrayListの配列とList の配列をシリアル化できません。
1.4. 注意すべきこと
1.4.1. 使用上(システム構築/運用上)の注意
XMLファイルはただのテキストファイルであり、直接ユーザーが編集できてしまうので、設定ファイルを切り出すことで、セキュリティ上のリスクや、実行時エラーなどのリスクを生むかもしれません。1
また、C#など.Net系の言語をビルドしたexeファイルは逆アセンブルが容易に可能なため、ハッシュ値などのチェック値を入れるなどの対策だけだと、完全にはガードできないものと思います。
1.4.2. 実行ファイルとの相対パスに置きたい場合の注意
1.4.3. その他
- 文字コード(UTF-8のBOMの有無にも念のため注意)
- 例外処理(例:ファイルを開いていて書き込めないとき・・・
System.IO.IOException
)のケア -
null
の処置 - 浮動小数点数を10進数表示することによる誤差
- エスケープが必要な文字の扱い(XMLを直接編集する場合)
番外:
- コマンドプロンプト上で日本語等のマルチバイト文字を出力させて試す場合は、コマンドプロンプトのコードページの変更が必要な場合があります。
2. 本記事の本題
自作ツールを作っている最中に、設定ファイルを切り出したくなることがよくあります。
デフォルトの設定値を埋め込んだXMLファイルを作りたいけど、それ用のソースコードを用意するのが面倒。
なので、どういう自作クラスを書くとどういうXMLが生成されるのかをテンプレとして整理してみます。
色々試した結果をおいておきます。
2.1. メンバが数値や文字列などの基本的な型の場合
2.1.1. XMLへの書き込み
using System;
using System.IO;
[Serializable]
public class SampleClass
{
public string SampleFieldString;
public int SampleFieldInt;
public uint SampleFieldUInt;
public long SampleFieldLong;
public bool SampleFieldBool;
public double SampleFieldDouble;
public float SampleFieldFloat;
public char SampleFieldChar;
public Decimal SampleFieldDecimal;
public DateTime SampleFieldDateTime;
}
class SerializerSample
{
[STAThread]
static void Main()
{
var sampleValues = new SampleClass(){
SampleFieldString = "hoge",
SampleFieldInt = -1,
SampleFieldUInt = 1,
SampleFieldLong = 2,
SampleFieldBool = false,
SampleFieldDouble = 1.2d,
SampleFieldFloat = 1.3f,
SampleFieldChar = 'c',
SampleFieldDecimal = 1.4m,
SampleFieldDateTime = DateTime.Now,
};
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SampleClass));
using ( var sw = new System.IO.StreamWriter("SampleOutput.xml", false, new System.Text.UTF8Encoding(false)) ) // BOMなしUTF-8
{
serializer.Serialize(sw, sampleValues);
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<SampleClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SampleFieldString>hoge</SampleFieldString>
<SampleFieldInt>-1</SampleFieldInt>
<SampleFieldUInt>1</SampleFieldUInt>
<SampleFieldLong>2</SampleFieldLong>
<SampleFieldBool>false</SampleFieldBool>
<SampleFieldDouble>1.2</SampleFieldDouble>
<SampleFieldFloat>1.3</SampleFieldFloat>
<SampleFieldChar>99</SampleFieldChar>
<SampleFieldDecimal>1.4</SampleFieldDecimal>
<SampleFieldDateTime>2020-11-21T14:52:46.6364558+09:00</SampleFieldDateTime>
</SampleClass>
生成されたXMLファイルには型情報は含まれないようです。
(復元するときにチェックされるものと思われますが、調べてないので不明です。)
2.2.1.1. null
の扱い
string
型のフィールドであるSampleFieldString
にnull
を代入しておいてXMLを生成したところ、
生成されたXMLからSampleFieldString
がいなくなりました。
<?xml version="1.0" encoding="utf-8"?>
<SampleClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SampleFieldInt>-1</SampleFieldInt>
<SampleFieldUInt>1</SampleFieldUInt>
<SampleFieldLong>2</SampleFieldLong>
<SampleFieldBool>false</SampleFieldBool>
<SampleFieldDouble>1.2</SampleFieldDouble>
<SampleFieldFloat>1.3</SampleFieldFloat>
<SampleFieldChar>99</SampleFieldChar>
<SampleFieldDecimal>1.4</SampleFieldDecimal>
<SampleFieldDateTime>2020-11-21T15:15:07.2962926+09:00</SampleFieldDateTime>
</SampleClass>
2.2.1.2. エスケープされそうな文字を入れてみる
SampleFieldString = "<hoge=foo\"bar\"piyo?piyo!hoge2 hoge3/hoge4\nhoge5\r\nhoge6\thoge7&hoge8;hoge9>",
<SampleFieldString><hoge=foo"bar"piyo?piyo!hoge2 hoge3/hoge4
hoge5
hoge6 hoge7&hoge8;hoge9></SampleFieldString>
<
と>
と&
は、それぞれ<
, >
, &
というHTMLっぽい感じにエスケープされています。
改行コード\r
,\n
とタブ\t
はそのまま出力されるようです。
2.2.2. XMLからの復元
using System;
using System.IO;
[Serializable]
public class SampleClass
{
同上のため省略
}
class SerializerSample
{
[STAThread]
static void Main()
{
SampleClass sampleValues;
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(SampleClass));
using (var streamReader = new System.IO.StreamReader("SampleOutput.xml", System.Text.Encoding.UTF8))
using (var xmlReader = System.Xml.XmlReader.Create(streamReader))
{
sampleValues = (SampleClass)serializer.Deserialize(xmlReader);
}
Console.Write("SampleFieldString :"); Console.WriteLine(sampleValues.SampleFieldString );
Console.Write("SampleFieldInt :"); Console.WriteLine(sampleValues.SampleFieldInt );
Console.Write("SampleFieldUInt :"); Console.WriteLine(sampleValues.SampleFieldUInt );
Console.Write("SampleFieldLong :"); Console.WriteLine(sampleValues.SampleFieldLong );
Console.Write("SampleFieldBool :"); Console.WriteLine(sampleValues.SampleFieldBool );
Console.Write("SampleFieldDouble :"); Console.WriteLine(sampleValues.SampleFieldDouble );
Console.Write("SampleFieldFloat :"); Console.WriteLine(sampleValues.SampleFieldFloat );
Console.Write("SampleFieldChar :"); Console.WriteLine(sampleValues.SampleFieldChar );
Console.Write("SampleFieldDecimal :"); Console.WriteLine(sampleValues.SampleFieldDecimal );
Console.Write("SampleFieldDateTime:"); Console.WriteLine(sampleValues.SampleFieldDateTime);
}
}
SampleFieldString :hoge
SampleFieldInt :-1
SampleFieldUInt :1
SampleFieldLong :2
SampleFieldBool :False
SampleFieldDouble :1.2
SampleFieldFloat :1.3
SampleFieldChar :c
SampleFieldDecimal :1.4
SampleFieldDateTime:2020/11/21 14:52:46
2.2. メンバが配列やリストの場合
using System;
using System.IO;
using System.Collections.Generic;
[Serializable]
public class SampleClass
{
public List<string> SampleFieldStringList;
public string[] SampleFieldStringArray;
public List<int> SampleFieldIntList;
public int[] SampleFieldIntArray;
public List<long> SampleFieldLongList;
public long[] SampleFieldLongArray;
public List<decimal> SampleFieldDecimalList;
public decimal[] SampleFieldDecimalArray;
}
class SerializerSample
{
[STAThread]
static void Main()
{
var sampleValues = new SampleClass(){
SampleFieldStringList = new List<string>(){"hoge1","foo","bar"},
SampleFieldStringArray = new string[]{"hoge2","foo","bar"},
SampleFieldIntList = new List<int>(){1,2},
SampleFieldIntArray = new int[]{3,4},
SampleFieldLongList = new List<long>(){5,6},
SampleFieldLongArray = new long[]{7,8},
SampleFieldDecimalList = new List<decimal>(){0.1m,0.2m},
SampleFieldDecimalArray= new decimal[]{0.3m,0.4m},
};
以下略(本記事の2.1.1.章参照)
}
}
<?xml version="1.0" encoding="utf-8"?>
<SampleClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SampleFieldStringList>
<string>hoge1</string>
<string>foo</string>
<string>bar</string>
</SampleFieldStringList>
<SampleFieldStringArray>
<string>hoge2</string>
<string>foo</string>
<string>bar</string>
</SampleFieldStringArray>
<SampleFieldIntList>
<int>1</int>
<int>2</int>
</SampleFieldIntList>
<SampleFieldIntArray>
<int>3</int>
<int>4</int>
</SampleFieldIntArray>
<SampleFieldLongList>
<long>5</long>
<long>6</long>
</SampleFieldLongList>
<SampleFieldLongArray>
<long>7</long>
<long>8</long>
</SampleFieldLongArray>
<SampleFieldDecimalList>
<decimal>0.1</decimal>
<decimal>0.2</decimal>
</SampleFieldDecimalList>
<SampleFieldDecimalArray>
<decimal>0.3</decimal>
<decimal>0.4</decimal>
</SampleFieldDecimalArray>
</SampleClass>
- 配列と
List<>
は同じ出力になるようです。 -
<メンバ名><要素の型名>値</要素の型名>・・・</メンバ名>
となるようです。
(C#以外(VB.Netとか)だとどうなるのだろうか?)
2.3. メンバの型が自作クラスの場合
using System;
using System.IO;
[Serializable]
public class SampleClass
{
public SampleChildClass SampleFieldChildMember;
}
[Serializable]
public class SampleChildClass
{
public string SampleFieldString;
}
class SerializerSample
{
[STAThread]
static void Main()
{
var sampleValues = new SampleClass(){
SampleFieldChildMember = new SampleChildClass(){SampleFieldString="hoge"},
};
以下略(本記事の2.1.1.章参照)
}
}
<?xml version="1.0" encoding="utf-8"?>
<SampleClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SampleFieldChildMember>
<SampleFieldString>hoge</SampleFieldString>
</SampleFieldChildMember>
</SampleClass>
<メンバ名><子クラスのメンバ名>値</子クラスのメンバ名>・・・</メンバ名>
となるようです。
(子クラスのクラス名SampleChildClass
は出力されませんでした。)
2.4. メンバが構造体(System.Drawing.Point
とかSystem.Drawing.Size
)の場合
using System;
using System.IO;
using System.Drawing;
[Serializable]
public class SampleClass
{
public Point SampleFieldPoint;
public Size SampleFieldSize;
}
class SerializerSample
{
[STAThread]
static void Main()
{
var sampleValues = new SampleClass(){
SampleFieldPoint = new Point(10,20),
SampleFieldSize = new Size(30,40),
};
以下略(本記事の2.1.1.章参照)
}
}
<?xml version="1.0" encoding="utf-8"?>
<SampleClass xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SampleFieldPoint>
<X>10</X>
<Y>20</Y>
</SampleFieldPoint>
<SampleFieldSize>
<Width>30</Width>
<Height>40</Height>
</SampleFieldSize>
</SampleClass>
2.3章と同様ですね。
2.5. Dictionary<,>
・・・そのままだとシリアライズできない
下記エラーがでました。(※見やすさのためエラーメッセージに適宜改行を加えています。)
ハンドルされていない例外: System.InvalidOperationException: 型 'SampleClass' を反映中にエラーが発生しました。
--->
System.NotSupportedException: IDictionary が実装されているため、
型 System.Collections.Generic.Dictionary`2
[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],
[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]
のメンバー SampleClass.SampleFieldDict1 はシリアル化できません。
対応されている方がいらっしゃるので参考のためリンクを貼っておきます。
3. 実験結果のまとめ
2章の通り、実験した限りでは下記のようになりました。
- 値が
null
のメンバは出力されない。 - 文字列の場合、文字列中の
<
,>
,&
はそれぞれ<
,>
,&
にエスケープされる。 - 型情報は(配列や
List<>
の要素の場合を除き、)基本的に出力されない。 - メンバの型が配列や
List<>
の場合
⇒<メンバ名><要素の型名>値</要素の型名>・・・</メンバ名>
- メンバの型が構造体やクラスの場合
⇒<メンバ名><子クラスのメンバ名>値</子クラスのメンバ名>・・・</メンバ名>
-
Dictionary<,>
はそのままだとシリアライズできない。(2.5章参照)
-
コードに直接埋め込んでいても似たようなものかもしれませんが。。 ↩