ポンコツXAMLer奮闘記

C#(主にXAML)関連のメモ書きがメインです。

XMLファイルを簡単に読み書きしたい

アプリケーションを作っていると、設定ファイルを残す場面が必ずありますね。
でも、主要機能じゃないからあまり時間をかけられない。
でもでもー、CSVファイルとかにすると、自分でパーサーをガリガリ書かなきゃいけなくなる。。。
どうしたものか。。。

そんなときはXMLファイルです。
.NetのXMLパーサーは割と賢くなっていて、ガリガリコーディングしなくても、XMLのツリー構造を構築・逆にデータを再現することができます。
データが巨大になったり、バージョンアップでコロコロ構造が変わる場合はお勧めできないですがね。。。

その辺のトレードオフはご自身の判断でお願いします。


今回は下のXMLファイルを読み書きすることにします。

<?xml version="1.0" encoding="utf-8"?>
<Settings>
  <MyName>ポンコツ太郎</MyName>
  <MyAge>26</MyAge>
  <MyAddr>日本のスクラップ山</MyAddr>
  <MyAddrList>
    <AddrItem>
      <Name>メリケン</Name>
      <Age>10</Age>
      <Addr>アメリカ</Addr>
    </AddrItem>
    <AddrItem>
      <Name>ジョーカー</Name>
      <Age>17</Age>
      <Addr>四茶</Addr>
    </AddrItem>
  </MyAddrList>
</Settings>

まずは、XMLファイルのタグと一対一になるクラスを定義します。

[XmlRoot("Settings")]
public class CSettingsXml
{
    /// <summary>
    /// 自分の名前
    /// </summary>
    [XmlElement("MyName")]
    public string MyName = "";

    /// <summary>
    /// 自分の年齢
    /// </summary>
    [XmlElement("MyAge")]
    public int MyAge = 0;

    /// <summary>
    /// 自分の住所
    /// </summary>
    [XmlElement("MyAddr")]
    public string MyAddr = "";

    /// <summary>
    /// 自分の連絡帳
    /// </summary>
    [XmlArray("MyAddrList")]
    [XmlArrayItem("AddrItem")]
    public List<CAddrItem> MyAddrList = new List<CAddrItem>();

    /// <summary>
    /// XMLファイルを読み込む
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public static CSettingsXml Load(string path)
    {
        CSettingsXml loaddata;

        using (var sr = new StreamReader(path, new System.Text.UTF8Encoding(false)))
        {
            loaddata = (CSettingsXml)serializer.Deserialize(sr);
            sr.Close();
        }

        return loaddata;
    }

    /// <summary>
    /// XMLファイルに保存する
    /// </summary>
    /// <param name="path"></param>
    public void Save(string path)
    {
        using (var sw = new StreamWriter(path, false, new System.Text.UTF8Encoding(false)))
        {
            var ns = new XmlSerializerNamespaces();
            ns.Add(String.Empty, String.Empty);
            serializer.Serialize(sw, this, ns);
            sw.Close();
        }
    }
}

public class CAddrItem
{
    /// <summary>
    /// 名前
    /// </summary>
    [XmlElement("Name")]
    public string Name = "";

    /// <summary>
    /// 年齢
    /// </summary>
    [XmlElement("Age")]
    public int Age = 0;

    /// <summary>
    /// 住所
    /// </summary>
    [XmlElement("Addr")]
    public string Addr = "";
}

これでCSettingsXmlのインスタンスでSave()を呼ぶと保存。CSettingsXml.Load()でデータの再現ができます。

どうです?簡単でしょ?

うーん、でも、これだと、XMLファイルがいっぱいできたときに全部のクラスにSave()とLoad()を実装しなきゃなりません。

せっかくのオブジェクト指向言語ですからね。カッコよくしましょう。


以下のクラスを追加してください。これを見たらもうお分かりになるはず。。。

public abstract class AXMLSerializer<T>
{
    protected static readonly XmlSerializer serializer = new XmlSerializer(typeof(T));

    /// <summary>
    /// XMLファイルを読み込む
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public static T Load(string path)
    {
        T loaddata;

        using (var sr = new StreamReader(path, new System.Text.UTF8Encoding(false)))
        {
            loaddata = (T)serializer.Deserialize(sr);
            sr.Close();
        }

        return loaddata;
    }

    /// <summary>
    /// XMLファイルに保存する
    /// </summary>
    /// <param name="path"></param>
    public void Save(string path)
    {
        using (var sw = new StreamWriter(path, false, new System.Text.UTF8Encoding(false)))
        {
            var ns = new XmlSerializerNamespaces();
            ns.Add(String.Empty, String.Empty);
            serializer.Serialize(sw, this, ns);
            sw.Close();
        }
    }
}

そして、CSettingsXmlを改造します。

[XmlRoot("Settings")]
public class CSettingsXml : AXMLSerializer<CSettingsXml>
{
    /// <summary>
    /// 自分の名前
    /// </summary>
    [XmlElement("MyName")]
    public string MyName = "";

    /// <summary>
    /// 自分の年齢
    /// </summary>
    [XmlElement("MyAge")]
    public int MyAge = 0;

    /// <summary>
    /// 自分の住所
    /// </summary>
    [XmlElement("MyAddr")]
    public string MyAddr = "";
}

何が変わったかというと、Save()とLoad()をCSettingsXmlからAXMLSerializerに移動。
そして、CSettingsXmlをAXMLSerializerのサブクラスにしたわけですね。
こうすると、XMLが増えてもAXMLSerializerを継承すれば、すぐにSave()とLoad()が使えるようになります。

つくしぃ。。。

しかし、public static T Load(string path)を見て分かる通り、Tとはなんぞや?と思われた方もいるでしょう。
これが噂のジェネリック型というやつです。(※医薬品ではありません)

要は、キャスト無しで型を選べるようになるということです。

はるか昔、C言語が繁栄したときは、

func(int type, char *param)
{
    if(1 == type)
    {
        func2((long *)param);
    }
    else if(2 == type)
    {
        func3((int *)param);
    }
    else
    {
        func4((short *)param);
    }
}

のようにしてパラメータを汎用化していた頃がありました。
(普通はparamの型は構造体かな?ちょっと例が悪いですが。。。)

でもジェネリックを使うと、

public class CSettingsXml : AXMLSerializer<CSettingsXml>

と定義するだけで、Tの部分がCSettingsXmlに置き換わり、キャストが不要になるのです!

詳細はジェネリックで検索、検索~☆

今回はここまでにしましょう。