本日は C# の小ネタ枠です。
C# で DeepCopy を利用する方法です。
シャロウコピーとディープコピー
変数のコピーには、シャロウコピーとディープコピーの2種類のコピーがあります。
C# で等価演算子を使って変数をコピーした場合、参照型の変数はシャロウコピーが行われ、実体はコピーされずに参照の設定のみがコピーされます
コピー先の変数とコピー元の変数で同じ実体を参照することになるため、どちらかの変数の実体を操作すると双方に影響があります。
例えば以下のような処理でシャロウコピーが行われます。
// 参照型の変数を作成する Member member01 = new Member { Name = "alpha", Address = "01" }; // 変数をシャロウコピーする var shallowCopy = member01; // コピー先の変数から実体を操作する shallowCopy.Address = "02"; // コピー元とコピー先の両方が影響を受ける Debug.Log($"member01 : Name = {member01.Name}, Address = {member01.Address} ;"); Debug.Log($"shallowCopy : Name = {shallowCopy.Name}, Address = {shallowCopy.Address} ;");
一方で実体もコピーして別々の変数として扱えるのがディープコピーです。
C# ではディープコピーの標準関数がないため、ディープコピーを行いたい場合は関数を自作する必要があります。
後述の参考サイトを基に、以下のディープコピー用関数を作成しました。
・CopyHelper.cs
using System.IO; using System.Runtime.Serialization.Formatters.Binary; /// <summary> /// コピー用汎用関数 /// </summary> public static class CopyHelper { /// <summary> /// 対象のディープコピーを行う /// シリアライズ(Serializable 属性)されていないクラスではエラー /// </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); } } }
以下のように利用してディープコピーを実行できます。
// 参照型の変数を作成する Member member01 = new Member { Name = "alpha", Address = "01" }; // 自作した DeepCopy 関数を呼び出して変数をディープコピーする var deepCopy = member01.DeepCopy(); // コピー先の変数で実体を操作する deepCopy.Address = "02"; // コピー元の実体はコピー先と異なるので影響を受けない Debug.Log($"member01 : Name = {member01.Name}, Address = {member01.Address} ;"); Debug.Log($"deepCopy : Name = {deepCopy.Name}, Address = {deepCopy.Address} ;");
ディープコピーを行う際の注意点
ディープコピーを行う対象はシリアライズ可能なクラスである必要があります。
自作クラスをディープコピーする場合は、クラスに Serializable 属性を設定します。
・Member.cs
using System; /// <summary> /// メンバークラス /// </summary> [Serializable] // DeepCopy対象では Serializable 属性が必須 public class Member { public string Name { get; set; } public string Address { get; set; } }
Unity での注意点
MonoBehaviour を継承したコンポーネントはシリアライズできないため、DeepCopy を実行すると SerializationException が発生します。
// MonoBehaviour を継承したコンポーネントを取得する MemberMonoBehaviour member01 = this.GetComponent<MemberMonoBehaviour>(); // 自作した DeepCopy 関数を呼び出して変数をディープコピーする // MonoBehaviour を継承したコンポーネントの場合、ここで SerializationException エラーが発生する var deepCopy = member01.DeepCopy(); // コピー先の変数で実体を操作する deepCopy.Address = "02"; Debug.Log($"member01 : Name = {member01.Name}, Address = {member01.Address} ;"); Debug.Log($"deepCopy : Name = {deepCopy.Name}, Address = {deepCopy.Address} ;");
・MemberMonoBehaviour.cs
using System; using UnityEngine; [Serializable] public class MemberMonoBehaviour : MonoBehaviour { public string Name { get; set; } public string Address { get; set; } }
その他のディープコピー
Listの場合
Listに対するディープコピーを試しました。
Member member01 = new Member { Name = "alpha", Address = "01" }; Member member02 = new Member { Name = "beta", Address = "11" }; List<Member> members = new List<Member> { member01, member02 }; var deepCopy = members.DeepCopy(); deepCopy[0].Address = "02"; foreach (var member in members) { Debug.Log($"member : Name = {member.Name}, Address = {member.Address} ;"); } foreach (var member in deepCopy) { Debug.Log($"deepCopy : Name = {member.Name}, Address = {member.Address} ;"); }
自作クラスを含む場合
自作クラスを含む自作クラスに対するディープコピーを試しました。
Member member01 = new Member { Name = "alpha", Address = "01" }; Member member02 = new Member { Name = "beta", Address = "11" }; List<Member> members = new List<Member> { member01, member02 }; Teams teams = new Teams { TeamName = "abc", Members = members }; var deepCopy = teams.DeepCopy(); deepCopy.TeamName = "123"; deepCopy.Members[1].Address = "12"; Debug.Log($"teams : TeamName = {teams.TeamName} ;"); foreach (var member in teams.Members) { Debug.Log($"teams : Name = {member.Name}, Address = {member.Address} ;"); } Debug.Log($"teams : TeamName = {deepCopy.TeamName} ;"); foreach (var member in deepCopy.Members) { Debug.Log($"deepCopy : Name = {member.Name}, Address = {member.Address} ;"); }
・Teams.cs
using System; using System.Collections.Generic; /// <summary> /// チームクラス /// </summary> [Serializable] // DeepCopy対象では Serializable 属性が必須 public class Teams { public string TeamName; public List<Member> Members; }