動的に変化するXMLファイルをLinQで読み込む【C#】

タイトルの件の記事は既出とは思うが、
投稿の練習&後輩の教育用にローカライズされたものが欲しかったので。

### プロジェクト作成(本題と関係ないので読み飛ばしてね)

新しいプロジェクトの作成でWindowsフォームアプリケーション(.NET Framework)を選択
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/eedc8a8f-d27d-b3f7-852c-52d5ea59f730.png)
適当に名前つけて作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/f871999f-a57a-2602-875d-e43658b9c4db.png)
とりあえずテンプレートを用意。
ここからボタンとテキストボックスを追加してXMLを読み込む処理を入れていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/d2c926aa-9ef5-934c-c5ca-b65f893ea217.png)

### ボタンとテキストボックス、あとイベント追加(本題と関係ないので読み飛ばしてね その2)

コントロール(ボタンとテキストボックス)の追加
それぞれツールボックスからドラッグ&ドロップで追加できる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/d03afe51-be82-1d5e-5bb3-d33d5872717d.png)
テキストボックスは複数行表示させたいので、
プロパティ > プロパティ > `Multiline = True ` に設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/9bdbeb73-d969-a537-def5-778e923cecd6.png)
そしていい感じにテキストボックスを大きくする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/796db7f2-971b-577b-0f79-8d7d355438f6.png)
ボタンの
プロパティ > イベント > Click の所をダブルクリックして、
クリックイベントを追加する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/be724584-9c18-1648-608a-b219e150d027.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/59d795c2-b777-c7ba-09c1-aa6f65e0d9d5.png)
コードが自動生成される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/b6df1ddb-cee2-5f9d-1cea-448ceeb8a6d4.png)

---
### ここから本題：XML読み込みをLINQで実装する
#### 1. XMLファイルの作成
まずは読み込むためのXMLファイルを作成する。
ひな形はこんな感じ

``` XML:Data.xml
<?xml version="1.0" encoding="utf-8" ?>
```

別にメモ帳でも秀丸でも書ければ何でもいいけど、
visualStudioで書くなら
ソリューションエクスプローラー > 何もないところで右クリック > 追加 > 新しい項目
(またはCtrl + Shift + A でも良い)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/da054ab3-8497-6918-ec5c-6cab56051b8d.png)
新しい項目の追加 ウィンドウが開くので **XMLファイル** を選択し、
適当な名前つけて追加する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/d421cfed-51f1-1f3f-a23b-4b735ddcf260.png)
XMLファイルが自動生成される。
まずはここに読み込ませたいデータを書いていきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/4ed3d5fa-6869-a7c5-e367-b9fadcd41c86.png)
今回読み込みたいXMLのデータはこんな感じ

``` XML:Data.xml
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
    <Gosannke>
        <PokeName>Fushigidane</PokeName>
        <PokeName>Hitokage</PokeName>
        <PokeName>Zenigame</PokeName>
    </Gosannke>
    <Fushigidane>
        <Type>kusa</Type>
        <Type>doku</Type>
        <Waza>Taiatari</Waza>
        <Waza>Nakigoe</Waza>
        <Waza>Turunomuti</Waza>
    </Fushigidane>
    <Hitokage>
        <Type>honoo</Type>
        <Waza>Hikkaku</Waza>
        <Waza>Nakigoe</Waza>
    </Hitokage>
    <Zenigame>
        <Type>mizu</Type>
        <Waza>Taiatari</Waza>
        <Waza>Shippowohuru</Waza>
        <Waza>Mizudeppo</Waza>
        <Waza>Karanikomoru</Waza>
    </Zenigame>
</Pokemon>
```

表にするとこう

| ポケモン名 | タイプ | わざ |
|:-:|:-:|:--|
| フシギダネ | くさ / どく | たいあたり / なきごえ / つるのムチ |
| ヒトカゲ | ほのお | ひっかく / なきごえ |
| ゼニガメ | みず | たいあたり / しっぽをふる / みずでっぽう / からにこもる |

見てわかる通り、タイプとわざの数にばらつきがあります。
(最終的にこういうデータを扱いたいという話)

とりあえず手始めに、以下のデータを読み込む処理をLINQで書いてみます。

``` XML:Data.xml
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
    <Gosannke>
        <PokeName>Fushigidane</PokeName>
        <PokeName>Hitokage</PokeName>
        <PokeName>Zenigame</PokeName>
    </Gosannke>
</Pokemon>
```
#### 2. XMLを読み込む
usingを2つ追加し、パスを取得して読み込んでみます。

``` C#:Form.cs
using System;
using System.Windows.Forms;
using System.IO;
using System.Data;
using System.Linq;                   // 追加したusing
using System.Xml.Linq;               // 追加したusing
using System.Collections.Generic; 
private void Button1_Click(object sender, EventArgs e)
{
    // カレントディレクトリを2つ上の階層に設定し直す
    Directory.SetCurrentDirectory(@"..\..\");

    // Data.xmlのパスを取得
    string xmlFilePath = Directory.GetCurrentDirectory() + @"\Data.xml";

    // Data.xmlを読み込む
    XElement pokeData = XElement.Load(xmlFilePath);

    // 御三家ポケモンの名前を全て取得
    IEnumerable<string> GosannkeInfo = 
        from poke in pokeData.Elements("Gosannke").Elements("PokeName") select poke.Value;

    // 取得したポケモンの名前をテキストボックスに出力
    foreach (string pokeName in GosannkeInfo)
    {
        textBox1.Text += pokeName + "\r\n";
    }

    // カレントディレクトリを元の場所に戻す
    Directory.SetCurrentDirectory(@".\bin\Debug");
}
```

実行してボタンを押すとこんな感じ。
ポケモンの名前を取得できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/2c809104-47ec-2ac1-b3a9-23f8d3eab555.png)
#### 3. 【応用】取得したタグ名の中身を読みこむ
では本命のこのxmlを読み込んでみましょう

``` XML:Data.xml
<?xml version="1.0" encoding="utf-8" ?>
<Pokemon>
    <Gosannke>
        <PokeName>Fushigidane</PokeName>
        <PokeName>Hitokage</PokeName>
        <PokeName>Zenigame</PokeName>
    </Gosannke>
    <Fushigidane>
        <Type>kusa</Type>
        <Type>doku</Type>
        <Waza>Taiatari</Waza>
        <Waza>Nakigoe</Waza>
        <Waza>Turunomuti</Waza>
    </Fushigidane>
    <Hitokage>
        <Type>honoo</Type>
        <Waza>Hikkaku</Waza>
        <Waza>Nakigoe</Waza>
    </Hitokage>
    <Zenigame>
        <Type>mizu</Type>
        <Waza>Taiatari</Waza>
        <Waza>Shippowohuru</Waza>
        <Waza>Mizudeppo</Waza>
        <Waza>Karanikomoru</Waza>
    </Zenigame>
</Pokemon>
```

先ほどの `foreach` の箇所を以下のように書き換えました。
`foreach` がネストしてて冗長に見えますね…。

``` C#:Form.cs
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
    textBox1.Text += pokeName + "\r\n";

    // <Type>要素の取得
    IEnumerable<string> TypeInfo = from type in pokeData.Elements(pokeName).Elements("Type") select type.Value;
    textBox1.Text += "TYPE : " + "\r\n";
    foreach (string Type in TypeInfo)
    {
        textBox1.Text += " " + Type + "\r\n";
    }

    // <Waza>要素の取得
    IEnumerable<string> WazaInfo = from waza in pokeData.Elements(pokeName).Elements("Waza") select waza.Value;
    textBox1.Text += "WAZA : " + "\r\n";
    foreach (string Waza in WazaInfo)
    {
        textBox1.Text += " " + Waza + "\r\n";
    }

    // 見栄えのための改行
    textBox1.Text += "\r\n";
}

```

一応動作自体は期待通り。
でもコードが気持ち悪いので`foreach` 部分をLINQで書き直してみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/5eefb4b3-81d7-4ea2-f6a4-3eaae3f8393c.png)

#### 4. foreach文をLINQにリファクタリングする

`foreach`のデメリットは、単純にネストが深く可読性が悪くなる点です。
動作を変えず、可読性をよくするために、LINQで書き直します。
分かり易いように、`<Type>` 要素の部分だけをLINQに書き換えました。

``` C#:Form.cs
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
    textBox1.Text += pokeName + "\r\n";

    // <Type>要素の取得
    IEnumerable<string> TypeInfo = from type in pokeData.Elements(pokeName).Elements("Type") select type.Value;
    textBox1.Text += "TYPE : " + "\r\n";
    TypeInfo.ToList().ForEach(Type => { textBox1.Text += " " + Type + "\r\n"; });
    
    // ↑と↓を見比べると分かり易い！
    
    // <Waza>要素の取得
    IEnumerable<string> WazaInfo = from waza in pokeData.Elements(pokeName).Elements("Waza") select waza.Value;
    textBox1.Text += "WAZA : " + "\r\n";
    foreach (string Waza in WazaInfo)
    {
        textBox1.Text += " " + Waza + "\r\n";
    }

    // 見栄えのための改行
    textBox1.Text += "\r\n";
}
```

`<Waza>` 要素も同様にLINQで書いてみましょう。

``` C#:Form.cs
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
    textBox1.Text += pokeName + "\r\n";

    // <Type>要素の取得
    IEnumerable<string> TypeInfo = from type in pokeData.Elements(pokeName).Elements("Type") select type.Value;
    textBox1.Text += "TYPE : " + "\r\n";
    TypeInfo.ToList().ForEach(Type => { textBox1.Text += " " + Type + "\r\n"; });

    // <Waza>要素の取得
    IEnumerable<string> WazaInfo = from waza in pokeData.Elements(pokeName).Elements("Waza") select waza.Value;
    textBox1.Text += "WAZA : " + "\r\n";
    WazaInfo.ToList().ForEach(Waza => { textBox1.Text += " " + Waza + "\r\n"; });

    // 見栄えのための改行
    textBox1.Text += "\r\n";
}
```

タグ要素の取得は、共通の処理なので関数化しましょう。

``` C#:Form.cs
private void OutputPokeData(XElement pokeData, string pokeName, string tag)
{
    IEnumerable<string> Infos = from Info in pokeData.Elements(pokeName).Elements(tag) select Info.Value;
    textBox1.Text += tag + " : " + "\r\n";
    Infos.ToList().ForEach(Info => { textBox1.Text += " " + Info + "\r\n"; });
}
```

``` C#:Form.cs
// 取得したポケモンの名前をテキストボックスに出力
foreach (string pokeName in PokeInfo)
{
    textBox1.Text += pokeName + "\r\n";

    // <Type>要素の取得
    OutputPokeData(pokeData, pokeName, "Type");

    // <Waza>要素の取得
    OutputPokeData(pokeData, pokeName, "Waza");

    // 見栄えのための改行
    textBox1.Text += "\r\n";
}
```

とっても見やすくなりましたね！
最後にこの`foreach`もLINQに書き直して、ソース全体を見てみましょう。

``` C#:Form.cs
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using System.IO;

namespace XMLReader
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Button1_Click(object sender, EventArgs e)
        {
            // カレントディレクトリを2つ上の階層に設定し直す
            Directory.SetCurrentDirectory(@"..\..\");

            // Data.xmlのパスを取得
            string xmlFilePath = Directory.GetCurrentDirectory() + @"\Data.xml";

            // Data.xmlを読み込む
            XElement pokeData = XElement.Load(xmlFilePath);

            // 御三家ポケモンの名前を全て取得
            IEnumerable<string> PokeInfo = 
                from poke in pokeData.Elements("Gosannke").Elements("PokeName") select poke.Value;

            // 取得したポケモンの名前をテキストボックスに出力
            PokeInfo.ToList().ForEach(pokeName => {
                textBox1.Text += pokeName + "\r\n";         // ポケモン名表示
                OutputPokeData(pokeData, pokeName, "Type"); // <Type>要素の取得
                OutputPokeData(pokeData, pokeName, "Waza"); // <Waza>要素の取得
                textBox1.Text += "\r\n";                    // 見栄えのための改行
            });

            // カレントディレクトリを元の場所に戻す
            Directory.SetCurrentDirectory(@".\bin\Debug");
        }

        private void OutputPokeData(XElement pokeData, string pokeName, string tag)
        {
            IEnumerable<string> Infos = from Info in pokeData.Elements(pokeName).Elements(tag) select Info.Value;
            textBox1.Text += tag + " : " + "\r\n";
            Infos.ToList().ForEach(Info => { textBox1.Text += " " + Info + "\r\n"; });
        }
    }
}

```

動作も全く同じであることが確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/723263/12d76e82-f14f-1381-0aed-4679e0947d3c.png)