【C#】オブジェクトの複製:MemberwiseCloneメソッドによるシャローコピー(浅いコピー)の実装

目次

オブジェクトの「参照渡し」と「複製」の違い

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()を呼び出し、自身の型にキャストして返します。
  • 挙動: シャローコピー(浅いコピー)を作成します。
  • 注意: intstringのみを持つクラスであれば問題ありませんが、List<T>などのコレクションや他のクラスをプロパティに持つ場合は、参照共有の問題が発生するため注意が必要です。
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次