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

2019年9月15日

配列(参照型)のコピー

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

サンプル

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);
            }
        }
    }
}

2019年9月15日C#,ツール,便利機能

Posted by hidepon