scrap book

( ..)φメモメモ

(C#) 変更を検知して再読み込みする設定ファイルクラス

ソースコード

main

using System;
using System.Threading;

namespace ReloadableConfiguration
{
    class Program
    {
        static void Main(string[] args)
        {
            // SimpleConfigの使い方サンプル
            UseSimpleConfig();


            // ReloadableConfigの使い方サンプル
            Console.WriteLine();
            UseReloadableConfig();


            // キー入力待ち
            Console.ReadKey();
        }

        /// <summary>
        /// SimpleConfigの使い方サンプル
        /// </summary>
        static void UseSimpleConfig()
        {
            //
            //
            //
            WriteTitle("(a-1) 初回アクセス時に読み込み");

            // ファイルパスを指定
            SimpleConfig.FilePath = @"SampleFile\Sample1.xml";

            // 読み込んだ内容を出力
            Console.WriteLine("Name : " + SimpleConfig.Config.Name);

            // 設定内容を出力
            Console.WriteLine("--------------------------------");
            Console.WriteLine(SimpleConfig.Config.ToXmlStirng());


            //
            //
            //
            WriteTitle("(a-2) 明示的読み込み");

            // ファイルパスを指定
            SimpleConfig.FilePath = @"SampleFile\Sample2.xml";

            // 明示的に読み込み、成否を出力
            bool isLoadSuccess = SimpleConfig.Load();
            Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗"));

            // 読み込んだ内容を出力
            Console.WriteLine("Name : " + SimpleConfig.Config.Name);


            //
            //
            //
            WriteTitle("(a-3) 明示的読み込み(失敗)");

            // 存在しないファイルパスを指定
            SimpleConfig.FilePath = @"SampleFile\InvalidFilePath.xml";

            // 明示的に読み込み、成否を出力
            isLoadSuccess = SimpleConfig.Load();
            Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗"));

            // 読み込んだ内容を出力
            //   成功時の内容が保持される
            Console.WriteLine("Name : " + SimpleConfig.Config.Name);
        }

        /// <summary>
        /// ReloadableConfigの使い方サンプル
        /// </summary>
        static void UseReloadableConfig()
        {
            //
            //
            //
            WriteTitle("(b-1) 初回アクセス時に読み込み");

            // ファイルパスを指定
            ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReload1.xml";

            // 読み込んだ内容を出力
            Console.WriteLine("Name : " + ReloadableConfig.Config.Name);

            // 設定内容を出力
            Console.WriteLine("--------------------------------");
            Console.WriteLine(ReloadableConfig.Config.ToXmlStirng());


            // 設定値の"名前"について値を変更し、ファイルを生成
            // ->  検知されないことを期待
            ReloadableConfig.Config.Name = "expect : no detect 1";
            XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true);
            Thread.Sleep(500);


            //
            // 
            //
            WriteTitle("(b-2) 明示的読み込み");

            // ファイルパスを指定
            ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReload2.xml";

            // 明示的に読み込み、成否を出力
            bool isLoadSuccess = ReloadableConfig.Load();
            Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗"));

            // 読み込んだ内容を出力
            Console.WriteLine("Name : " + ReloadableConfig.Config.Name);


            //
            //
            //
            WriteTitle("(b-3) 明示的読み込み(失敗)");

            // 存在しないファイルパスを指定
            ReloadableConfig.FilePath = @"SampleFileReloadable\InvalidFilePath.xml";

            // 明示的に読み込み、成否を出力
            isLoadSuccess = ReloadableConfig.Load();
            Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗"));

            // 読み込んだ内容を出力
            //   成功時の内容が保持される
            Console.WriteLine("Name : " + ReloadableConfig.Config.Name);


            //
            //
            //
            WriteTitle("(b-4) 生成の検知");

            // ファイルパスを指定
            ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReloadEnabled1.xml";

            // 明示的に読み込み、成否を出力
            isLoadSuccess = ReloadableConfig.Load();
            Console.WriteLine("読込" + (isLoadSuccess ? "成功" : "失敗"));

            // 読み込んだ内容を出力
            Console.WriteLine("Name            : " + ReloadableConfig.Config.Name);
            Console.WriteLine("EnableFileWatch : " + ReloadableConfig.Config.EnableFileWatch);

            // ファイルを削除
            System.IO.File.Delete(ReloadableConfig.FilePath);
            Thread.Sleep(500);

            // 設定値の"名前"について値を変更し、ファイルを生成
            // ->  この時点でCreatedイベントハンドラが動作することを期待
            // ->  Created がハンドルされた
            ReloadableConfig.Config.Name = "sample reloadable enabled - created";
            XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true);
            Thread.Sleep(500);


            //
            //
            //
            WriteTitle("(b-5) 更新の検知");

            // 設定値の"名前"について値を変更し、ファイルを生成
            // ->  この時点でChangedイベントハンドラが動作することを期待
            // ->  Changed -> Changed がハンドルされた
            ReloadableConfig.Config.Name = "sample reloadable enabled - changed";
            XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true);
            Thread.Sleep(500);


            //
            //
            //
            WriteTitle("(b-6) 名前変更の検知");

            // 設定ファイルをリネームし、監視対象から外す
            // ->  検知されないことを期待
            // ->  Changed がハンドルされた
            if (System.IO.File.Exists(ReloadableConfig.FilePath + @".AfterRename"))
            {
                System.IO.File.Delete(ReloadableConfig.FilePath + @".AfterRename");
            }
            System.IO.File.Move(ReloadableConfig.FilePath, ReloadableConfig.FilePath + @".AfterRename");
            Thread.Sleep(500);

            // 検知されたことが分かるように設定値の"名前"について値を変更し、ファイルを生成
            ReloadableConfig.Config.Name = "sample reloadable enabled - renamed";
            XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath + @".BeforeRename", true);

            // 設定ファイルをリネームし、監視対象にする
            // ->  この時点でChangedイベントハンドラが動作することを期待
            // ->  Changed がハンドルされた
            System.IO.File.Move(ReloadableConfig.FilePath + @".BeforeRename", ReloadableConfig.FilePath);
            Thread.Sleep(500);


            //
            //
            //
            WriteTitle("(b-7) ファイル監視状態の変更");

            //
            ReloadableConfig.FilePath = @"SampleFileReloadable\SampleReloadDisabled1.xml";
            ReloadableConfig.Load();

            // 設定値の"名前"について値を変更し、ファイルを生成
            // ->  検知されないことを期待
            // ->  検知されない
            ReloadableConfig.Config.Name = "reload is disabled??";
            XmlFileHelper.GenerateXmlFile(ReloadableConfig.Config, ReloadableConfig.FilePath, true);
            Thread.Sleep(500);

            Console.WriteLine(Environment.NewLine + "end.");


            // finalize
            ReloadableConfig.FinalizeFileWatch();
        }

        static void WriteTitle(string title)
        {
            Console.WriteLine();
            Console.WriteLine("================================");
            Console.WriteLine($" {title}");
            Console.WriteLine("================================");
        }
    }
}

(比較的)シンプルな設定値クラス

目標その1。

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

namespace ReloadableConfiguration
{
    /// <summary>
    /// 設定
    /// </summary>
    [Serializable]
    public class SimpleConfig
    {
        #region inner class
        [Serializable]
        public class BoxVolume
        {
            [XmlAttribute]
            public int Value { get; set; }
            [XmlAttribute]
            public string Unit { get; set; }
        }

        public enum BoxItemType
        {
            Undefined,
            Fruit,
            Fish
        }

        [Serializable]
        public class BoxItem
        {
            [XmlAttribute]
            public BoxItemType BoxItemType { get; set; }
            [XmlElement]
            public string Name { get; set; }
            [XmlElement]
            public int Count { get; set; }
        }

        #endregion

        #region クラスフィールド/クラスプロパティ
        /// <summary>
        /// 設定
        /// </summary>
        private static SimpleConfig config;

        /// <summary>
        /// 設定
        /// </summary>
        public static SimpleConfig Config
        {
            get
            {
                if (config == null)
                {
                    Load();
                }

                return config;
            }
        }

        /// <summary>
        /// ファイルパス
        /// </summary>
        public static string FilePath { get; set; }

        #endregion


        [XmlAttribute]
        public string Name { get; set; }
        public BoxVolume Volume { get; set; }
        public List<BoxItem> Items { get; set; }


        #region インスタンスメソッド
        /// <summary>
        /// コンストラクタ
        /// </summary>
        private SimpleConfig()
        {
        }

        /// <summary>
        /// XML文字列化
        /// </summary>
        /// <returns>XML文字列化</returns>
        public string ToXmlStirng()
        {
            var xmlString = string.Empty;

            try
            {
                XmlSerializer serializer = new XmlSerializer(config.GetType());
                using (StringWriter writer = new StringWriter())
                {
                    serializer.Serialize(writer, config);
                    xmlString = writer.ToString();
                }
            }
            catch
            {
            }

            return xmlString;
        }

        #endregion

        #region クラスメソッド
        /// <summary>
        /// 読込
        /// </summary>
        /// <returns>成否</returns>
        public static bool Load()
        {
            bool result = true;

            try
            {
                XmlSerializer serializer = new XmlSerializer(typeof(SimpleConfig));
                using (StreamReader reader = new StreamReader(FilePath))
                {
                    config = (SimpleConfig)serializer.Deserialize(reader);
                }
            }
            catch
            {
                result = false;
            }

            return result;
        }

        #endregion
    }
}

再読み込みする設定値クラス

目標その2~4。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.Serialization;
using System.Windows;

namespace ReloadableConfiguration
{
    /// <summary>
    /// 設定
    /// </summary>
    [Serializable]
    public class ReloadableConfig
    {
        #region inner class
        [Serializable]
        public class BoxVolume
        {
            [XmlAttribute]
            public int Value { get; set; }
            [XmlAttribute]
            public string Unit { get; set; }
        }

        public enum BoxItemType
        {
            Undefined,
            Fruit,
            Fish
        }

        [Serializable]
        public class BoxItem
        {
            [XmlAttribute]
            public BoxItemType BoxItemType { get; set; }
            [XmlElement]
            public string Name { get; set; }
            [XmlElement]
            public int Count { get; set; }
        }

        #endregion

        #region クラスフィールド/クラスプロパティ
        /// <summary>
        /// 設定
        /// </summary>
        private static ReloadableConfig config;

        /// <summary>
        /// 設定
        /// </summary>
        public static ReloadableConfig Config
        {
            get
            {
                if (config == null)
                {
                    Load();
                }

                return config;
            }
        }

        /// <summary>
        /// ファイルパス
        /// </summary>
        public static string FilePath { get; set; }

        /// <summary>
        /// ファイル監視
        /// </summary>
        private static FileSystemWatcher watcher;

        #endregion


        /// <summary>
        /// ファイル監視有効化状態
        /// </summary>
        public Nullable<bool> EnableFileWatch { get; set; } = null;

        [XmlAttribute]
        public string Name { get; set; }
        public BoxVolume Volume { get; set; }
        public List<BoxItem> Items { get; set; }


        #region インスタンスメソッド
        /// <summary>
        /// コンストラクタ
        /// </summary>
        private ReloadableConfig()
        {
        }

        /// <summary>
        /// XML文字列化
        /// </summary>
        /// <returns>XML文字列化</returns>
        public string ToXmlStirng()
        {
            var xmlString = string.Empty;

            try
            {
                XmlSerializer serializer = new XmlSerializer(config.GetType());
                using (StringWriter writer = new StringWriter())
                {
                    serializer.Serialize(writer, config);
                    xmlString = writer.ToString();
                }
            }
            catch
            {
            }

            return xmlString;
        }

        #endregion

        #region クラスメソッド
        /// <summary>
        /// 読込
        /// </summary>
        /// <returns>成否</returns>
        public static bool Load()
        {
            bool result = true;

            try
            {
                XmlSerializer serializer = new XmlSerializer(typeof(ReloadableConfig));
                using (StreamReader reader = new StreamReader(FilePath))
                {
                    config = (ReloadableConfig)serializer.Deserialize(reader);
                }

                // ファイル監視を設定
                ConfigureFileWatch();
            }
            catch
            {
                result = false;
            }

            return result;
        }

        /// <summary>
        /// ファイル監視設定
        /// </summary>
        private static void ConfigureFileWatch()
        {
            // インスタンス生成が未だの場合、インスタンス生成と初期化を実施
            if (watcher == null)
            {
                watcher = new FileSystemWatcher();

                watcher.Created += Watcher_Created;
                watcher.Changed += Watcher_Changed;
                watcher.Renamed += Watcher_Renamed;

                watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
                watcher.IncludeSubdirectories = false;
            }

            // 一時的にファイル監視を無効化
            watcher.EnableRaisingEvents = false;

            // 監視対象ファイルパスを設定
            watcher.Path = Path.GetDirectoryName(FilePath);
            watcher.Filter = Path.GetFileName(FilePath);

            // 設定値に従いファイル監視の有効/無効を変更
            watcher.EnableRaisingEvents = config.EnableFileWatch.GetValueOrDefault(false);
        }

        /// <summary>
        /// ファイル監視終了
        /// </summary>
        public static void FinalizeFileWatch()
        {
            watcher?.Dispose();
            watcher = null;
        }

        /// <summary>
        /// ファイル監視イベント(作成)
        /// </summary>
        /// <param name="sender">イベント元</param>
        /// <param name="e">イベント引数</param>
        private static void Watcher_Created(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name + "()");
            Console.WriteLine("  " + e.ChangeType);
            Console.WriteLine("  " + e.FullPath);

            if (Path.Combine(((FileSystemWatcher)sender).Path, ((FileSystemWatcher)sender).Filter) == e.FullPath)
            {
                Load();
                Console.WriteLine("  reloaded!! : " + config.Name);
            }
        }

        /// <summary>
        /// ファイル監視イベント(変更)
        /// </summary>
        /// <param name="sender">イベント元</param>
        /// <param name="e">イベント引数</param>
        private static void Watcher_Changed(object sender, FileSystemEventArgs e)
        {
            Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name + "()");
            Console.WriteLine("  " + e.ChangeType);
            Console.WriteLine("  " + e.FullPath);

            if (Path.Combine(((FileSystemWatcher)sender).Path, ((FileSystemWatcher)sender).Filter) == e.FullPath)
            {
                Load();
                Console.WriteLine("  reloaded!! : " + config.Name);
            }
        }

        /// <summary>
        /// ファイル監視イベント(リネーム)
        /// </summary>
        /// <param name="sender">イベント元</param>
        /// <param name="e">イベント引数</param>
        private static void Watcher_Renamed(object sender, RenamedEventArgs e)
        {
            Console.WriteLine(System.Reflection.MethodInfo.GetCurrentMethod().Name + "()");
            Console.WriteLine("  " + e.ChangeType);
            Console.WriteLine("  " + e.FullPath);

            if (Path.Combine(((FileSystemWatcher)sender).Path, ((FileSystemWatcher)sender).Filter) == e.FullPath)
            {
                Load();
                Console.WriteLine("  reloaded!! : " + config.Name);
            }
        }

        #endregion
    }
}

XMLファイル生成クラス(動作確認用のおまけ)

using System.IO;
using System.Xml.Serialization;

namespace ReloadableConfiguration
{
    /// <summary>
    /// XMLファイルヘルパー
    /// </summary>
    public static class XmlFileHelper
    {
        /// <summary>
        /// XML文字列生成
        /// </summary>
        /// <param name="obj">オブジェクト</param>
        /// <param name="filepath">ファイルパス</param>
        /// <param name="enableException">例外送出有無</param>
        /// <returns>XML文字列</returns>
        public static void GenerateXmlFile(object obj, string filepath, bool enableException = false)
        {
            try
            {
                XmlSerializer serializer = new XmlSerializer(obj.GetType());
                using (StreamWriter writer = new StreamWriter(filepath))
                {
                    serializer.Serialize(writer, obj);
                }
            }
            catch
            {
                if (enableException)
                {
                    throw;
                }
            }
        }
    }
}

設定ファイル

「シンプルな設定値クラス」用

SampleFile/Sample1.xml
<SimpleConfig Name="sample 1">
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>4</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</SimpleConfig>
SampleFile/Sample2.xml
<SimpleConfig Name="sample 2">
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>4</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</SimpleConfig>

「再読み込みする設定値クラス」用

SampleFileReloadable/SampleReload1.xml
<ReloadableConfig Name="sample reloadable 1">
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>3</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>6</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>9</Count>
    </BoxItem>
  </Items>
</ReloadableConfig>
SampleFileReloadable/SampleReload2.xml
<ReloadableConfig Name="sample reloadable 2">
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>4</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</ReloadableConfig>
SampleFileReloadable/SampleDisabled1.xml
<ReloadableConfig Name="sample reloadable disabled 1">
  <EnableFileWatch>false</EnableFileWatch>
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>4</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</ReloadableConfig>
SampleFileReloadable/SampleReloadEnabled1.xml
<ReloadableConfig Name="sample reloadable enabled 1">
  <EnableFileWatch>true</EnableFileWatch>
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>4</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</ReloadableConfig>

実行結果

「シンプルな設定値クラス」分

================================
 (a-1) 初回アクセス時に読み込み
================================
Name : sample 1
--------------------------------
<?xml version="1.0" encoding="utf-16"?>
<SimpleConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="sample 1">
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>4</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</SimpleConfig>

================================
 (a-2) 明示的読み込み
================================
読込成功
Name : sample 2

================================
 (a-3) 明示的読み込み(失敗)
================================
読込失敗
Name : sample 2

「再読み込みする設定値クラス」分

================================
 (b-1) 初回アクセス時に読み込み
================================
Name : sample reloadable 1
--------------------------------
<?xml version="1.0" encoding="utf-16"?>
<ReloadableConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="sample reloadable 1">
  <EnableFileWatch xsi:nil="true" />
  <Volume Value="10" Unit="kL" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>3</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>6</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>9</Count>
    </BoxItem>
  </Items>
</ReloadableConfig>

================================
 (b-2) 明示的読み込み
================================
読込成功
Name : sample reloadable 2

================================
 (b-3) 明示的読み込み(失敗)
================================
読込失敗
Name : sample reloadable 2

================================
 (b-4) 生成の検知
================================
読込成功
Name            : sample reloadable enabled 1
EnableFileWatch : True
Watcher_Created()
  Created
  SampleFileReloadable\SampleReloadEnabled1.xml
  reloaded!! : sample reloadable enabled - created

================================
 (b-5) 更新の検知
================================
Watcher_Changed()
  Changed
  SampleFileReloadable\SampleReloadEnabled1.xml
  reloaded!! : sample reloadable enabled - changed

================================
 (b-6) 名前変更の検知
================================
Watcher_Renamed()
  Renamed
  SampleFileReloadable\SampleReloadEnabled1.xml.AfterRename
Watcher_Renamed()
  Renamed
  SampleFileReloadable\SampleReloadEnabled1.xml
  reloaded!! : sample reloadable enabled - renamed

================================
 (b-7) ファイル監視状態の変更
================================

end.

所感

軽い気持ちで作り始めたけど結構かかってしまった。それなりの量なので動作確認をちゃんとテストコードに起こした方がいい。けどもう眠いので諦める。。説明つける気力もない。。反省点としては「設定値」と「ファイル監視」をもっと疎にすればよかった…かな?大して変わらないかも。

FileSystemWatcherについて
  • Filterから外れるリネームの場合でもRenamedイベントが発生する。意外だった。Renamedイベントハンドラは「変更後」がFilterと合う場合のみ再読み込みするようにした。
  • 書き込み側アプリの挙動次第で発生するイベントが変わる面がある(というかいつイベントが発生するか厳密に理解していない)ので、Renamedイベントハンドラ以外もとりあえずガード入れた。

(C#) シリアライズ可能なオブジェクトを階層化して表示

概要

前々回と前回(以下)からの続き。

シリアライズ可能なオブジェクトをXML文字列にしたり、さらにそれをXDocumentを使って解析して、見やすい形で表示したりしてみた。

バージョン

.NET Framework 4.7.2
C# 4.7

目次

ソースコード

StringBuilder拡張クラス

表示用に拡張メソッド用意しただけ。今回の本題とはあまり関係ない。ほぼ前回からの流用。

using System;
using System.Globalization;
using System.Text;

namespace ObjectToXmlString
{
    /// <summary>
    /// StringBuilder拡張クラス
    /// </summary>
    public static class StringBuilderExtensions
    {
        /// <summary>
        /// 書式指定文字列追加
        /// 改行付き
        /// </summary>
        /// <param name="stringBuilder">StringBuilder</param>
        /// <param name="format">書式</param>
        /// <param name="args">可変長引数</param>
        public static void AppendFormatLine(this StringBuilder stringBuilder,
            string format, params object[] args)
        {
            stringBuilder.AppendFormat(
                CultureInfo.CurrentCulture,
                "{0}{1}", string.Format(format, args), Environment.NewLine);
        }

        /// <summary>
        /// 書式指定文字列追加
        /// 改行および先頭空白インデント付き
        /// </summary>
        /// <param name="stringBuilder">StringBuilder</param>
        /// <param name="indent">インデント</param>
        /// <param name="format">書式</param>
        /// <param name="args">可変長引数</param>
        public static void AppendFormatLine(this StringBuilder stringBuilder,
            int indent, string format, params object[] args)
        {
            stringBuilder.AppendFormat(
                CultureInfo.CurrentCulture,
                "{0}{1}{2}", new string(' ', indent), string.Format(format, args), Environment.NewLine);
        }
    }
}

XML文字列化クラス

オブジェクトをXML文字列にするだけのクラス。前回からの流用。

using System.IO;
using System.Xml.Serialization;

namespace ObjectToXmlString
{
    /// <summary>
    /// XML文字列ヘルパー
    /// </summary>
    public static class XmlStringHelper
    {
        /// <summary>
        /// XML文字列生成
        /// </summary>
        /// <param name="obj">オブジェクト</param>
        /// <param name="enableException">例外送出有無</param>
        /// <returns>XML文字列</returns>
        public static string GenerateXmlStirng(object obj, bool enableException = false)
        {
            var xmlString = string.Empty;

            try
            {
                XmlSerializer serializer = new XmlSerializer(obj.GetType());
                using (StringWriter writer = new StringWriter())
                {
                    serializer.Serialize(writer, obj);
                    xmlString = writer.ToString();
                }
            }
            catch
            {
                if (enableException)
                {
                    throw;
                }
            }

            return xmlString;
        }
    }
}

加工して表示するクラス

今回の本題。ただし説明するより実行結果見た方が早そうなので説明なし。

using System;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Collections.Generic;

namespace ObjectToXmlString
{
    /// <summary>
    /// ダンプヘルパー
    /// </summary>
    public static class DumpHelper
    {
        /// <summary>
        /// XML文字列生成(軽量版)
        /// XML宣言とルート要素のXMLNS名前空間の属性を削除したXML文字列化
        /// </summary>
        /// <param name="obj">オブジェクト</param>
        /// <param name="enableException">例外送出有無</param>
        /// <returns>XML文字列</returns>
        public static string GenerateXmlStirngSlim(object obj, bool enableException = false)
        {
            var xmlString = string.Empty;

            try
            {
                XDocument xDoc = XDocument.Parse(
                    XmlStringHelper.GenerateXmlStirng(obj, enableException));

                xDoc.Root.Attributes()
                    .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/")
                    .ToList().ForEach(attr => attr.Remove());

                xmlString = xDoc.ToString();
            }
            catch
            {
                if (enableException)
                {
                    throw;
                }
            }

            return xmlString;
        }

        /// <summary>
        /// XML文字列生成(1行版)
        /// </summary>
        /// <param name="obj">オブジェクト</param>
        /// <param name="newLineReplacingString">改行置換文字列</param>
        /// <param name="enableException">例外送出有無</param>
        /// <returns>XML文字列</returns>
        public static string GenerateXmlStringOneLine(object obj, string newLineReplacingString, bool enableException = false)
        {
            var xmlString = string.Empty;

            try
            {
                xmlString =
                    GenerateXmlStirngSlim(obj, enableException)
                    .Replace(Environment.NewLine, newLineReplacingString);
            }
            catch
            {
                if (enableException)
                {
                    throw;
                }
            }

            return xmlString;
        }

        /// <summary>
        /// 階層化文字列生成
        /// </summary>
        /// <param name="obj">オブジェクト</param>
        /// <param name="enableException">例外送出有無</param>
        /// <returns>階層化文字列</returns>
        public static string GenerateLayeredString(object obj, bool enableException = false)
        {
            var sb = new StringBuilder();

            try
            {
                var xmlString = GenerateXmlStirngSlim(obj, enableException);
                XDocument xDoc = XDocument.Parse(xmlString);

                // 階層化表示用の文字列を生成
                foreach (var item in xDoc.Elements())
                {
                    sb.Append(GenerateLayeredStringOfElement(0, item));
                }
            }
            catch
            {
                if (enableException)
                {
                    throw;
                }

                sb.Clear();
            }

            return sb.ToString();
        }

        /// <summary>
        /// XML要素の階層化文字列生成
        /// 再帰呼び出し用
        /// </summary>
        /// <param name="indent">インデント</param>
        /// <param name="xElm">XML要素</param>
        /// <param name="xElmIndex">XML要素の配列インデックス</param>
        /// <returns階層化文字列</returns>
        private static string GenerateLayeredStringOfElement(int indent, XElement xElm, int xElmIndex = -1)
        {
            var sb = new StringBuilder();


            // 表示用書式の生成
            //   配下のXML属性およびXML要素について最長のローカル名長を取得し
            //   "key=value"のうちkey部分の桁数合わせに使用
            var elementsNoChilds = xElm.Elements().Where(e => !e.HasAttributes && !e.HasElements);
            var maxLocalNameLength = new[] {
                xElm.HasAttributes ? xElm.Attributes().Max(a => a.Name.LocalName.Length) : 0,
                elementsNoChilds.Any() ? elementsNoChilds.Max(e => e.Name.LocalName.Length) : 0,
            }.Max();
            var keyValueFormat = $"{{0,-{maxLocalNameLength}}} = {{1}}";


            // 配下にXML属性あるいはXML要素をもつ場合、自身のローカル名を表示
            if (xElm.HasAttributes || xElm.HasElements)
            {
                // 自身が配列の構成要素の場合、末尾にインデックスを付与
                if (xElmIndex >= 0)
                {
                    sb.AppendFormatLine(indent, "{0}[{1}]", xElm.Name.LocalName, xElmIndex);
                }
                else
                {
                    sb.AppendFormatLine(indent, xElm.Name.LocalName);
                }
            }

            // XML属性の表示
            foreach (var item in xElm.Attributes())
            {
                sb.AppendFormatLine(indent + 2, keyValueFormat, item.Name.LocalName, item.Value);
            }

            // 配下のXML要素が配列か判定
            var areChildArray = (xElm.Elements().Count() > 1 && xElm.Elements().All(e => e.Name == xElm.Elements().First().Name));

            // XML要素の表示
            foreach (var (item, index) in xElm.Elements().Select((item, index) => (item, index)))
            {
                // 配下にXML属性もXML要素も持たない場合、"key=value"形式で表示
                if (!item.HasAttributes && !item.HasElements)
                {
                    sb.AppendFormatLine(indent + 2, keyValueFormat, item.Name.LocalName, item.Value);
                    continue;
                }

                // 配下のXML要素が配列の場合、配列インデックスを渡す
                if (areChildArray)
                {
                    sb.Append(GenerateLayeredStringOfElement(indent + 2, item, index));
                }
                else
                {
                    sb.Append(GenerateLayeredStringOfElement(indent + 2, item));
                }

            }

            return sb.ToString();
        }
    }
}

呼び出し側コード

サンプルとして使うオブジェクトと、前述したクラスを呼び出すコード。

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

namespace ObjectToXmlString
{
    //
    // 文字列化対象
    //
    [Serializable]
    public class Box
    {
        public class BoxVolume
        {
            [XmlAttribute]
            public int Value { get; set; } = 10;
            [XmlAttribute]
            public string Unit { get; set; } = "L";
        }

        public enum BoxItemType
        {
            Undefined,
            Fruit,
            Fish
        }

        public class BoxItem
        {
            [XmlAttribute]
            public BoxItemType BoxItemType { get; set; }
            public string Name { get; set; }
            public int Count { get; set; }
        }

        [XmlAttribute]
        public string Name { get; set; } = "box a";
        public BoxVolume Volume { get; set; } = new BoxVolume();
        public List<BoxItem> Items { get; } = new List<BoxItem>() {
            new BoxItem(){ BoxItemType = BoxItemType.Fruit, Name = "Orange", Count = 1 },
            new BoxItem(){ BoxItemType = BoxItemType.Fish, Name = "Tuna", Count = 2 },
            new BoxItem(){ BoxItemType = BoxItemType.Undefined, Name = null, Count = 0 },
        };
    }


    //
    // 実行コード
    //
    class Program
    {
        static void Main(string[] args)
        {
            Box boxA = new Box();

            //
            // XML文字列
            //
            WriteResult("XML文字列", XmlStringHelper.GenerateXmlStirng(boxA));

            //
            // 加工して表示
            //
            WriteResult("XML文字列(軽量版)", DumpHelper.GenerateXmlStirngSlim(boxA));
            WriteResult("XML文字列生成(1行版)", DumpHelper.GenerateXmlStringOneLine(boxA, "#"));
            WriteResult("階層化文字列", DumpHelper.GenerateLayeredString(boxA));


            Console.ReadKey();
        }

        static void WriteResult(string title, string result)
        {
            Console.WriteLine("----------------");
            Console.WriteLine($" {title}");
            Console.WriteLine("----------------");
            Console.WriteLine($"{result}");
            Console.WriteLine();
        }
    }
}

実行結果

--------------------------------
 XML文字列
--------------------------------
<?xml version="1.0" encoding="utf-16"?>
<Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a">
  <Volume Value="10" Unit="L" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>1</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</Box>

--------------------------------
 XML文字列(軽量版)
--------------------------------
<Box Name="box a">
  <Volume Value="10" Unit="L" />
  <Items>
    <BoxItem BoxItemType="Fruit">
      <Name>Orange</Name>
      <Count>1</Count>
    </BoxItem>
    <BoxItem BoxItemType="Fish">
      <Name>Tuna</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem BoxItemType="Undefined">
      <Count>0</Count>
    </BoxItem>
  </Items>
</Box>

--------------------------------
 XML文字列生成(1行版)
--------------------------------
<Box Name="box a">#  <Volume Value="10" Unit="L" />#  <Items>#    <BoxItem BoxItemType="Fruit">#      <Name>Orange</Name>#      <Count>1</Count>#    </BoxItem>#    <BoxItem BoxItemType="Fish">#      <Name>Tuna</Name>#      <Count>2</Count>#    </BoxItem>#    <BoxItem BoxItemType="Undefined">#      <Count>0</Count>#    </BoxItem>#  </Items>#</Box>

--------------------------------
 階層化文字列
--------------------------------
Box
  Name = box a
  Volume
    Value = 10
    Unit  = L
  Items
    BoxItem[0]
      BoxItemType = Fruit
      Name        = Orange
      Count       = 1
    BoxItem[1]
      BoxItemType = Fish
      Name        = Tuna
      Count       = 2
    BoxItem[2]
      BoxItemType = Undefined
      Count       = 0

所感

XDocumentを使ってみるついでに見やすい階層化表示なんてやってみたけど、試験不十分なので、下手に表示されないリスク負うくらいなら、素直にXML文字列でもいいような気もする。でも夏休みの暇つぶしにはちょうどよかった。

(C#) XDocumentと戯れる

ソースコード

XML文字列化クラス(前回の流用)

オブジェクトをXML文字列にするだけのクラス。

using System;
using System.IO;
using System.Xml.Serialization;

namespace ObjectToXmlString
{
    /// <summary>
    /// XML文字列ヘルパー
    /// </summary>
    public static class XmlStringHelper
    {
        /// <summary>
        /// XML文字列化
        /// </summary>
        /// <param name="obj">オブジェクト</param>
        /// <param name="throwException">例外送出状態</param>
        /// <returns>XML文字列</returns>
        public static string ConvertToXmlStirng(object obj, bool throwException = false)
        {
            var xmlString = string.Empty;

            try
            {
                XmlSerializer serializer = new XmlSerializer(obj.GetType());
                using (StringWriter writer = new StringWriter())
                {
                    serializer.Serialize(writer, obj);
                    xmlString = writer.ToString();
                }
            }
            catch
            {
                if (throwException)
                {
                    throw;
                }
            }

            return xmlString;
        }
    }
}

StringBuilder拡張クラス

StringBuilderに用意されているメソッドが「書式指定付きで追加」か「改行付きで追加」しか無いので、両方付きのメソッドを用意しただけ。今回の主題とは何も関係ない。

using System;
using System.Globalization;
using System.Text;

namespace ObjectToXmlString
{
    public static class StringBuilderExtensions
    {
        public static void AppendFormatLine(this StringBuilder stringBuilder, string format, params object[] args)
        {
            stringBuilder.AppendFormat(
                CultureInfo.CurrentCulture,
                "{0}{1}", string.Format(format, args), Environment.NewLine);
        }
    }
}

XDocumentお試しコード1~4

ここからが本題。

お試し1 .. XML文字列 から XDocumentインスタンス生成

まずはXML文字列からXDocumentの生成。XDocument#Parse()で可能だった。このほかにも、XDocument#Load()でファイルから読み込めたりする。
ついでにそのまま XDocument.ToString() して分かった((2)の部分)けど、 ToString() ではXML本文のみが出力されるみたい。XML宣言部は (1) で出力できる。

using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace ObjectToXmlString
{
    public static class XDocumentTrial
    {
        /// <summary>
        /// XML文字列 から XDocumentインスタンス生成
        /// &文字列出力
        /// </summary>
        public static string Trial1(object obj)
        {
            var sb = new StringBuilder();


            // オブジェクトをXML文字列化
            var xmlString = XmlStringHelper.ConvertToXmlStirng(obj);

            // XML文字列を解析
            XDocument xDoc = XDocument.Parse(xmlString);

            // (1)
            sb.AppendLine("[ Declaration ]");
            sb.AppendLine(xDoc.Declaration.ToString());
            sb.AppendLine();

            // (2)
            sb.AppendLine("[ ToString() ]");
            sb.AppendLine(xDoc.ToString());

            return sb.ToString();
        }
    }
}
お試し2 .. 属性(Attribute)や要素(Element)の表示

属性や要素の表示を試したコード。それぞれXElement.Attributes()、XElement.Elements()でコレクションとして取得できた。ほか、XElement.Annotations()で注釈(コメントのこと?)も取得できそう。
ちなみに再帰的に辿らずにRoot要素にだけ使ってみたら、リストの部分は値が繋がってしまった。これについてはお試し4でうまく表示できた。

/// <summary>
/// 属性(Attribute)や要素(Element)の表示
/// </summary>
public static string Trial2(object obj)
{
    var sb = new StringBuilder();

    var xmlString = XmlStringHelper.ConvertToXmlStirng(obj);
    XDocument xDoc = XDocument.Parse(xmlString);

    // (1) 属性の出力
    foreach (var (item, index) in xDoc.Root.Attributes().Select((item, index) => (item, index)))
    {
        sb.AppendFormatLine("Attributes[{0}]", index);
        sb.AppendFormatLine("  Name.NamespaceName = {0}", item.Name.NamespaceName);
        sb.AppendFormatLine("  Name.LocalName     = {0}", item.Name.LocalName);
        sb.AppendFormatLine("  Value              = {0}", item.Value);
    }
    sb.AppendLine();

    // (2) 要素の出力
    foreach (var (item, index) in xDoc.Root.Elements().Select((item, index) => (item, index)))
    {
        sb.AppendFormatLine("Elements[{0}]", index);
        sb.AppendFormatLine("  Name.NamespaceName = {0}", item.Name.NamespaceName);
        sb.AppendFormatLine("  Name.LocalName     = {0}", item.Name.LocalName);
        sb.AppendFormatLine("  Value              = {0}", item.Value);
        sb.AppendFormatLine("  HasAttributes      = {0}", item.HasAttributes);
        sb.AppendFormatLine("  HasElements        = {0}", item.HasElements);
    }

    return sb.ToString();
}
お試し3 .. 属性の削除

出力結果を見ていて、xmlns名前空間の属性が邪魔だったので、消してみた。(1)の部分。

/// <summary>
/// xmlns名前空間の属性を削除
/// </summary>
public static string Trial3(object obj)
{
    var sb = new StringBuilder();

    var xmlString = XmlStringHelper.ConvertToXmlStirng(obj);
    XDocument xDoc = XDocument.Parse(xmlString);

    // (1) 名前空間が"xmlns"の属性をすべて削除
    xDoc.Root.Attributes()
        .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/")
        .ToList().ForEach(attr => attr.Remove());

    // 属性の出力
    foreach (var (item, index) in xDoc.Root.Attributes().Select((item, index) => (item, index)))
    {
        sb.AppendFormatLine("Attributes[{0}]", index);
        sb.AppendFormatLine("  Name.NamespaceName = {0}", item.Name.NamespaceName);
        sb.AppendFormatLine("  Name.LocalName     = {0}", item.Name.LocalName);
        sb.AppendFormatLine("  Value              = {0}", item.Value);
    }

    return sb.ToString();
}
お試し4 .. 再帰的に走査して階層化表示

属性を再帰的に走査して、いい感じに階層化して表示してみた。十分なパターンで試験していないけど、おおむねいい感じ。

/// <summary>
/// 再帰的に走査して階層化表示
/// </summary>
public static string Trial4(object obj, bool throwException = false)
{
    var sb = new StringBuilder();

    try
    {
        var xmlString = XmlStringHelper.ConvertToXmlStirng(obj, throwException);
        XDocument xDoc = XDocument.Parse(xmlString);

        // 名前空間が"xmlns"の属性をすべて削除
        xDoc.Root.Attributes()
            .Where(attr => attr.Name.NamespaceName == @"http://www.w3.org/2000/xmlns/")
            .ToList().ForEach(attr => attr.Remove());

        // 再帰的に走査して階層化表示
        foreach (var item in xDoc.Elements())
        {
            sb.Append(Trial4Core(item));
        }
    }
    catch
    {
        if (throwException)
        {
            throw;
        }

        sb.Clear();
    }

    return sb.ToString();
}

private static string Trial4Core(XElement xElement, int offset = 0)
{
    var sb = new StringBuilder();
    var spaces = new string(' ', offset);


    // Append Attributes
    foreach (var (item, index) in xElement.Attributes().Select((item, index) => (item, index)))
    {
        sb.AppendFormatLine("{0}Attributes[{1}]", spaces, index);
        sb.AppendFormatLine("{0}  Name.LocalName = {1}", spaces, item.Name.LocalName);
        sb.AppendFormatLine("{0}  Value          = {1}", spaces, item.Value);
    }

    // Append Elements
    foreach (var (item, index) in xElement.Elements().Select((item, index) => (item, index)))
    {
        // Append Attributes and Elements recursively
        if (item.HasAttributes || item.HasElements)
        {
            sb.AppendFormatLine("{0}{1}", spaces, item.Name.LocalName);
            sb.Append(Trial4Core(item, offset + 2));
            continue;
        }

        sb.AppendFormatLine("{0}Elements[{1}]", spaces, index);
        sb.AppendFormatLine("{0}  Name.LocalName = {1}", spaces, item.Name.LocalName);
        sb.AppendFormatLine("{0}  Value          = {1}", spaces, item.Value);
    }

    return sb.ToString();
}

呼び出し側コード

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

namespace ObjectToXmlString
{
    //
    // 文字列化対象
    //
    [Serializable]
    public class Box
    {
        public class BoxVolume
        {
            [XmlAttribute]
            public int Value { get; set; } = 10;
            [XmlAttribute]
            public string Unit { get; set; } = "L";
        }

        public class BoxItem
        {
            public string Name { get; set; }
            public int Count { get; set; }
        }

        [XmlAttribute]
        public string Name { get; set; } = "box a";
        public BoxVolume Volume { get; set; } = new BoxVolume();
        public List<BoxItem> Items { get; } = new List<BoxItem>() {
            new BoxItem(){ Name = "Orange", Count = 1 },
            new BoxItem(){ Name = "Apple", Count = 2 },
            new BoxItem(){ Name = null, Count = 0 },
        };
    }


    //
    // 実行コード
    //
    class Program
    {
        static void Main(string[] args)
        {
            Box boxA = new Box();

            //
            // XML文字列
            //
            WriteResult("XML文字列",
                XmlStringHelper.ConvertToXmlStirng(boxA));

            //
            // XDocumentお試し
            //
            WriteResult("Trial1",
                XDocumentTrial.Trial1(boxA));
            WriteResult("Trial2",
                XDocumentTrial.Trial2(boxA));
            WriteResult("Trial3",
                XDocumentTrial.Trial3(boxA));
            WriteResult("Trial4",
                XDocumentTrial.Trial4(boxA));


            Console.ReadKey();
        }

        static void WriteResult(string title, string result)
        {
            Console.WriteLine("----------------");
            Console.WriteLine($"{title}");
            Console.WriteLine("----------------");
            Console.WriteLine($"{result}");
            Console.WriteLine();
        }
    }
}

実行結果

----------------
XML文字列
----------------
<?xml version="1.0" encoding="utf-16"?>
<Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a">
  <Volume Value="10" Unit="L" />
  <Items>
    <BoxItem>
      <Name>Orange</Name>
      <Count>1</Count>
    </BoxItem>
    <BoxItem>
      <Name>Apple</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem>
      <Count>0</Count>
    </BoxItem>
  </Items>
</Box>

----------------
Trial1
----------------
[ Declaration ]
<?xml version="1.0" encoding="utf-16"?>

[ ToString() ]
<Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Name="box a">
  <Volume Value="10" Unit="L" />
  <Items>
    <BoxItem>
      <Name>Orange</Name>
      <Count>1</Count>
    </BoxItem>
    <BoxItem>
      <Name>Apple</Name>
      <Count>2</Count>
    </BoxItem>
    <BoxItem>
      <Count>0</Count>
    </BoxItem>
  </Items>
</Box>


----------------
Trial2
----------------
Attributes[0]
  Name.NamespaceName = http://www.w3.org/2000/xmlns/
  Name.LocalName     = xsi
  Value              = http://www.w3.org/2001/XMLSchema-instance
Attributes[1]
  Name.NamespaceName = http://www.w3.org/2000/xmlns/
  Name.LocalName     = xsd
  Value              = http://www.w3.org/2001/XMLSchema
Attributes[2]
  Name.NamespaceName =
  Name.LocalName     = Name
  Value              = box a

Elements[0]
  Name.NamespaceName =
  Name.LocalName     = Volume
  Value              =
  HasAttributes      = True
  HasElements        = False
Elements[1]
  Name.NamespaceName =
  Name.LocalName     = Items
  Value              = Orange1Apple20
  HasAttributes      = False
  HasElements        = True


----------------
Trial3
----------------
Attributes[0]
  Name.NamespaceName =
  Name.LocalName     = Name
  Value              = box a


----------------
Trial4
----------------
Attributes[0]
  Name.LocalName = Name
  Value          = box a
Volume
  Attributes[0]
    Name.LocalName = Value
    Value          = 10
  Attributes[1]
    Name.LocalName = Unit
    Value          = L
Items
  BoxItem
    Elements[0]
      Name.LocalName = Name
      Value          = Orange
    Elements[1]
      Name.LocalName = Count
      Value          = 1
  BoxItem
    Elements[0]
      Name.LocalName = Name
      Value          = Apple
    Elements[1]
      Name.LocalName = Count
      Value          = 2
  BoxItem
    Elements[0]
      Name.LocalName = Count
      Value          = 0

解説?は上述したので割愛。
ところでBoxItem[2].Nameにnull入れてみたのだけど表示されない(XML文字列の時点で無い)。nullならnullと出て欲しいんだけどどうにかならないかな。。たしかNullable<int>やら自前の型なら<Name value=nil />みたいな文字列になったと思うんだけどなぁ。。記憶違いかな。。

参考(その他)

他の方法。XDocumentが使いやすそうだったので今回はパスしたけどメモ。

  1. XPathを使ってXMLファイルをパースする (XmlDocumentを利用) (C#プログラミング)
  2. XML をパースする(C++/CX, C#版) - Qiita

(C#) オブジェクトをXML文字列にする

概要

ログ出力のためにオブジェクトをXML文字列化することが多かったので、それ用のクラスをつくった。
ログを汚さないために1行で出力するバージョン付き。置換すれば元のXML文字列に戻せるので、いざというときは解析に使える。
中身は単なるXMLシリアライズなので渡すクラスにはSerializableAttributeが必要。

つくったクラス

using System;
using System.IO;
using System.Xml.Serialization;

namespace ObjectToXmlString
{
    public static class XmlStringHelper
    {
        // XML文字列化
        public static string ConvertToXmlStirng(object obj, bool throwException = false)
        {
            var xmlString = string.Empty;

            try
            {
                XmlSerializer serializer = new XmlSerializer(obj.GetType());
                using (StringWriter writer = new StringWriter())
                {
                    serializer.Serialize(writer, obj);
                    xmlString = writer.ToString();
                }
            }
            catch
            {
                if (throwException)
                {
                    throw;
                }
            }

            return xmlString;
        }

        // XML文字列化(1行)
        public static string ConvertToXmlStringAsOneLine(object obj, string newLineReplacingString, bool throwException = false)
        {
            var xmlString = string.Empty;

            try
            {
                xmlString = ConvertToXmlStirng(obj, throwException);
                xmlString = xmlString.Replace(Environment.NewLine, newLineReplacingString);
            }
            catch
            {
                if (throwException)
                {
                    throw;
                }
            }

            return xmlString;
        }
    }
}

おためしコード

using System;
using System.Collections.Generic;

namespace ObjectToXmlString
{
    [Serializable]
    public class Box
    {
        public int Volume { get; set; } = 10;
        public string Name { get; set; } = "box a";
        public List<string> Items { get; } = new List<string>() { "Apple", "Orange" };
    }

    class Program
    {
        static void Main(string[] args)
        {
            Box boxA = new Box();

            WriteResult("XML文字列",
                XmlStringHelper.ConvertToXmlStirng(boxA));
            WriteResult("XML文字列(1行)",
                XmlStringHelper.ConvertToXmlStringAsOneLine(boxA, "#"));


            Console.ReadKey();
        }

        static void WriteResult(string title, string result)
        {
            Console.WriteLine("----------------");
            Console.WriteLine($"{title}");
            Console.WriteLine("----------------");
            Console.WriteLine($"{result}");
            Console.WriteLine();
        }
    }
}

実行結果

----------------
XML文字列
----------------
<?xml version="1.0" encoding="utf-16"?>
<Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <Volume>10</Volume>
  <Name>box a</Name>
  <Items>
    <string>Apple</string>
    <string>Orange</string>
  </Items>
</Box>

----------------
XML文字列(1行)
----------------
<?xml version="1.0" encoding="utf-16"?>#<Box xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">#  <Volume>10</Volume>#  <Name>box a</Name>#  <Items>#    <string>Apple</string>#    <string>Orange</string>#  </Items>#</Box>

windows10のアクティビティ機能を無効化する

概要

Windows10でEdgeを使うと、Win+Tabで表示されるタスクビュー(?)に「アクティビティ」として表示されて邪魔。これを表示されなくする方法を忘れないようにメモしておく。アクティビティの削除だとかは参考[1]を参照のこと。

  1. 手順1: レジストリエディタを開く。[Win+R] -> 「regedit」
  2. 以下を開く。
コンピューター\HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\System
  1. 「EnableActivityFeed」キーを開く。無ければ追加(DWORD)。
  2. 値を「0」にする。

TortoiseSVNで作ったリポジトリのコミットログを編集可能にする

TortoiseSVNリポジトリを作ったものの、コミットログが編集できず地味に面倒だったのでメモ。

結果としてバッチファイルを1つおけばいいだけだった。
TortoiseSVNリポジトリを作るといくつかフォルダができる。
そのうち「hook」というフォルダに以下のバッチファイルを置けばOK。

set REPOS="%1"
set REV="%2"
set USER="%3"
set PROPNAME="%4"
set ACTION="%5"

if %ACTION%=="M" (
  if %PROPNAME%=="svn:log" (
    exit 0
  )
)

echo "Changing revision properties other than svn:log is prohibited" >&2
exit 1

エクスプローラの右クリックで新規作成されるファイルを変更する(Office 2013)

エクスプローラの右クリックメニュー「新規作成」をしたときに作成される「新規 Microsoft Excel ワークシート.xlsx」を自分の用意したExcelファイルに変更する方法。
Microsoft Officeのバージョンは Office 2013。


Office 2013

結論としては、以下のファイルを差し替えれば良い。
(注意:同じPCの全ユーザーに影響する…はず)

C:\Windows\SHELLNEW\EXCEL12.xlsx


Office 2016

Office 2016の場合は以下のパスのファイルを差し替えれば良い模様。
(出典:Excel 2016でデスクトップ右クリック及びExcelメニューの新規作成で作成される新規ファイルのカスタマイズ方法 | DevelopersIO

C:\Program Files (x86)\Microsoft Office\root\VFS\Windows\SHELLNEW

(2019/08/12追記)
2013と同じ方法でできた。上記の引用元のパス無いし。