オブジェクトの「参照渡し」と「複製」の違い
C#において、クラス(参照型)の変数を別の変数に代入した場合、コピーされるのはオブジェクトの実体ではなく「参照(メモリアドレス)」のみです。
var original = new MyClass();
var copy = original; // 参照のみがコピーされる
// copy を変更すると、original も変更されたように見える(同じ実体を見ているため)
インスタンスの内容(プロパティの値など)をそっくりそのままコピーして、独立した別のインスタンスを作成したい場合、明示的な「複製(クローン)」処理が必要になります。
C#の全クラスの基底であるSystem.Objectクラスには、簡易的な複製を行うためのMemberwiseCloneメソッドが用意されています。
MemberwiseClone メソッド
MemberwiseCloneメソッドは、オブジェクトの**シャローコピー(Shallow Copy / 浅いコピー)**を作成して返します。
このメソッドはprotectedで定義されているため、外部から直接product.MemberwiseClone()のように呼び出すことはできません。クラス内部で、このメソッドを呼び出すラッパーメソッド(通常はCloneという名前など)を実装する必要があります。
コード例:シャローコピーの実装
社員情報を管理するEmployeeDataクラスを例に、複製機能を追加します。
using System;
public class CloningExample
{
public static void Main()
{
// 1. 元のオブジェクトを作成
var original = new EmployeeData
{
Id = 101,
FullName = "Alice Smith",
JoinDate = new DateTime(2023, 4, 1)
};
// 2. オブジェクトを複製 (シャローコピー)
var clone = original.Clone();
// 3. 複製されたデータの確認
Console.WriteLine("--- 複製直後 ---");
Console.WriteLine($"Original: {original}");
Console.WriteLine($"Clone : {clone}");
// 4. 複製先のデータを変更
// (独立したインスタンスであるため、Originalには影響しない)
clone.FullName = "Alice Jones"; // 結婚して姓が変わったと仮定
clone.Id = 999;
Console.WriteLine("\n--- Clone変更後 ---");
Console.WriteLine($"Original: {original}");
Console.WriteLine($"Clone : {clone}");
}
}
/// <summary>
/// 社員データを表すクラス
/// </summary>
public class EmployeeData
{
public int Id { get; set; }
public string FullName { get; set; } = "";
public DateTime JoinDate { get; set; }
/// <summary>
/// 自身のシャローコピー(浅いコピー)を作成して返す
/// </summary>
public EmployeeData Clone()
{
// MemberwiseClone は object 型を返すため、キャストが必要
return (EmployeeData)this.MemberwiseClone();
}
public override string ToString()
{
return $"ID={Id}, Name={FullName}, Date={JoinDate:yyyy/MM/dd}";
}
}
出力結果:
--- 複製直後 ---
Original: ID=101, Name=Alice Smith, Date=2023/04/01
Clone : ID=101, Name=Alice Smith, Date=2023/04/01
--- Clone変更後 ---
Original: ID=101, Name=Alice Smith, Date=2023/04/01
Clone : ID=999, Name=Alice Jones, Date=2023/04/01
複製先(Clone)のプロパティを変更しても、複製元(Original)には影響がないことが確認できます。
重要な注意点:シャローコピー(浅いコピー)とは
MemberwiseCloneが行う「シャローコピー」には、重要な特性があります。それは、参照型のフィールドは「参照のコピー」しか行わないという点です。
- 値型(int, double, DateTime, structなど): 値そのものがコピーされます。複製先で変更しても複製元には影響しません。
- 参照型(List, 配列, 自作クラスなど): 「参照(アドレス)」だけがコピーされます。つまり、複製元と複製先が同じオブジェクトの実体を共有してしまいます。 (例外:
string型は参照型ですが、不変(Immutable)であるため、実質的に値型のように安全に扱えます)
シャローコピーの問題点(参照型の共有)
もし上記のEmployeeDataクラスに List<string> Roles (役割リスト)というプロパティがあった場合、MemberwiseCloneで複製すると、Rolesリストの実体は共有されます。
複製先で clone.Roles.Add("Manager") を実行すると、複製元の original.Roles にも “Manager” が追加されてしまいます。
参照型の内部データも含めて完全に独立した複製を作るには、「ディープコピー(Deep Copy)」と呼ばれる手法(シリアライズを経由する方法や、手動ですべてのプロパティを再生成する方法)が必要になります。
まとめ
MemberwiseCloneメソッドは、オブジェクトの複製を作成する最も手軽な方法です。
- 実装: クラス内で
this.MemberwiseClone()を呼び出し、自身の型にキャストして返します。 - 挙動: シャローコピー(浅いコピー)を作成します。
- 注意:
intやstringのみを持つクラスであれば問題ありませんが、List<T>などのコレクションや他のクラスをプロパティに持つ場合は、参照共有の問題が発生するため注意が必要です。
