ホームに戻る
出典 :
シャローコピーとディープコピー(C#) - 超初心者向けプログラミング入門 C#:オブジェクトの中身をコピーする方法(MemberwiseCloneメソッド実装) – サイゼントの技術ブログ オブジェクトや配列などの複製を作るには?(ディープコピー編)[C#/VB]:.NET TIPS - @IT 配列の複製を作るには?(シャローコピー編)[C#/VB]:.NET TIPS - @IT
関連 :
参照渡し
目次 :

C#におけるオブジェクトの代入

C#のクラスは参照型であり、クラスオブジェクトの「代入」を行った場合代入先には代入元への参照が渡される
このため、代入後に一方のメンバを書き換えた場合、他方にも影響が及ぶ
これはメソッド(関数)の引数としてクラスオブジェクトを渡した場合も同様である。
構造体(struct)や、組み込み型(intなど)は値型であり、上記には当てはまらない。
文字列(string = System.String)は参照型であるが、代入演算子がオーバーロードされているため値型と同様に扱われる。
// クラス cMyClass 定義 class cMyClass { public int valA; public string valB; } class cOtherClass { public void DoSomething() { // cMyClass 型オブジェクト ob1 cMyClass ob1 = new cMyClass(); ob1.valA = 4; ob1.valB = "ouch"; // cMyClass 型オブジェクト ob2 に ob1 を代入 // ⇒ 0b2 は 0b1 と共通のインスタンスを指す cMyClass ob2 = ob1; // ob1 、ob2 の内容を出力 Console.WriteLine("編集前の値"); Console.WriteLine("ob1.valA = " + ob1.valA); Console.WriteLine("ob1.valB = " + ob1.valB); Console.WriteLine("ob2.valA = " + ob2.valA); Console.WriteLine("ob2.valB = " + ob2.valB); // ob2 のメンバを編集 ob2.valB = "oops"; // ob1 、ob2 の内容を出力 Console.WriteLine("編集後の値"); Console.WriteLine("ob1.valA = " + ob1.valA); Console.WriteLine("ob1.valB = " + ob1.valB); Console.WriteLine("ob2.valA = " + ob2.valA); Console.WriteLine("ob2.valB = " + ob2.valB); } }
実行結果 :
編集前の値 ob1.valA = 4 ob1.valB = ouch ob2.valA = 4 ob2.valB = ouch 編集後の値 ob1.valA = 4 ob1.valB = oops //< 直接編集していない ob1.valB が書き換えられている ob2.valA = 4 ob2.valB = oops //< 直接編集した ob2.valB

シャローコピーとディープコピー

前節のように単純にオブジェクト(参照)の代入を行った場合、変数が指すインスタンスは共通となる。
このような参照のみをコピーし、実体を複製しないコピーを「シャロー(浅い)コピー」と呼ぶ。
逆に、実体を複製するコピーを「ディープ(深い)コピー」と呼ぶ。

MemberwiseClone() メソッドを使用したコピー

C#の Object 型には、コピーを行うための MemberwiseClone() メソッドが定義されている。
(すべての型は Object 型から派生するため、ユーザ定義のすべての型で MemberwiseClone() を用いることができる。)
戻り値は Object 型のため、元の型として用いるにはキャストが必要。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace CloneTest { // クローン対象のオブジェクト class CloneableItem { // クラスメンバ public int ItemId { get; set; } public string ItemName { get; set; } // コピーを行うための Clone() メソッドを定義 public CloneableItem Clone() { // MemberwiseCloneメソッドを使用 // Object型で返ってくるのでキャストが必要 return (CloneableItem)MemberwiseClone(); } } // クローンのテスト class ItemCloneMain { static void Main(string[] args) { // コピー元オブジェクト生成、値設定 CloneableItem ob3 = new CloneableItem(); ob3.ItemId = 3; ob3.ItemName = "rifle"; // Clone() メソッドの実行 CloneableItem ob4 = ob3.Clone(); // 編集前の状態を出力 Console.WriteLine("編集前の値"); Console.WriteLine("コピー元 : " + ob3.ItemId + " / " + ob3.ItemName); Console.WriteLine("コピー先 : " + ob4.ItemId + " / " + ob4.ItemName); // 値を編集 ob3.ItemName = "cannon"; // 編集後の状態を出力 Console.WriteLine("編集後の値"); Console.WriteLine("コピー元 : " + ob3.ItemId + " / " + ob3.ItemName); Console.WriteLine("コピー先 : " + ob4.ItemId + " / " + ob4.ItemName); } } }
実行結果 :
編集前の値 コピー元 : 3 / rifle コピー先 : 3 / rifle 編集後の値 コピー元 : 3 / cannon // コピー元を編集しても コピー先 : 3 / rifle // コピー先はそのまま
MemberwiseClone() は代入と異なり、別のインスタンスを生成するため、値型(およびstring)のメンバを編集しても他方に影響が及ぶことは無い。
しかし参照型のメンバはシャローコピーであるため、完全なディープコピーを作成する場合はメンバごとの処置が必要となる。

シリアライズ・デシリアライズを使用したディープコピー

MemoryStream と BinaryFormatter を用いることで、オブジェクトのシリアライズ、デシリアライズを行うことができる。
元のオブジェクトをシリアライズ(と同時に複製)し、デシリアライズで復元すればディープコピーとなる。
但し、この方法が使用できるのはシリアライズが可能なオブジェクトに限定される(詳細要調査)。
class Sample { : // ディープコピー取得用メソッド public Sample GetCopy() { // シリアライズ用 MemoryStream 、BinaryFormatter var ms = new MemoryStream(); var bf = new BinaryFormatter(); // シリアライズ bf.Serialize(ms, this); ms.Seek(0, SeekOrigin.Begin); // デシリアライズ ⇒ 複製を返す // ( Deserialize() の戻り値は Object 型のためキャストが必要) return (Sample)bf.Deserialize(ms); } : }

その他の手法

C++と同様、コピーコンストラクタを定義するなどの方法でディープコピーを実現できるが、ここでは割愛する。