Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9304

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

$
0
0

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

1. 基本

1.1. 超概要

XmlSerializerクラスを使うと、

C#
[Serializable]publicclassなんちゃら{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#ソースコード
usingSystem;usingSystem.IO;[Serializable]publicclassSampleClass{publicstringSampleFieldString;publicintSampleFieldInt;publicuintSampleFieldUInt;publiclongSampleFieldLong;publicboolSampleFieldBool;publicdoubleSampleFieldDouble;publicfloatSampleFieldFloat;publiccharSampleFieldChar;publicDecimalSampleFieldDecimal;publicDateTimeSampleFieldDateTime;}classSerializerSample{[STAThread]staticvoidMain(){varsampleValues=newSampleClass(){SampleFieldString="hoge",SampleFieldInt=-1,SampleFieldUInt=1,SampleFieldLong=2,SampleFieldBool=false,SampleFieldDouble=1.2d,SampleFieldFloat=1.3f,SampleFieldChar='c',SampleFieldDecimal=1.4m,SampleFieldDateTime=DateTime.Now,};varserializer=newSystem.Xml.Serialization.XmlSerializer(typeof(SampleClass));using(varsw=newSystem.IO.StreamWriter("SampleOutput.xml",false,newSystem.Text.UTF8Encoding(false)))// BOMなしUTF-8{serializer.Serialize(sw,sampleValues);}}}
生成されたXMLファイルSampleOutput.xml
<?xml version="1.0" encoding="utf-8"?><SampleClassxmlns: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"?><SampleClassxmlns: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#
usingSystem;usingSystem.IO;[Serializable]publicclassSampleClass{同上のため省略}classSerializerSample{[STAThread]staticvoidMain(){SampleClasssampleValues;varserializer=newSystem.Xml.Serialization.XmlSerializer(typeof(SampleClass));using(varstreamReader=newSystem.IO.StreamReader("SampleOutput.xml",System.Text.Encoding.UTF8))using(varxmlReader=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#抜粋
usingSystem;usingSystem.IO;usingSystem.Collections.Generic;[Serializable]publicclassSampleClass{publicList<string>SampleFieldStringList;publicstring[]SampleFieldStringArray;publicList<int>SampleFieldIntList;publicint[]SampleFieldIntArray;publicList<long>SampleFieldLongList;publiclong[]SampleFieldLongArray;publicList<decimal>SampleFieldDecimalList;publicdecimal[]SampleFieldDecimalArray;}classSerializerSample{[STAThread]staticvoidMain(){varsampleValues=newSampleClass(){SampleFieldStringList=newList<string>(){"hoge1","foo","bar"},SampleFieldStringArray=newstring[]{"hoge2","foo","bar"},SampleFieldIntList=newList<int>(){1,2},SampleFieldIntArray=newint[]{3,4},SampleFieldLongList=newList<long>(){5,6},SampleFieldLongArray=newlong[]{7,8},SampleFieldDecimalList=newList<decimal>(){0.1m,0.2m},SampleFieldDecimalArray=newdecimal[]{0.3m,0.4m},};以下略(本記事の2.1.1.章参照)}}
生成されたXMLファイル
<?xml version="1.0" encoding="utf-8"?><SampleClassxmlns: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#
usingSystem;usingSystem.IO;[Serializable]publicclassSampleClass{publicSampleChildClassSampleFieldChildMember;}[Serializable]publicclassSampleChildClass{publicstringSampleFieldString;}classSerializerSample{[STAThread]staticvoidMain(){varsampleValues=newSampleClass(){SampleFieldChildMember=newSampleChildClass(){SampleFieldString="hoge"},};以下略(本記事の2.1.1.章参照)}}
生成されたXMLファイル
<?xml version="1.0" encoding="utf-8"?><SampleClassxmlns: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#抜粋
usingSystem;usingSystem.IO;usingSystem.Drawing;[Serializable]publicclassSampleClass{publicPointSampleFieldPoint;publicSizeSampleFieldSize;}classSerializerSample{[STAThread]staticvoidMain(){varsampleValues=newSampleClass(){SampleFieldPoint=newPoint(10,20),SampleFieldSize=newSize(30,40),};以下略(本記事の2.1.1.章参照)}}
生成されたXMLファイル
<?xml version="1.0" encoding="utf-8"?><SampleClassxmlns: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. コードに直接埋め込んでいても似たようなものかもしれませんが。。 


Viewing all articles
Browse latest Browse all 9304

Trending Articles