深いコピー(ディープコピー)

2022年2月5日

参照型のコピーは、アドレスのコピーです
単純に、インスタンス変数を代入すると、アドレスがコピーされるだけでメモリは共有されることになります。
今回は、全く別のメモリエリアにコピーしたいので、メソッドを定義して実現します

シリアライズを利用した深いコピー

深いコピーを実現するメソッド

Jsonシリアライザーを使って、インスタンスを一旦文字列に変換します。
その文字列をデシリアライズして、新しくインスタンスを作成して戻します

static MyClassA DeepCopy(MyClassA src)
{
    return JsonSerializer.Deserialize<MyClassA>(JsonSerializer.Serialize(src));
}

メソッドの使い方

MyClassA型のインスタンス(myClassSource)を同じくMyClassA型のインスタンス(myClassDist)にコピーします。
全く違うメモリエリアにコピーされるため、相互に影響を及ぼしません
全てのコードについては、次のテストサンプルを参照してください

var myClassSource = new MyClassA();
var myClassDist = DeepCopy(myClassSource);

シンプルなサンプル

上記のMyClassAクラスは、参照型のため、通常の浅いコピーをすると参照先アドレスの値がコピーされます。
単純にインスタンス変数を代入するとメモリエリアが共有されることになり同じメモリエリアを共有することになります。今回は、全く違うメモリエリアに双方影響を受けないコピーとしたいので、内容を全てコピーする深いコピーを実現します

閉じられたクラスでのテスト

インスタンスをシリアライズ化するため、コピーされるクラスは、Serializable属性をつけています

メソッドと使い方

using System;
using System.Collections.Generic;
using System.Text.Json;

namespace ExtentionTest2
{
    static class Program
    {
        static void Main(string[] args)
        {
            var myClassSource = new MyClassA();

            var myClassDist = DeepCopy(myClassSource);

            myClassSource.hp = 200;

            Console.WriteLine(myClassSource.hp);
            Console.WriteLine(myClassDist.hp);

        }

        static MyClassA DeepCopy(MyClassA src)
        {
            return JsonSerializer.Deserialize<MyClassA>(JsonSerializer.Serialize(src));
        }
    }

    [Serializable]
    public class MyClassA
    {
        public int hp = 10;
    }
}

結果サンプル

200
10

応用

上記サンプルでは、深いコピーは、MyClassAクラスに限定されます。
ここでは、汎用性を持たせるため、MyClassA以外のクラスでも使えるようにします

クラスを限定しないメソッドの書き方

クラスをTとし、ジェネリックを使い実現します

コード

MyClassAだけに限定されません。三勝型であれば使うことができます

static T DeepCopy<T>(T src) where T : class
{
    return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(src));
}

使い方

DeepCopy(インスタンス変数名)メソッドとして呼び出せます

var myClassSource = new MyClassA();
var myClassDist = DeepCopy(myClassSource);

使用できるクラスも限定させない

拡張メソッドにして、インスタンスが持っているメソッドのような構成にします

コード

static class Extention
{
    public static T DeepCopy<T>(this T src) where T : class
    {
        return JsonSerializer.Deserialize<T>(JsonSerializer.Serialize(src));
    }
}

使い方

(インスタンス変数名).DeepCopy()メソッドとして呼び出せます

var myClassSource = new MyClassA();
var myClassDist = myClassSource.DeepCopy();

エラーチェックも追加した使い方

コード

namespace Extention
{
    public static class ClassExtension
    {

        /// <summary>
        /// シリアライズ可能なクラスをディープコピーする。
        /// </summary>
        /// <typeparam name="T">コピー対象のクラス</typeparam>
        /// <param name="src">コピーするインスタンス</param>
        /// srcのディープコピー。
        /// クラスにSerializable属性が付いていなければnull。
        public static T DeepCopy<T>(this T src) where T : class
        {
            //ディープコピー不可ならnullを返す
            if (!HasAttribute<T, System.SerializableAttribute>())
                return null;

            T result;

            try
            {
                var jsonText = System.Text.Json.JsonSerializer.Serialize(src);
                result = System.Text.Json.JsonSerializer.Deserialize<T>(jsonText);

            }
            catch (System.Exception ex)
            {
                throw new System.Exception(ex.Message);
            }

            return result;
        }

        /// <summary>
        /// クラスに指定の属性が存在するかをチェックする。
        /// </summary>
        /// <typeparam name="T">チェック対象のクラス</typeparam>
        /// <typeparam name="Attr">チェックする属性</typeparam>
        /// <returns>Attr属性の有無。</returns>
        public static bool HasAttribute<T, Attr>()
            where T : class
            where Attr : System.Attribute
        {
            //Serializable属性が付いていなければfalseを返す
            return System.Attribute.GetCustomAttribute(
                    typeof(T), typeof(Attr)
                ) != null;
        }
    }
}

使い方

テストとして、クラスのメンバーが参照型でそのクラスのメンバーもさらに参照型という入り組んだ構成を試します

using System;
using System.Collections.Generic;
using Extention2;

namespace ExtentionTest2
{
    static class Program
    {
        static void Main(string[] args)
        {
            var myClassSource = new MyClassA();

            var myClassDist = myClassSource.DeepCopy();

            myClassSource.myClassBList[0].myClassCList[0].score = 200;

            Console.WriteLine(myClassSource.myClassBList[0].myClassCList[0].score);
            Console.WriteLine(myClassDist.myClassBList[0].myClassCList[0].score);

        }
    }

    [Serializable]
    public class MyClassA
    {
        public List<MyClassB> myClassBList = new()
        {
            new MyClassB(new List<MyClassC>() { new MyClassC(3) }),
            new MyClassB(new List<MyClassC>() { new MyClassC(4) }),
        };
    }

    [Serializable]
    public class MyClassB
    {
        public List<MyClassC> myClassCList = new();

        public MyClassB(List<MyClassC> myClassCList)
        {
            this.myClassCList = myClassCList;
        }
    }

    [Serializable]
    public class MyClassC
    {
        public int score;

        public MyClassC(int score)
        {
            this.score = score;
        }
    }
}

結果サンプル

200
3

C#

Posted by hidepon