DataContractSerializerでXMLをデシリアライズする

.NET(C#)で、設定情報をXMLで管理するための実装方法を試してみた。

####**1.設定情報のxmlファイルを作成**
【CompanyInfo.xml】

```xml
<?xml version="1.0" encoding="utf-8" ?>
<root xmlns="http://schemas.datacontract.org/2004/07/XMLDataContract">
	<company>ほんにゃら産業</company>
	<departments>
		<department>
			<name>営業部</name>
			<employees>
				<employee>
					<name>かりあげくん</name>
					<gender>男</gender>
				</employee>
				<employee>
					<name>木村課長</name>
					<gender>男</gender>
				</employee>
				<employee>
					<name>鈴木</name>
					<gender>男</gender>
				</employee>
				<employee>
					<name>ヒサエさん</name>
					<gender>女</gender>
				</employee>
			</employees>
		</department>
		<department>
			<name>総務部</name>
			<employees>
				<employee>
					<name>佐藤クン</name>
					<gender>男</gender>
				</employee>
				<employee>
					<name>山田さん</name>
					<gender>女</gender>
				</employee>
				<employee>
					<name>太子さん</name>
					<gender>女</gender>
				</employee>
			</employees>
		</department>
	</departments>
</root>
```
* プロパティの「出力ディレクトリにコピー」は「コピーをしない」がデフォルトだが、そのままだとファイルがない旨のエラーが発生するので、それ以外に設定する。
* xmlns属性を追加しないとエラーになる。プロジェクトからxmlファイルを追加する場合は、自動的に挿入されないので手入力する必要がある。

```xml
<root xmlns="http://schemas.datacontract.org/2004/07/XMLDataContract" >
<!-- xmlns="http://schemas.datacontract.org/2004/07/名前空間名 -->

```
#### **2.デシリアライズのためのクラスを作成**
* 参照設定に「System.Runtime.Serialization」を追加する。
* デシリアライズさせるためのクラスを定義する。まずは会社名(company)だけをやってみる。

【CompanyInfo.cs】

```csharp
using System.Runtime.Serialization;

namespace XMLDataContract {
	[DataContract(Name = "root")]
	public class CompanyInfo {

		[DataMember(Name = "company")]
		public string Company {
			get;private set;
		}
	}
}
```
#### **3.デシリアライズのメソッドを作成**
* 汎用的に利用するメソッドを作成

【XMLDataContract.cs】

```c#
using System.IO;
using System.Xml;
using System.Runtime.Serialization;

namespace XMLDataContract {
	public class XMLDataContract {
		public static T Load<T>(Stream stream) where T : new() {
			T result = default(T);

			var serialiser = new DataContractSerializer(typeof(T));

			using(var reader = XmlReader.Create(stream)) {
				result = (T)serialiser.ReadObject(reader);
			}

			return result;
		}
	}
}
```
単体テストのコード

```c#
using Microsoft.VisualStudio.TestTools.UnitTesting;
using XMLDataContract;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace XMLDataContract.Tests {
	[TestClass()]
	public class XMLDataContractTests {
		[TestMethod()]
		public void LoadTest() {

			//Arrange
			string filename = "CompanyInfo.xml";

			using(Stream filestream = File.Open(filename, FileMode.Open)) {
				//Act
				var deserialize = XMLDataContract.Load<CompanyInfo>(filestream);

				//Assert
				Assert.AreEqual(deserialize.Company, "ほんにゃら産業");
			}

		}
    }
}
```
実際にテストを実行すると問題なければ、その他の属性もデシリアライズさせてみる。

#### **4.その他の属性をデシリアライズ**
* CompanyInfo.xmlは部門(department)が配列(departments)になっていて、更に従業員(employee)が配列(employees)になっている。これらをデシリアライズさせるには「CollectionDataContractAttribute」を使用する。先程作成した「CompanyInfo.cs」に追記する。

```c#
using System.Runtime.Serialization;
using System.Collections.Generic;

namespace XMLDataContract {
	[DataContract(Name = "root")]
	public class CompanyInfo {

		[DataMember(Name = "company")]
		public string Company {
			get;private set;
		}

		[DataMember(Name = "departments")]
		public Departments Departments {
			get;private set;
		}
	}

	[CollectionDataContract(Name = "departments",ItemName = "department")]
	public class Departments : List<Department> {

	}

	[DataContract(Name = "department")]
	public class Department {

		[DataMember(Name = "name")]
		public string DeparmentName {
			get;private set;
		}
	}
}
```
単体テスト

```c#
		[TestMethod()]
		public void LoadTest2() {

			//Arrange
			string filename = "CompanyInfo.xml";

			using(Stream filestream = File.Open(filename, FileMode.Open)) {
				//Act
				var deserialize = XMLDataContract.Load<CompanyInfo>(filestream);

				//Assert
				Assert.AreEqual(deserialize.Departments[1].DeparmentName, "総務部");
			}

		}


```
#### **5.最終型の実装**

```c#
using System.Runtime.Serialization;
using System.Collections.Generic;

namespace XMLDataContract {
	[DataContract(Name = "root")]
	public class CompanyInfo {

		[DataMember(Name = "company")]
		public string Company {
			get;private set;
		}

		[DataMember(Name = "departments")]
		public Departments Departments {
			get;private set;
		}
	}

	[CollectionDataContract(Name = "departments",ItemName = "department")]
	public class Departments : List<Department> {

	}

	[DataContract(Name = "department")]
	public class Department {

		[DataMember(Name = "name",Order = 1)]
		public string DeparmentName {
			get;private set;
		}

		[DataMember(Name = "employees",Order = 2)]
		public Employees Employees {
			get; private set;
		}
	}

	[CollectionDataContract(Name = "employees", ItemName = "employee")]
	public class Employees : List<Emplyee> {

	}

	[DataContract(Name = "employee")]
	public class Emplyee {
		[DataMember(Name = "name",Order = 1)]
		public string EmployeeName {
			get;private set;
		}

		[DataMember(Name = "gender",Order = 2)]
		public string Gender {
			get;private set;
		}
	}
}

```

ここでの注意点として、CollectionDataContractAttributeのItemNameに設定しているクラス内のDataMamberAttributeに「Order=」を設定していないとデシリアライズされない。ここで結構悩みました。

単体テスト

```c#
		[TestMethod()]
		public void LoadTest3() {

			//Arrange
			string filename = "CompanyInfo.xml";

			using(Stream filestream = File.Open(filename, FileMode.Open)) {

				//Act
				var deserialize = XMLDataContract.Load<CompanyInfo>(filestream);

				//Assert
				Assert.AreEqual(deserialize.Departments[0].Employees[1].EmployeeName, "木村課長");
				Assert.AreEqual(deserialize.Departments[0].Employees[1].Gender, "男");

			}
		}
```

以上です。