技術とか戦略とか

IT技術者が技術や戦略について書くブログです。

C#:オブジェクトの中身をコピーする方法(MemberwiseCloneメソッド実装)

参照型変数(主に、自分で作成したクラスのオブジェクト)をコピーする場合、単純に「=」で代入するだけでは不十分な場合があります。
参照型変数の中身は参照先(オブジェクトのメモリ領域を示すポインタ)です。
「=」で代入するだけでは、参照先だけがコピーされて、参照しているものは同じという状態になるので、コピー先の変更がコピー元に影響してしまいますし、その逆にコピー元の変更がコピー先に影響してしまいます。
 
これを避けたい場合は、MemberwiseCloneメソッドを用いて中身を丸ごとコピー(新たにメモリ領域を確保し書き込み、ディープコピー)する必要があります。
MemberwiseCloneメソッドはobject型で定義されており、C#ではすべての型はobject型から派生しているので、特別な記述を行わなくともMemberwiseCloneメソッドを使用できます。ただし、戻り値はobject型なので、キャスト等の考慮は必要です。
javaの場合はこちらの記事(https://cyzennt.co.jp/blog/2020/01/24/java%ef%bc%9a%e3%82%aa%e3%83%96%e3%82%b8%e3%82%a7%e3%82%af%e3%83%88%e3%81%ae%e4%b8%ad%e8%ba%ab%e3%82%92%e3%82%b3%e3%83%94%e3%83%bc%e3%81%99%e3%82%8b%e6%96%b9%e6%b3%95%ef%bc%88clone%e3%83%a1%e3%82%bd/)のようにインターフェースの実装が必要だったり例外処理が必要だったりと色々面倒です。後発言語であるC#では言語仕様上ディープコピーを始めから考慮している印象を受けます。)
 
以下、サンプルコードです。
参照先のみコピーした場合とMemberwiseCloneメソッドで中身をコピーした場合を比較しています。
参照先のみコピーした場合は、コピー後にコピー先を変更した際にコピー元が影響を受けていますが、MemberwiseCloneメソッドで中身をコピーした場合は影響を受けていません。
 
【サンプルコード】
・CloneableItem.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloneTest
{
    class CloneableItem
    {
        // ミュータブルの参照型変数を含む場合はそれも一緒にcloneが必要
        // javaの時は書き忘れたがシリアライズ・デシリアライズでも良い
        // (今回は割愛)
        public int ItemId { get; set; }
        public string ItemName { get; set; }

        // MemberwiseCloneメソッドを使用
        public CloneableItem Clone()
        {
            // Object型で返ってくるのでキャストが必要
            return (CloneableItem)MemberwiseClone(); ;
        }
    }
}
 
・ItemCloneMain.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CloneTest
{
    class ItemCloneMain
    {
        static void Main(string[] args)
        {
            Console.WriteLine("[参照先のみコピーした場合]");
            CloneableItem cloneableItem1 = new CloneableItem();
            cloneableItem1.ItemId = 1;
            cloneableItem1.ItemName = "sword";
            Console.WriteLine
                ("コピー前のコピー元オブジェクト:ID=" +
                 cloneableItem1.ItemId +
                 " NAME=" +
                 cloneableItem1.ItemName);
            CloneableItem cloneableItem2 = cloneableItem1;
            Console.WriteLine
                ("コピー後のコピー元オブジェクト:ID=" +
                 cloneableItem1.ItemId +
                 " NAME=" +
                 cloneableItem1.ItemName);
            Console.WriteLine
                ("コピー後のコピー先オブジェクト:ID=" +
                 cloneableItem2.ItemId +
                 " NAME=" +
                 cloneableItem2.ItemName);
            cloneableItem2.ItemId = 2;
            cloneableItem2.ItemName = "shield";
            Console.WriteLine
                ("編集後のコピー元オブジェクト :ID=" +
                 cloneableItem1.ItemId +
                 " NAME=" +
                 cloneableItem1.ItemName);
            Console.WriteLine
                ("編集後のコピー先オブジェクト :ID=" +
                 cloneableItem2.ItemId +
                 " NAME=" +
                 cloneableItem2.ItemName);

            Console.WriteLine("[参照先のみコピーした場合]");
            CloneableItem cloneableItem3 = new CloneableItem();
            cloneableItem3.ItemId = 3;
            cloneableItem3.ItemName = "armor";
            Console.WriteLine
                ("コピー前のコピー元オブジェクト:ID=" +
                 cloneableItem3.ItemId +
                 " NAME=" +
                 cloneableItem3.ItemName);
            CloneableItem cloneableItem4 = cloneableItem3.Clone();
            Console.WriteLine
                ("コピー後のコピー元オブジェクト:ID=" +
                 cloneableItem3.ItemId +
                 " NAME=" +
                 cloneableItem3.ItemName);
            Console.WriteLine
                ("コピー後のコピー先オブジェクト:ID=" +
                 cloneableItem4.ItemId +
                 " NAME=" +
                 cloneableItem4.ItemName);
            cloneableItem4.ItemId = 4;
            cloneableItem4.ItemName = "helm";
            Console.WriteLine
                ("編集後のコピー元オブジェクト :ID=" +
                 cloneableItem3.ItemId +
                 " NAME=" +
                 cloneableItem3.ItemName);
            Console.WriteLine
                ("編集後のコピー先オブジェクト :ID=" +
                 cloneableItem4.ItemId +
                 " NAME=" +
                 cloneableItem4.ItemName);

            Console.ReadKey(true);
        }
    }
}
 
【実行結果】
[参照先のみコピーした場合]
コピー前のコピー元オブジェクト:ID=1 NAME=sword
コピー後のコピー元オブジェクト:ID=1 NAME=sword
コピー後のコピー先オブジェクト:ID=1 NAME=sword
編集後のコピー元オブジェクト :ID=2 NAME=shield
編集後のコピー先オブジェクト :ID=2 NAME=shield
[参照先のみコピーした場合]
コピー前のコピー元オブジェクト:ID=3 NAME=armor
コピー後のコピー元オブジェクト:ID=3 NAME=armor
コピー後のコピー先オブジェクト:ID=3 NAME=armor
編集後のコピー元オブジェクト :ID=3 NAME=armor
編集後のコピー先オブジェクト :ID=4 NAME=helm