複雑な構造を持つデータのシリアライズ

## はじめに

循環参照があり、複数のクラスが含まれる複雑な構造のデータをシリアライズしたいと思いました。
しかし、調べてみても単純なクラスが１個だけあるサンプルが殆どでした。

唯一見つかった循環参照を扱ったサンプルも、シリアライズするクラスは一つです。
こちらはDataContractSerializerを使用しています。

https://stackoverflow.com/questions/736568/serialization-derialization-of-a-tree-structure

果たして、私のやりたいことはDataContractSerializerを使ってできるのでしょうか？

## 注意

.NET Frameworkです。.NET Coreではビルドできません。

循環参照をシリアライズするのにDataContractSerializerのコンストラクタでpreserveObjectReferences引数をtrueにする必要があるそうです。

実際、後述のサンプルでこれをfalseにすると例外が発生します。
![循環参照で例外.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317334/c091ad23-3120-98ee-9f98-070824ecf66f.png)

#### コンストラクタの数が違う

.NET FrameworkだとDataContractSerializerのコンストラクタは13個あるのに、
![DataContractSerializer_Framework.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317334/2f28b0b5-d90e-fcf2-7e7c-dc0f4667844d.png)

.NET Coreだと7個で、preserveObjectReferences引数を持つものはありません。
![DataContractSerializer_Core.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317334/9f113a38-5ea3-3578-c2e0-28b97cae2812.png)

## 試行
#### 2つの異なるクラスが参照し合うパターン

問題なく動作しました。
Part, Moduleに付けているDataContract, KnownTypeは必須で、外すと例外が発生します。

```c#
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;

namespace DataContractSerializerSample1
{
    [DataContract]
    [KnownType(typeof(Module))]
    class Part
    {
        [DataMember]
        public Module Owner;

		[DataMember]
        public Part AnotherPart { get; set; }

		public Part(Module owner)
		{
			Owner = owner;
		}
    }

    [DataContract]
    [KnownType(typeof(Part))]
    class Module
    {
        [DataMember]
        public List<Part> Parts { get; private set; } = new List<Part>();
    }

    static class Program
    {
        static void Main()
        {
            Module module = new Module();
            Part a = new Part(module);
            Part b = new Part(module);

            // 循環参照あり
            a.AnotherPart = b;
            b.AnotherPart = a;
            module.Parts.Add(a);
            module.Parts.Add(b);

            DataContractSerializer dcs = new DataContractSerializer(
                typeof(Product), null, int.MaxValue, false, true, null);
            using (MemoryStream ms = new MemoryStream())
            {
                // シリアライズ
                dcs.WriteObject(ms, module);

                // デシリアライズ
                ms.Position = 0;
                Module c = (Module)dcs.ReadObject(ms);
                Console.WriteLine(ReferenceEquals(c.Parts[0], c.Parts[1].AnotherPart));
                Console.WriteLine(ReferenceEquals(c.Parts[1], c.Parts[0].AnotherPart));
                Console.WriteLine(ReferenceEquals(c.Parts[0].Owner, c.Parts[1].Owner));
                Console.WriteLine(ReferenceEquals(c, c.Parts[0].Owner));
            }
        }
    }
}
```
#### 継承を含むパターン

これも問題なく動作しました。
しかし、Part, Moduleに付けているKnownTypeを外しても例外が発生しませんでした。（？）
また、Productに継承元のModuleについてのKnownTypeが無くても動きました。

```c#
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;

namespace DataContractSerializerSample2
{
    [DataContract]
    [KnownType(typeof(Module))]
    class Part
    {
        [DataMember]
        public Module Owner;

		[DataMember]
        public Part AnotherPart { get; set; }

		public Part(Module owner)
		{
			Owner = owner;
		}
    }

    [DataContract]
    [KnownType(typeof(Part))]
    class Module
    {
        [DataMember]
        public List<Part> Parts { get; private set; } = new List<Part>();
    }

    [DataContract]
    class Product : Module
    {
        [DataMember]
        public string SN;
    }

    static class Program
    {
        static void Main()
        {
            Product product = new Product();
            product.SN = "SN0123456789";
            Part a = new Part(product);
            Part b = new Part(product);

            // 循環参照あり
            a.AnotherPart = b;
            b.AnotherPart = a;
            product.Parts.Add(a);
            product.Parts.Add(b);

            DataContractSerializer dcs = new DataContractSerializer(
                typeof(Product), null, int.MaxValue, false, true, null);
            using (MemoryStream ms = new MemoryStream())
            {
                // シリアライズ
                dcs.WriteObject(ms, product);

                // デシリアライズ
                ms.Position = 0;
                Product c = (Product)dcs.ReadObject(ms);
                Console.WriteLine(ReferenceEquals(c.Parts[0], c.Parts[1].AnotherPart));
                Console.WriteLine(ReferenceEquals(c.Parts[1], c.Parts[0].AnotherPart));
                Console.WriteLine(ReferenceEquals(c.Parts[0].Owner, c.Parts[1].Owner));
                Console.WriteLine(ReferenceEquals(c, c.Parts[0].Owner));
                Console.WriteLine(c.SN);
            }
        }
    }
}
```

## 結論
不明点はありますが、現時点でやりたいことは出来そうです。

## まとめ
* シリアライズするクラス全てにDataContract付けるべし
* .NET Coreでは不可
* 循環参照可
* 継承しててもOK
* (KnownTypeは何か問題があれば調べる)

## 終わりに
改善点やツッコミ処等があればご指摘お願いします。