参照型変数(主に、自分で作成したクラスのオブジェクト)をコピーする場合、単純に「=」で代入するだけでは不十分な場合があります。
参照型変数の中身は参照先(オブジェクトのメモリ領域を示すポインタ)です。
「=」で代入するだけでは、参照先だけがコピーされて、参照しているものは同じという状態になるので、コピー先の変更がコピー元に影響してしまいますし、その逆にコピー元の変更がコピー先に影響してしまいます。
これを避けたい場合は、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