こんにちは!トミセンです。
オブジェクトを複製する場合に必要な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に影響してしまいます。
まとめておきます。
シャローコピーの特徴
- 値型:違うオブジェクト
- 参照型:同じオブジェクト
src・destの意味に興味がある方は、こちらの「src・dest・dstの意味とは?」の記事をどうぞ。
ディープコピー(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);
}
}
ジェネリックを使った拡張メソッドにしています。
型を特定してないので、どの型でも使用できます。
次に、使い方です。
// .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# オブジェクトを値で比較する方法」をどうぞ。
それでは、また。
こちらの記事も読まれています!
- 未経験でプログラマーになったけど辞めたい!どうするべき?
- ITエンジニアはスキルが不安なままアラフォーになるとヤバイ!
- IT業界に強いエンジニアのための転職サイト・転職エージェント3選
- プログラミングの独学は挫折率9割。たった1つの理由と解決方法(姉妹サイト)