DeepCopy.Expression – メンバ内の参照関係を保持したまま複製する

  • 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 は、シリアライズを使う方法よりも高速で柔軟なディープコピーができるライブラリです。メンバ内の参照関係を保持したままディープコピーしたい場合はぜひ試してみてください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

先頭に戻る