23708


アプリケーションの設定をXAMLで書き出そうと、以下のようなコードを書きました。

[code]

    using System.Windows.Markup;
    using System.Xaml;
    
    public class Item
    {
    }
    
    [ContentProperty("Items")]
    public class Settings
    {
        public List<Item> Items { get; set; } = new List<Item>();
    }
    
    void Main()
    {
        var settings = new Settings();
        settings.Items.Add(new Item());
        settings.Items.Add(new Item());
        Console.WriteLine(XamlServices.Save(settings));
    }
    
[/code]

上のコードでは `<Root>` の直下に `<Item>` が並ぶことを期待していたのですが、出力してみると間に `<scg:List ...>`
という要素が入ってしまいます。

[code]

    <Settings xmlns="clr-namespace:;assembly=query_qufjxk" xmlns:scg="clr-namespace:System.Collections.Generic;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
      <scg:List x:TypeArguments="Item" Capacity="4">
        <Item />
        <Item />
      </scg:List>
    </Settings>
    
[/code]

色々試した結果、`List<T>` ではなく `Collection<T>` を使えば、期待通りになることがわかりました。

[code]

    <Settings xmlns="clr-namespace:;assembly=query_ujgmdq">
      <Item />
      <Item />
    </Settings>
    
[/code]

個人的には後者の方がWPFなどで馴染みのある形なので、もしかするとXAMLでは `List<T>` より `Collection<T>`
（あるいは別の型）を使う慣習があるのではと思った次第です。気にせず `List<T>` を使えばいいのでしょうか？

独自のコレクション型を実装するのであれば `List<T>` より `Collection<T>`
を継承するだろうとは思うのですが、今回はそこまでする予定はないので `List<T>` で格納するつもりでいました。


CC BY-SA 3.0
2016-04-03T15:11:35.410
23706
2016-04-04T02:21:27.593
2016-04-04T01:56:02.147
8000
8000
question
c# xaml
XAMLにシリアライズするプロパティにはList<T>よりもCollection<T>を使うべき？


慣習というよりは`List<T>`には読み取り専用ではない`Capacity`プロパティがあり、これをシリアライズするためにコレクション要素が省略されないということだと思います。

# XAML出力時の動作について追記

まず`XamlServices.Save`の実装について説明しますが、内部的には`XamlReader`や`XamlWriter`の派生クラスを使用して以下のように処理されています。

[code]

    // XamlObjectReader reader;
    // XamlXmlWriter writer;
    while (reader.Read())
        writer.WriteNode(reader);
    
[/code]

つまりXAMLの入出力は

  * `XamlReader`でインスタンスやXMLからXAMLオブジェクトグラフを読み取る
  * `XamlReader`に設定されているXAMLオブジェクトのノードを1個ずつ`XamlWriter`に追記する
  * `XamlWriter`はファイルやオブジェクトに書き込まれた結果を出力する

という仕組みになっています。

ですので、`XamlObjectReader`を使用すれば問題のインスタンスがどう認識されているか理解することができます。例えば以下のようなコードです。

[code]

    using (var xor = new XamlObjectReader(settings))
    {
        var indent = 0;
        while (xor.Read())
        {
            switch (xor.NodeType)
            {
                case XamlNodeType.EndMember:
                case XamlNodeType.EndObject:
                    indent -= 4;
                    break;
            }
    
            Console.Write(new string(' ', indent));
            Console.WriteLine($"{xor.NodeType} {xor.Type} {xor.Member}");
    
            switch (xor.NodeType)
            {
                case XamlNodeType.GetObject:
                case XamlNodeType.StartMember:
                case XamlNodeType.StartObject:
                    indent += 4;
                    break;
            }
        }
    }
    
[/code]

これを実行するとプロパティが`Collection<T>`になっている場合は以下のような`NodeType`が出力されます。問題の`Settings.Items`に対しては`GetObject`が指定されていることが分かります。

[code]

    NamespaceDeclaration
    StartObject {clr-namespace:…}Settings
        StartMember  {clr-namespace:…}Settings.Items
            GetObject
                StartMember  {http://schemas.microsoft.com/winfx/2006/xaml}_Items
                    StartObject {clr-namespace:…}Item
                    EndObject
                    StartObject {clr-namespace:…}Item
                    EndObject
                EndMember
            EndObject
        EndMember
    EndObject
    
[/code]

一方、`List<T>`を使用した場合や`Collection<T>`派生型に適当なプロパティを増やした場合、`GetObject`の部分は以下のように`StartObject`に変わり、プロパティの代入が指示されます。

[code]

    StartObject {clr-namespace:System.Collections.Generic;assembly=mscorlib}List({clr-namespace:…}Item)
    
[/code]

このように`XamlServices.Save`による出力結果の違いは内部で使われている`XamlReader`の動作に由来しています。ドキュメントには`XamlObjectReader`派生型とだけあり、具体的にどのような仕様かは明示されていませんが、`TypeConverter`はプロパティをXML属性として出力するかどうかだけに使用されているように思われます。


CC BY-SA 3.0
2016-04-03T16:03:50.647
23708
2016-04-04T02:21:27.593
2020-06-17T08:14:45.997
-1
5750
23706
answer


[こちらの記事](https://stackoverflow.com/questions/31891979/serialize-a-listt-
content-property-to-xaml-without-it-being-shown-explicitly-i)を参考に修正したところ:

[code]

        public List<Item> Items { get; private set; } = new List<Item>();
    
[/code]

ご所望の出力が得られるようです。  
「publicなsetterがある場合には、データ型に定義されたconverterの動作を優先する」と言う状態に見えるのですが、確定的に記述したドキュメントは見つかりませんでした。  
…と思ったのですが、「派生クラスでCapacityを隠蔽」した時の動作から考えるとちょっと違うようですね。


CC BY-SA 3.0
2016-04-03T22:41:35.067
23713
2016-04-04T02:04:39.610
2017-05-23T12:38:56.467
-1
13972
23706
answer