C# DeepCopyする方法

こんにちは!トミセンです。

オブジェクトを複製する場合に必要なDeepCopyですが、

「C#でDeepCopyどうやるの?」
「C#って、Javaと書き方が違うの?」

分かってしまえば簡単なんですが、簡単なことほど情報を見つけるのに苦労しがちで、思ったより時間がかかってしまいますよね。

DeepCopyする方法は様々あって、どれを選べばいいのか迷ってしまいがちです。
また、簡単に出来そうなサンプルでも、上手くいかなかったりして思ったより手間取ってしまうこともありますよね。

今回はそんなお悩みを解決します。
ずばり!「C# DeepCopyする方法」についてご紹介します。

それでは一緒に学んでいきましょう!

この記事がおすすめな人
  • DeepCopyをC#で使いたい人
  • シャローコピーとディープコピーの違いが知りたい人
目次

シャローコピーとディープコピーって?

プログラミングではオブジェクトをコピーする処理は頻繁に使われます。

まずは、コピーの種類について確認しておきます。

シャローコピー(ShallowCopy)

シャローコピーは、最も基本的なコピーです。代入操作やコピーメソッドなどはシャローコピーになります。

値型の場合は問題ありませんが、参照型の場合には注意が必要です。

参照型のシャローコピーは、コピー元とコピー先が同じオブジェクトになるからです。

次のソースコードを見てください。

using System;

public class Test
{
    public void Main()
    {
        // 値型
        int src = 10;
        int dest = src;

        dest = 20;

        Console.WriteLine($"src :{src}");
        Console.WriteLine($"dest:{dest}");

        if (Object.ReferenceEquals(src, dest))
        {
            Console.WriteLine("srcとdestは同じオブジェクト");
        }
        else
        {
            Console.WriteLine("srcとdestは違うオブジェクト");
        }
		
        // 【実行結果】      
        // src :10
        // dest:20
        // srcとdestは違うオブジェクト


        // 参照型
        var srcMember = new Member { Name = "鈴木", Address = "東京都" };
        var destMember = srcMember;

        destMember.Address = "千葉県";

        Console.WriteLine($"srcMember  Name:{srcMember.Name}, Address:{srcMember.Address}");
        Console.WriteLine($"destMember Name:{destMember.Name}, Address:{destMember.Address}");

        if (Object.ReferenceEquals(srcMember, destMember))
        {
            Console.WriteLine("srcMemberとdestMemberは同じオブジェクト");
        }
        else
        {
            Console.WriteLine("srcMemberとdestMemberは違うオブジェクト");
        }
		
        // 【実行結果】      
        // srcMember  Name:鈴木, Address:千葉県
        // destMember Name:鈴木, Address:千葉県
        // srcMemberとdestMemberは同じオブジェクト
    }
}

/// <summary>
/// データクラス
/// </summary>
public class Member
{
    public string Name { get; set; }

    public string Address { get; set; }
}

値型・参照型共にsrcに値をセットし、srcをdestにコピーします。その後にdestの値を変更しています。

値型はdestの変更がsrcに影響しませんが、参照型はdestの変更がsrcに影響してしまいます。

まとめておきます。

シャローコピーの特徴

  • 値型:違うオブジェクト
  • 参照型:同じオブジェクト

ディープコピー(DeepCopy)

同じオブジェクトでは困るというときにディープコピーする必要があります。

C#にはディープコピーができる標準のメソッドはありません。独自でメソッドを作る必要があります。

DeepCopyを実装しました。次のソースを見てください。

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

public class Test2
{
    public void Main()
    {
        // 参照型
        var srcMember = new Member { Name = "鈴木", Address = "東京都" };
        var destMember = srcMember.DeepCopy(); // ← .DeepCopy()でコピー

        destMember.Address = "千葉県";

        Console.WriteLine($"srcMember  Name:{srcMember.Name}, Address:{srcMember.Address}");
        Console.WriteLine($"destMember Name:{destMember.Name}, Address:{destMember.Address}");

        if (Object.ReferenceEquals(srcMember, destMember))
        {
            Console.WriteLine("srcMemberとdestMemberは同じオブジェクト");
        }
        else
        {
            Console.WriteLine("srcMemberとdestMemberは違うオブジェクト");
        }
		
        // 【実行結果】
        // srcMember  Name:鈴木, Address:東京都
        // destMember Name:鈴木, Address:千葉県
        // srcMemberとdestMemberは違うオブジェクト
		
		
        // List<Member>
        var srcMembers = new List<Member> { new Member { Name = "鈴木", Address = "東京都" }, new Member { Name = "佐藤", Address = "埼玉" } };
        var destMembers = srcMembers.DeepCopy(); // ← .DeepCopy()でコピー

        destMembers[1].Address = "千葉県";

        srcMembers.ForEach(x => Console.WriteLine($"srcMembers  Name:{x.Name}, Address:{x.Address}"));
        destMembers.ForEach(x => Console.WriteLine($"destMembers  Name:{x.Name}, Address:{x.Address}"));

        if (Object.ReferenceEquals(srcMembers, destMembers))
        {
            Console.WriteLine("srcMembersとdestMembersは同じオブジェクト");
        }
        else
        {
            Console.WriteLine("srcMembersとdestMembersは違うオブジェクト");
        }
		
        // 【実行結果】
        // srcMembers  Name:鈴木, Address:東京都
        // srcMembers  Name:佐藤, Address:埼玉
        // destMembers Name:鈴木, Address:東京都
        // destMembers Name:佐藤, Address:千葉県
        // srcMembersとdestMembersは違うオブジェクト
    }
}

/// <summary>
/// データクラス
/// </summary>
[Serializable] // ← データクラスに「Serializable」属性を設定
public class Member
{
    public string Name { get; set; }

    public string Address { get; set; }
}

/// <summary>
/// CopyHelper
/// </summary>
public static class CopyHelper
{
    /// <summary>
    /// DeepCopy
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="src"></param>
    /// <returns></returns>
    public static T DeepCopy<T>(this T src)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, src);
            stream.Position = 0;

            return (T)formatter.Deserialize(stream);
        }
    }
}

destの変更がsrcに影響していないのが分かると思います。

ソースコードだけでは分かりにくい部分があるので、いくつかのポイントをコードを見ながら解説していきます!

まず、DeepCopyのメソッドからです。

// DeepCopyメソッド
public static T DeepCopy<T>(this T src)
{
    using (MemoryStream stream = new MemoryStream())
    {
        var formatter = new BinaryFormatter();
        formatter.Serialize(stream, src);
        stream.Position = 0;

        return (T)formatter.Deserialize(stream);
    }
}

ジェネリックを使った拡張メソッドにしています。

型を特定してないので、どの型でも使用できます。

次に、使い方です。

あわせて読みたい
【C#】拡張メソッド まとめ 拡張メソッドは、非常に便利です。 既存のクラスへのメソッドを追加して、インスタンスメソッドのように呼び出しができます。 元々クラスに存在したメソッドのように使...

 

// .DeepCopy()でコピー
var destMember = srcMember.DeepCopy(); 

コピーは、先程作った拡張メソッドを使います。変数.DeepCopy()と書くことでコピーすることができます。

最後にデータクラスにも変更が必要になります。

// データクラスに「Serializable」属性を設定
[Serializable] 
public class Member

データクラスにSerializable属性を設定してください。

念のため、リスト型でもテストをしてみます。

// List<Member>
var srcMembers = new List<Member> { new Member { Name = "鈴木", Address = "東京都" }, new Member { Name = "佐藤", Address = "埼玉" } };
var destMembers = srcMembers.DeepCopy(); // ← .DeepCopy()でコピー

destMembers[1].Address = "千葉県";

srcMembers.ForEach(x => Console.WriteLine($"srcMembers  Name:{x.Name}, Address:{x.Address}"));
destMembers.ForEach(x => Console.WriteLine($"destMembers  Name:{x.Name}, Address:{x.Address}"));

if (Object.ReferenceEquals(srcMembers, destMembers))
{
    Console.WriteLine("srcMembersとdestMembersは同じオブジェクト");
}
else
{
    Console.WriteLine("srcMembersとdestMembersは違うオブジェクト");
}

// 【実行結果】
// srcMembers  Name:鈴木, Address:東京都
// srcMembers  Name:佐藤, Address:埼玉
// destMembers Name:鈴木, Address:東京都
// destMembers Name:佐藤, Address:千葉県
// srcMembersとdestMembersは違うオブジェクト

List型でも問題なく使用できていますね。

これで、「C#でDeepCopy」ができました!

まとめ

DeepCopyは、画面で変更前と変更後を比較する場合などに、多様しますよね。

C# オブジェクトを値で比較する方法が知りたい方は、こちらの記事「C# オブジェクトを値で比較する方法」をどうぞ。

それでは、また。

あわせて読みたい
C# オブジェクトを値で比較する方法 こんにちは!トミセンです。 オブジェクトを比較するにはSystem.Object.ReferenceEquals()やSystem.Object.Equals()が使われますが、 「C#でオブジェクトを値で比較する...

こちらの記事も読まれています!


よかったらシェアしてね!
目次
閉じる