参照型のディープコピー(詳細コピー)

2022年9月8日

配列(参照型)のコピー

メモリの同じところを指しているので、コピーしても同じところが変更されます。

サンプル

int[] array1 = { 1, 2, 3 };
int[] array2;
array2 = array1;
array2[1] = 10;
for (int i = 0; i < array1.Length; i++)
{
    Console.WriteLine(array1[i]);
}
Console.WriteLine();
for (int i = 0; i < array2.Length; i++)
{
    Console.WriteLine(array2[i]);
}

結果

1
10
3
 
1
10
3

影響を受けないコピー(その1)

サンプル

int[] array1 = { 1, 2, 3 };
int[] array2 = new int[array1.Length];
for (int i = 0; i < array1.Length; i++)
{
    array2[i] = array1[i];
}
array2[1] = 10;
for (int i = 0; i < array1.Length; i++)
{
    Console.WriteLine(array1[i]);
}
Console.WriteLine();
for (int i = 0; i < array2.Length; i++)
{
    Console.WriteLine(array2[i]);
}

結果

1
2
3
 
1
10
3

影響を受けないコピー(その2)

サンプル

int[] array1 = { 1, 2, 3 };
int[] array2 = new int[array1.Length];
Array.Copy(array1, array2, array1.Length);
array2[1] = 10;
for (int i = 0; i < array1.Length; i++)
{
    Console.WriteLine(array1[i]);
}
Console.WriteLine();
for (int i = 0; i < array2.Length; i++)
{
    Console.WriteLine(array2[i]);
}

結果

1
10
3
 
1
10
3

Unity ゲームに適用してみると・・・

ObjectExtension.cs

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public static class ObjectExtension
{
    // ディープコピーの複製を作る拡張メソッド
    public static T DeepClone<T>(this T src)
    {
        using (var memoryStream = new MemoryStream())
        {
            // オブジェクト全体を、バイナリ形式でシリアル化および逆シリアル化します。
            var binaryFormatter = new BinaryFormatter();
            // シリアライズ
            binaryFormatter.Serialize(memoryStream, src);
            memoryStream.Seek(0, SeekOrigin.Begin);
            // デシリアライズ
            return (T)binaryFormatter.Deserialize(memoryStream);
        }
    }
}

Player.cs

using System;
[Serializable]
public class Player
{
    public int Hp { get; set; }
    public string Name { get; set; }
}

Sample.cs

using UnityEngine;
class Sample : MonoBehaviour
{
    void Start()
    {
        Player player1 = new Player
        {
            Hp = 100,
            Name = "Player1"
        };
        var player2 = player1.DeepClone();
        player2.Name = "player2";
        Debug.Log(player1.Hp + " " + player1.Name);
        Debug.Log(player2.Hp + " " + player2.Name);
    }
}

元の考え方

using System;
using System.Linq;
using UnityEngine;
// 配列に格納するクラス
[Serializable]
public class Player
{
    public int No { get; set; }
    public string Name { get; set; }
    public override string ToString() => $"{No:00}:{Name}";
}
class Program : MonoBehaviour
{
    void Start()
    {
        Player[] src =
        {
          new Player {No=1, Name="aaa" },
          new Player {No=2, Name="bbb" },
          new Player {No=3, Name="ccc" },
        };
        // ディープコピー
        var clone = src.DeepClone();
        Debug.Log($"複製先:{string.Join(", ", clone.Select(s => s.ToString()))}");
        // 出力:
        // 複製先:01:aaa, 02:bbb, 03:ccc
        // 複製元のオブジェクトに変更を加える
        src[1].Name = "ZZZ";
        Debug.Log($"複製元:{string.Join(", ", src.Select(s => s.ToString()))}");
        // 出力:
        // 複製元:01:aaa, 02:ZZZ, 03:ccc
        // しかし、複製先は変わっていない
        Debug.Log($"複製先:{string.Join(", ", clone.Select(s => s.ToString()))}");
        // 出力:
        // 複製先:01:aaa, 02:bbb, 03:ccc
    }
}

UnityEngineをシミュレートした場合

Object.cs

using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace UnityEngine
{
    // シリアル化したいクラスに必要なアトリビュート
    [Serializable]
    public class Object
    {
        // オブジェクトの名前
        public string name;
        // ディープコピー(深いコピー) インスタンス自体をコピーすること
        public static T Instantiate<T>(T original) where T : Object
        {
            using (var memoryStream = new MemoryStream())
            {
                var binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStream, original);
                memoryStream.Seek(0, SeekOrigin.Begin);
                return (T)binaryFormatter.Deserialize(memoryStream);
            }
        }
    }
}

参考

C#,ツール,便利機能

Posted by hidepon