はじめに
循環参照があり、複数のクラスが含まれる複雑な構造のデータをシリアライズしたいと思いました。
しかし、調べてみても単純なクラスが1個だけあるサンプルが殆どでした。
唯一見つかった循環参照を扱ったサンプルも、シリアライズするクラスは一つです。
こちらはDataContractSerializerを使用しています。
果たして、私のやりたいことはDataContractSerializerを使ってできるのでしょうか?
注意
.NET Frameworkです。.NET Coreではビルドできません。
循環参照をシリアライズするのにDataContractSerializerのコンストラクタでpreserveObjectReferences引数をtrueにする必要があるそうです。
実際、後述のサンプルでこれをfalseにすると例外が発生します。
コンストラクタの数が違う
.NET FrameworkだとDataContractSerializerのコンストラクタは13個あるのに、
.NET Coreだと7個で、preserveObjectReferences引数を持つものはありません。
試行
2つの異なるクラスが参照し合うパターン
問題なく動作しました。
Part, Moduleに付けているDataContract, KnownTypeは必須で、外すと例外が発生します。
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が無くても動きました。
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は何か問題があれば調べる)
終わりに
改善点やツッコミ処等があればご指摘お願いします。