1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C# - xmlファイルへ設定情報(自作クラスのフィールド・プロパティ)を読み書きする/色々な値を入れて実験してみた

Last updated at Posted at 2020-11-21

忙しい人向け ⇒ 3章 まとめ

1. 基本

1.1. 超概要

XmlSerializerクラスを使うと、

C#

[Serializable]
public class なんちゃら
{
    public 型名 かんちゃら;
}

みたいな自作クラスのインスタンスの値を下記のようにXMLに保存したり、復元(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. やりかた(参考サイト)

丸投げ・・・

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への書き込み

C#ソースコード

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ファイルSampleOutput.xml

<?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型のフィールドであるSampleFieldStringnullを代入しておいてXMLを生成したところ、
生成されたXMLからSampleFieldStringがいなくなりました。

生成されたXMLファイル

<?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. エスケープされそうな文字を入れてみる

C#抜粋

SampleFieldString   = "<hoge=foo\"bar\"piyo?piyo!hoge2 hoge3/hoge4\nhoge5\r\nhoge6\thoge7&hoge8;hoge9>",
生成されたXMLファイル抜粋

  <SampleFieldString>&lt;hoge=foo"bar"piyo?piyo!hoge2 hoge3/hoge4
hoge5
hoge6	hoge7&amp;hoge8;hoge9&gt;</SampleFieldString>

<>&は、それぞれ&lt;, &gt;, &amp;というHTMLっぽい感じにエスケープされています。
改行コード\r,\nとタブ\tはそのまま出力されるようです。

2.2.2. XMLからの復元

C#
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. メンバが配列やリストの場合

C#抜粋

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ファイル

<?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. メンバの型が自作クラスの場合

C#

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ファイル

<?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)の場合

C#抜粋

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ファイル

<?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のメンバは出力されない。
  • 文字列の場合、文字列中の<,>,&はそれぞれ&lt;,&gt;,&amp;にエスケープされる。
  • 型情報は(配列やList<>の要素の場合を除き、)基本的に出力されない。
  • メンバの型が配列やList<>の場合

    <メンバ名><要素の型名>値</要素の型名>・・・</メンバ名>
  • メンバの型が構造体やクラスの場合

    <メンバ名><子クラスのメンバ名>値</子クラスのメンバ名>・・・</メンバ名>
  • Dictionary<,>はそのままだとシリアライズできない。(2.5章参照)
  1. コードに直接埋め込んでいても似たようなものかもしれませんが。。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?