インスタンスをファイルに保存、読み出す方法(XMLフォーマット編)

2021年1月17日

自作クラスのインスタンスなどをXML形式で保存、読み出しをします。

準備

Windows版Visual Studio2019では、次の参照の追加をしてください。

System.Runtime.Serialization

サンプルクラスを作ります。

このクラスには、練習用サンプルとして次の型がメンバーとして含まれています。

  • フィールド
  • プロパティ
  • Dictionary型
  • List型


インスタンス毎でまとまったデータを扱いたいときに利用できます。

namespace LoadSaveSample
{
    class Program
    {
        static void Main(string[] args)
        {
        }
    }

    class Player
    {
        public string Name;
        public int Hp { get; set; }
        public Dictionary<string, string> Soubi;
        public List<string> Items;
    }
}

サンプルクラスのインスタンスを作ります。

注意)Mainメソッドの部分のみ表記しています
Player型のインスタンスを作成、各メンバー初期値を設定しています。

static void Main(string[] args)
{
    Player hero = new Player();
    hero.Name = "さいたま";
    hero.Hp = 1000;
    hero.Soubi = new Dictionary<string, string>
    {
        ["鎧"] = "鉄のヨロイ",
        ["兜"] = "鉄のカブト",
    };
    hero.Soubi.Add("刀", "鋼のつるぎ");

    hero.Items = new List<string>
    {
        "banana",
        "apple"
    };
    hero.Items.Add("orange");
}

DataContractSerializerによってシリアライズされるクラスに変更します。
(DataMember属性をつけるとシリアル化の対象になります。)

DataContractSerializer クラスを使用して、ある型のインスタンスを XML ストリームまたはドキュメントにシリアル化または逆シリアル化します。シリアライズ(インスタンスをファイルに読み書きできるように変換すること)のため、各メンバーに属性をつけます

// Name要素は必須ではありません。
[DataContract(Name = "プレイヤー")]
class Player
{
    // プライベートでもDataMemberAttributeがあればシリアル化される
    [DataMember]
    public string Name;
    [DataMember(Name = "ヒットポイント")]
    public int Hp { get; set; }
    [DataMember]
    public Dictionary<string, string> Soubi;
    [DataMember]
    public List<string> Items;
}

保存

型のインスタンスを XML ストリームにシリアル化します。

var xml = new DataContractSerializer(typeof(Player));

ファイルへの保存

XMLストリームをファイルストリームで保存します。
作成した型でファイルに保存することができます。WriteObjectメソッドを使います。

using (var fs = new FileStream("Player.xml", FileMode.Create))
{
    xml.WriteObject(fs, hero);
}

実行ファイルがある場所にPlayer.xmlファイルが作成されたことを確認します。
通常、¥bin¥Debugの中

<プレイヤー xmlns="http://schemas.datacontract.org/2004/07/LoadSaveSample" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Items xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <a:string>banana</a:string>
        <a:string>apple</a:string>
        <a:string>orange</a:string>
    </Items>
    <Name>さいたま</Name>
    <Soubi xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <a:KeyValueOfstringstring>
            <a:Key>鎧</a:Key>
            <a:Value>鉄のヨロイ</a:Value>
        </a:KeyValueOfstringstring>
        <a:KeyValueOfstringstring>
            <a:Key>兜</a:Key>
            <a:Value>鉄のカブト</a:Value>
        </a:KeyValueOfstringstring>
        <a:KeyValueOfstringstring>
            <a:Key>刀</a:Key>
            <a:Value>鋼のつるぎ</a:Value>
        </a:KeyValueOfstringstring>
    </Soubi>
    <ヒットポイント>1000</ヒットポイント>
</プレイヤー>

読み出し

ファイルからの読み出し

XmlDictionaryReader型の変数にファイルから読み出したデータを代入します。

// ファイルから読み出し
XmlDictionaryReader reader;
using (var fs = new FileStream("Player.xml", FileMode.Open))
{
    reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
}

データをデシリアライズし、インスタンスから読み取ります。(usingブロック内に記述)

hero = (Player)xml.ReadObject(reader, true);

すべてのコードについて

using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Xml;

namespace LoadSaveSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Player hero = new Player();
            hero.Name = "さいたま";
            hero.Hp = 1000;
            hero.Soubi = new Dictionary<string, string>
            {
                ["鎧"] = "鉄のヨロイ",
                ["兜"] = "鉄のカブト",
            };
            hero.Soubi.Add("刀", "鋼のつるぎ");

            hero.Items = new List<string>
            {
                "banana",
                "apple"
            };
            hero.Items.Add("orange");

            // 型のインスタンスを XML ストリームにシリアル化します。
            var xml = new DataContractSerializer(typeof(Player));

            // ファイルへの書き込み
            using (var fs = new FileStream("Player.xml", FileMode.Create))
            {
                xml.WriteObject(fs, hero);
            }
            // ファイルから読み出し
            XmlDictionaryReader reader;
            using (var fs = new FileStream("Player.xml", FileMode.Open))
            {
                reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
                // データをデシリアライズし、インスタンスから読み取ります。
                hero = (Player)xml.ReadObject(reader, true);
            }
        }
    }
    // DataContractAttributeまたはSerializableAttributeを適用する必要があります
    // DataContractSerializerによってシリアライズされるクラスに変換します。
    // Name要素は必須ではありません。
    [DataContract(Name = "プレイヤー")]
    class Player
    {
        // プライベートでもDataMemberAttributeがあればシリアル化される
        [DataMember]
        public string Name;
        [DataMember(Name = "ヒットポイント")]
        public int Hp { get; set; }
        [DataMember]
        public Dictionary<string, string> Soubi;
        [DataMember]
        public List<string> Items;
    }
}

データ コントラクト シリアライザーでサポートされる型

リファクタリングサンプル

リファクタリングポイント

  • Playerインスタンスの初期化をコンストラクタで実施
  • ロードとセーブをXMLクラス(Static)を作成し、移動
  • 各処理のメソッド化
  • 複数の方の処理ができるようにメソッドはジェネリックに変更
  • Player型(自作型)以外のList型,Dictionary型での動作の確認
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Xml;

namespace LoadSaveSample
{
    class Program
    {
        static void Main()
        {
            #region インスタンスに設定する各初期値の代入
            var name = "さいたま";
            var hp = 1000;
            // equipmentは、日本語で「装備」
            var equipments = new Dictionary<string, string>
            {
                ["鎧"] = "鉄のヨロイ",
                ["兜"] = "鉄のカブト"
            };

            var items = new List<string>
            {
                "banana",
                "apple"
            };
            #endregion

            #region Playerクラスのインスタンを作成(インスタンス名は hero)
            // 初期値はコンストラクタで代入するパターン
            var hero = new Player(name, hp, equipments, items);

            List<Viecle> viecles = new List<Viecle>
            {
                new Viecle { Name = "フェラーリ", Power = 500 },
                new Viecle { Name = "ポルシェ", Power = 300 }
            };

            hero.Viecles = viecles;
            // データ追加サンプル
            hero.Equipments.Add("刀", "鋼のつるぎ");
            hero.Items.Add("orange");
            hero.Viecles.Add(new Viecle { Name = "ランボルギーニ", Power = 1000 });
            #endregion

            #region ロード、セーブ
            // セーブ、ロード用のファイル名
            var PlayerClassFileName = "Player.xml";

            // 保存(セーブ)引数はインスタンスとファイル名。ジェネリックで型を指定
            // XMLクラスはStaticなのでインスタンスにする必要はない(new でインスタンスを作成しない)
            // XML.Save<Player>(hero, PlayerClassFileName);

            // ジェネリック型は次のように省略できる
            XML.Save(hero, PlayerClassFileName);
            // 読み出し(ロード)引数はファイル名。ジェネリックで型を指定
            var loadHero = XML.Load<Player>(PlayerClassFileName);
            #endregion

            #region 確認のためのコンソール表示
            // 確認のためのコンソール表示(HpとName)
            Console.WriteLine($"ヒーローの名前:{loadHero.PlayerName} \nヒットポイント:{loadHero.Hp}");

            // 確認のためのコンソール表示(loadHeroのDictionary)
            Console.WriteLine("\n*loadHeroの装備一覧");
            foreach (var equipment in loadHero.Equipments)
            {
                Console.WriteLine($"{equipment.Key} -> {equipment.Value}");
            }
            // 確認のためのコンソール表示(loadHeroのList)
            Console.WriteLine("\n*loadHeroのアイテム一覧");
            foreach (var item in loadHero.Items)
            {
                Console.WriteLine($"{item}");
            }
            // 確認のためのコンソール表示(loadHeroのList<Viecle>)
            Console.WriteLine("\n*loadHeroの乗り物一覧");
            foreach (var vc in loadHero.Viecles)
            {
                Console.WriteLine($"車種 {vc.Name} :馬力 {vc.Power}");
            }
            #endregion

            #region 色々な型で確認
            // Dictionary型で確認
            var DictionaryTypeFileName = "Equipments.xml";

            XML.Save(equipments, DictionaryTypeFileName);
            var loadEquipments = XML.Load<Dictionary<string, string>>(DictionaryTypeFileName);

            // 確認のためのコンソール表示(Dictionary)
            Console.WriteLine("\n*(確認)装備だけの一覧");
            // LINQを使って表示(あくまで参考。ここまで必要ありません)
            loadEquipments.ToList().ForEach(keyPair => Console.WriteLine($"{keyPair.Key} {keyPair.Value}"));

            // List型で確認
            var ListTypeFileName = "Items.xml";

            XML.Save(items, ListTypeFileName);
            var loadItems = XML.Load<List<string>>(ListTypeFileName);

            // 確認のためのコンソール表示(List)
            Console.WriteLine("\n*(確認)アイテムだけの一覧");
            // LINQを使って表示
            loadItems.ForEach(Console.WriteLine);

            // Viecle型で確認
            var ViecleTypeFileName = "Viecle.xml";

            XML.Save(viecles, ViecleTypeFileName);
            var loadViecles = XML.Load<List<Viecle>>(ViecleTypeFileName);

            // 確認のためのコンソール表示(List)
            Console.WriteLine("\n*(確認)乗り物だけの一覧");
            // LINQを使って表示
            loadViecles.ForEach(x => Console.WriteLine($"{x.Name} : {x.Power}"));
            #endregion
        }
    }
    // DataContractAttributeまたはSerializableAttributeを適用する必要があります
    // DataContractSerializerによってシリアライズされるクラスに変換します。
    // Name要素は必須ではありません。
    [DataContract(Name = "プレイヤー")]
    class Player
    {
        // プライベートでもDataMemberAttributeがあればシリアル化される
        [DataMember]
        public string PlayerName { get; set; }
        [DataMember(Name = "ヒットポイント")]
        public int Hp { get; set; }
        [DataMember]
        public Dictionary<string, string> Equipments;
        [DataMember]
        public List<string> Items;

        [DataMember]
        public List<Viecle> Viecles;

        public Player(
            string name,
            int hp,
            Dictionary<string, string> equipments,
            List<string> items)
        {
            PlayerName = name;
            Hp = hp;
            Equipments = equipments;
            Items = items;
        }
    }

    [DataContract(Name = "乗り物")]
    public class Viecle
    {
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public int Power { get; set; }
    }

    // XMLフォーマット関連のクラス
    static class XML
    {
        // ジェネリックメソッド
        // T:タイプ(型)で、データ コントラクト シリアライザーでサポートされる型
        public static void Save<T>(T instance, string fileName)
        {
            // 型のインスタンスを XML ストリームにシリアル化します。
            var xml = new DataContractSerializer(typeof(T));

            // ファイルへの書き込み
            using (var fs = new FileStream(fileName,
                                           FileMode.Create))
            {
                xml.WriteObject(fs, instance);
            }
        }

        // ジェネリックメソッド
        // T:タイプ(型)で、データ コントラクト シリアライザーでサポートされる型
        // 戻り値も同じ型
        public static T Load<T>(string fileName)
        {
            // 型のインスタンスを XML ストリームにシリアル化します。
            var xml = new DataContractSerializer(typeof(T));
            // ファイルから読み出し
            XmlDictionaryReader reader;
            using (var fs = new FileStream(fileName,
                                           FileMode.Open))
            {
                reader = XmlDictionaryReader.CreateTextReader(
                    fs,
                    new XmlDictionaryReaderQuotas());
                // データをデシリアライズし、インスタンスから読み取ります。
                return (T)xml.ReadObject(reader, true);
            }
        }
    }
}

/*
 * データ コントラクト シリアライザーでサポートされる型
 * https://docs.microsoft.com/ja-jp/dotnet/framework/wcf/feature-details/types-supported-by-the-data-contract-serializer?view=netframework-4.8
 */

Player.xml

<プレイヤー xmlns="http://schemas.datacontract.org/2004/07/LoadSaveSample" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Equipments xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <a:KeyValueOfstringstring>
            <a:Key>鎧</a:Key>
            <a:Value>鉄のヨロイ</a:Value>
        </a:KeyValueOfstringstring>
        <a:KeyValueOfstringstring>
            <a:Key>兜</a:Key>
            <a:Value>鉄のカブト</a:Value>
        </a:KeyValueOfstringstring>
        <a:KeyValueOfstringstring>
            <a:Key>刀</a:Key>
            <a:Value>鋼のつるぎ</a:Value>
        </a:KeyValueOfstringstring>
    </Equipments>
    <Items xmlns:a="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
        <a:string>banana</a:string>
        <a:string>apple</a:string>
        <a:string>orange</a:string>
    </Items>
    <PlayerName>さいたま</PlayerName>
    <Viecles>
        <乗り物>
            <Name>フェラーリ</Name>
            <Power>500</Power>
        </乗り物>
        <乗り物>
            <Name>ポルシェ</Name>
            <Power>300</Power>
        </乗り物>
        <乗り物>
            <Name>ランボルギーニ</Name>
            <Power>1000</Power>
        </乗り物>
    </Viecles>
    <ヒットポイント>1000</ヒットポイント>
</プレイヤー>

Equipments.xml

<ArrayOfKeyValueOfstringstring xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <KeyValueOfstringstring>
        <Key>鎧</Key>
        <Value>鉄のヨロイ</Value>
    </KeyValueOfstringstring>
    <KeyValueOfstringstring>
        <Key>兜</Key>
        <Value>鉄のカブト</Value>
    </KeyValueOfstringstring>
    <KeyValueOfstringstring>
        <Key>刀</Key>
        <Value>鋼のつるぎ</Value>
    </KeyValueOfstringstring>
</ArrayOfKeyValueOfstringstring>

Items.xml

<ArrayOfstring xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <string>banana</string>
    <string>apple</string>
    <string>orange</string>
</ArrayOfstring>

Viecle.xml

<ArrayOf乗り物 xmlns="http://schemas.datacontract.org/2004/07/LoadSaveSample" 
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <乗り物>
        <Name>フェラーリ</Name>
        <Power>500</Power>
    </乗り物>
    <乗り物>
        <Name>ポルシェ</Name>
        <Power>300</Power>
    </乗り物>
    <乗り物>
        <Name>ランボルギーニ</Name>
        <Power>1000</Power>
    </乗り物>
</ArrayOf乗り物>

C#,XML

Posted by hidepon