scrap book

( ..)φメモメモ

(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と同じ方法でできた。上記の引用元のパス無いし。

(C#) EntityFrameworkでは主キーのないテーブルを扱えない

EntityFrameworkでは主キーのないテーブルを扱えない問題とその対処メモ

対処方法

主キーさえ作ってあげれば良さそう。なので最悪適当なidを振れば良さそう。