C#でXMLファイルを読み書きする③

こんにちは、からすぱんです。

突如始まったC#でXMLファイルを読み書きシリーズ、今回で最後です。

3回目となる今回は、XMLデータ格納用クラスのプロパティに指定するカテゴリ分けについてご紹介できたらと思います。


XMLデータ格納用クラスのプロパティに指定するカテゴリ分けとは?

なんともわかりにくい表現ですね。。簡単に言えば、タグの属性なのか、要素なのか、はたまたルート要素なのかをクラスやプロパティに指定する方法のことです。

特に指定がない場合は、プロパティはすべてタグの要素として認識されます。

例えば、こちらは前回、前々回で使用したXMLデータ格納用クラスの定義です。Keyプロパティのヘッダに『[XmlAttribute(“Key”)]』とあり、その他のプロパティのヘッダには記述がありません。

これはつまり、出力結果を見るとわかる通り、Keyプロパティのみが属性で、他のプロパティは要素として認識・処理されます。

/// <summary>
/// XMLに格納する1つのデータ要素を格納する
/// </summary>

public class XmlData
{
    /// <summary>
    /// Key属性
    /// </summary>
    [XmlAttribute("Key")]
    public string Key { get; set; }

    /// <summary>
    /// 名前要素
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// ID要素
    /// </summary>
    public int Id { get; set; }
}

▼出力結果

<?xml version="1.0"?>
<XmlData Key="Test">
    <Name>name</Name>
    <Id>1</Id>
</XmlData>

主によく使う属性に、下記の4つがあります。

System.Xml.Serialization.XmlRootXMLのルートタグを表す
System.Xml.Serialization.XmlElementXMLのタグ要素を表す
System.Xml.Serialization.XmlAttributeXMLのタグ要素の属性を表す
System.Xml.Serialization.XmlTextXMLのタグの内容(テキスト)を表す

これらを使って、下記のXMLデータを扱ってみます。

<?xml version="1.0"?>
<person>
    <name>山田花子</name>
    <programming>C#</programming>
    <project id="1">
        <name>〇〇システム</name>
        <start_date>2021/01/01</start_date>
        <end_date>2021/05/01</end_date>
        <company>〇〇株式会社</company>
    </project>
    <project id="2">
        <name>△△システム</name>
        <start_date>2021/03/01</start_date>
        <end_date></end_date>
        <company>株式会社△△</company>
    </project>
</person>

上記XMLに対応するようクラスを作成していきます。

/// <summary>
/// Personクラス
/// </summary>
[System.Xml.Serialization.XmlRoot("person")]
public class Person
{
    /// <summary>
    /// 担当者名
    /// </summary>
    [System.Xml.Serialization.XmlElement("name")]
    public string Name { get; set; }

    /// <summary>
    /// 主なプログラミング言語
    /// </summary>
    [System.Xml.Serialization.XmlElement("programing")]
    public string Programing { get; set; }

    /// <summary>
    /// 担当プロジェクトリスト
    /// </summary>
    [System.Xml.Serialization.XmlElement("project")]
    public List<Project> ProjectList { get; set; }
}

/// <summary>
/// Projectクラス
/// </summary>
public class Project
{
    /// <summary>
    /// ID
    /// </summary>
    [System.Xml.Serialization.XmlAttribute("id")]
    public int Id { get; set; }

    /// <summary>
    /// プロジェクト名
    /// </summary>
    [System.Xml.Serialization.XmlElement("name")]
    public string Name { get; set; }

    /// <summary>
    /// プロジェクト開始日
    /// </summary>
    [System.Xml.Serialization.XmlElement("start_date")]
    public string StartDate { get; set; }

    /// <summary>
    /// プロジェクト終了日
    /// </summary>
    [System.Xml.Serialization.XmlElement("ends_date")]
    public string EndDate { get; set; }

    /// <summary>
    /// 会社情報
    /// </summary>
    [System.Xml.Serialization.XmlElement("company")]
    public Company Company { get; set; }
}

/// <summary>
/// Companyクラス
/// </summary>
public class Company
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public Company()
    {
        // **** シリアライズの際に、引数なしのデフォルトコンストラクタが必ず必要 ****
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="name">会社名</param>
    public Company(string name)
    {
        this.Name = name;
    }

    /// <summary>
    /// 会社名
    /// </summary>
    [System.Xml.Serialization.XmlText()]
    public string Name { get; set; }
}

担当プロジェクトは複数あるため、List<Project>を使用しています。

CompanyクラスのNameプロパティにはXmlTextを指定しています。また、シリアライズの際には引数なしのコンストラクタが必ず必要になるので、引数ありコンストラクタを作成する場合はデフォルトコンストラクタも必ず作成するよう注意が必要です。

シリアライズ・デシリアライズした結果をみていきましょう。

シリアライズ

public static void Main(string[] args)
{
    string filePath = @"C:\sample.xml";

    Person person = new Person();
    person.Name = "山田花子";
    person.Programing = "C#";
    person.ProjectList = new System.Collections.Generic.List<Project>();

    person.ProjectList.Add(new Project()
    {
        Id = 1,
        Name = "○○システム",
        StartDate = "2021/01/01",
        EndDate = "2021/05/01",
        Company = new Company("○○株式会社")
    });

    person.ProjectList.Add(new Project()
    {
        Id = 2,
        Name = "△△システム",
        StartDate = "2021/03/01",
        EndDate = "",
        Company = new Company("株式会社△△")
    });

    // 余計なネームスペースは削除
    var namespaces = new XmlSerializerNamespaces();
    namespaces.Add("", "");

    using (FileStream fs = new FileStream(filePath, FileMode.Create))
    {
        // シリアライズ
        XmlSerializer serializer = new XmlSerializer(typeof(Person));
        serializer.Serialize(fs, person, namespaces);
    }
}

▼出力結果

<?xml version="1.0"?>
<person>
  <name>山田花子</name>
  <programing>C#</programing>
  <project id="1">
    <name>○○システム</name>
    <start_date>2021/01/01</start_date>
    <end_date>2021/05/01</end_date>
    <company>○○株式会社</company>
  </project>
  <project id="2">
    <name>△△システム</name>
    <start_date>2021/03/01</start_date>
    <end_date />
    <company>株式会社△△</company>
  </project>
</person>

デシリアライズ

public static void Main(string[] args)
{
    string filePath = @"C:\sample.xml";

    using (FileStream fs = new FileStream(filePath, FileMode.Open))
    {
        // デシリアライズ
        XmlSerializer serializer = new XmlSerializer(typeof(Person));
        var data = serializer.Deserialize(fs) as Person;

        Console.WriteLine("担当者名: {0}, 主なプログラミング言語: {1}", data.Name, data.Programing);

        foreach (var project in data.ProjectList)
        {
            Console.WriteLine(
                "    プロジェクトID: {0}, プロジェクト名: {1}, 開始日: {2}, 終了日: {3}, 会社名: {4}",
                project.Id, project.Name, project.StartDate, project.EndDate, project.Company.Name);
        }

        Console.Read();
    }
}

▼出力結果

担当者名: 山田花子, 主なプログラミング言語: C#
    プロジェクトID: 1, プロジェクト名: ○○システム, 開始日: 2021/01/01, 終了日: 2021/05/01, 会社名: ○○株式会社
    プロジェクトID: 2, プロジェクト名: △△システム, 開始日: 2021/03/01, 終了日: , 会社名: 株式会社△△

シリアライズ・デシリアライズともにうまくいきました。

他にも、クラスプロパティはPublicにしたいけど、XMLには出力したくない場合の対策等、まだまだたくさん使える機能がたくさんありますので、またの機会にご紹介できたらと思います。

ではまた。