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

目次

オブジェクトの「代入」と「複製」の違い

C#において、クラス(参照型)の変数を別の変数に代入した場合、コピーされるのはオブジェクトの実体ではなく「参照(メモリアドレス)」のみです。

var original = new MyClass();

// 変数 original が保持している「参照(アドレス)」が copy に代入される。
// これを「参照のコピー」または単に「代入」と呼びます。
var copy = original; 

この状態では、originalcopyはヒープ領域にある**同一のオブジェクト(実体)**を指しています。したがって、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側でリストに要素を追加すると...
clone.Roles.Add("Manager");

// Original側のリストにも "Manager" が追加されて見える
// (同じ List インスタンスを参照しているため)

参照型の内部データも含めて完全に独立した複製を作るには、「ディープコピー(Deep Copy)」と呼ばれる手法(シリアライズを経由する方法や、手動ですべての参照型プロパティをnewして再生成する方法)が必要になります。

まとめ

MemberwiseCloneメソッドは、オブジェクトの複製を作成する最も手軽な方法です。

注意: intstringDateTimeのみを持つクラスであれば問題ありませんが、List<T>などのコレクションや他のクラスをプロパティに持つ場合は、参照共有の問題が発生するため注意が必要です。

実装: クラス内でthis.MemberwiseClone()を呼び出し、自身の型にキャストして返します。

挙動: シャローコピー(浅いコピー)を作成します。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

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

目次