- 2023/06/02 内容を修正
今回は、メンバ内の参照を保ったままディープコピーする方法について紹介します。
ディープコピーとは、オブジェクトの内容を別のオブジェクトに完全にコピーすることです。しかし、単純にディープコピーをすると、メンバ内の参照関係が失われてしまうことがあります。
例えば、次のようなクラスがあるとします。
public sealed class TestData
{
public void Initialize()
{
A = new Item("A");
B = new Item("B", A);
}
public Item A { get; private set; }
public Item B { get; private set; }
}
public sealed class Item
{
public Item(string name, Item child = null)
{
Name = name;
Child = child;
}
public string Name { get; set; }
public Item Child { get; }
}
このクラスのインスタンスを単純にディープコピーすると、A プロパティと B プロパティの Child プロパティが同じオブジェクトを参照しているはずですが、コピー後は別のオブジェクトを参照してしまいます。これは、ディープコピーの際に各メンバが個別にコピーされるためです。
DeepCopy.Expression によるコピー
先程のクラスのインスタンスを DeepCopy.Expression で複製してみます。
デフォルトでは参照関係は保持されない
以下の例のように、DeepCopy.Expression の デフォルトでは内部の参照関係は保たれません。
class Program
{
static void Main(string[] args)
{
var data = new TestData();
Console.WriteLine($"Source => Ref:{data.B.Child == data.A}");
var cloned= ObjectCloner.Clone(data);
Console.WriteLine($"Cloned => Ref:{cloned.B.Child == cloned.A}\r\n");
data.A.Name = "Original-A";
Console.WriteLine("Set \"Orignal-A\" to the name of A in the source instance.");
Console.WriteLine($"Source => A:\"{data.A.Name}\" , B.Child:\"{data.B.Child.Name}\"");
Console.WriteLine($"Cloned => A:\"{cloned.A.Name}\" , B.Child:\"{cloned.B.Child.Name}\"\r\n");
cloned.A.Name = "Cloned-A";
Console.WriteLine("Set \"Cloned-A\" to the name of A in the cloned instance.");
Console.WriteLine($"Source => A:\"{data.A.Name}\" , B.Child:\"{data.B.Child.Name}\"");
Console.WriteLine($"Cloned => A:\"{cloned.A.Name}\" , B.Child:\"{cloned.B.Child.Name}\"");
}
}
Source => Ref:True Cloned => Ref:False Set "Orignal-A" to the name of A in the source instance. Source => A:"Original-A" , B.Child:"Original-A" Cloned => A:"A" , B.Child:"A" Set "Cloned-A" to the name of A in the cloned instance. Source => A:"Original-A" , B.Child:"Original-A" Cloned => A:"Cloned-A" , B.Child:"A"
複製されたインスタンスでは参照関係がなくなっている為、コピーされたオブジェクトの A プロパティの名前を変更しても、B プロパティの Child の名前は元のままです。
参照を保持したままコピーするには
メンバ内の参照を保ったままディープコピーするには、ObjectCloner.Clone メソッドの第二引数に true を指定するか、 CopyTo メソッドの第三引数に true を指定すれば出来るようになります。この引数は、内部の参照関係を保持するかどうかを示す値で、デフォルトは false になっています。
例えば次のように実行します。
class Program
{
static void Main(string[] args)
{
var data = new TestData();
Console.WriteLine($"Source => Ref:{data.B.Child == data.A}");
var cloned= ObjectCloner.Clone(data, true);
Console.WriteLine($"Cloned => Ref:{cloned.B.Child == cloned.A}\r\n");
data.A.Name = "Original-A";
Console.WriteLine("Set \"Orignal-A\" to the name of A in the source instance.");
Console.WriteLine($"Source => A:\"{data.A.Name}\" , B.Child:\"{data.B.Child.Name}\"");
Console.WriteLine($"Cloned => A:\"{cloned.A.Name}\" , B.Child:\"{cloned.B.Child.Name}\"\r\n");
cloned.A.Name = "Cloned-A";
Console.WriteLine("Set \"Cloned-A\" to the name of A in the cloned instance.");
Console.WriteLine($"Source => A:\"{data.A.Name}\" , B.Child:\"{data.B.Child.Name}\"");
Console.WriteLine($"Cloned => A:\"{cloned.A.Name}\" , B.Child:\"{cloned.B.Child.Name}\"");
}
}
Source => Ref:True Cloned => Ref:True Set "Orignal-A" to the name of A in the source instance. Source => A:"Original-A" , B.Child:"Original-A" Cloned => A:"A" , B.Child:"A" Set "Cloned-A" to the name of A in the cloned instance. Source => A:"Original-A" , B.Child:"Original-A" Cloned => A:"Cloned-A" , B.Child:"Cloned-A"
これで、ディープコピー後のオブジェクトもコピー前と同じメンバ内の参照関係を持つようになります。
例えば、data.A.Name を変更すると、 data.B.Child.Name も変更されますが、cloned.A.Name や cloned.B.Child.Name は変更されません。逆に、cloned.A.Name を変更すると、cloned.B.Child.Name も変更されますが、data.A.Name や data.B.Child.Name は変更されません。
まとめ
DeepCopy.Expression は、シリアライズを使う方法よりも高速で柔軟なディープコピーができるライブラリです。メンバ内の参照関係を保持したままディープコピーしたい場合はぜひ試してみてください。