C#でDeepCopyを実装する

# 1. はじめに
* クラスをコピーする時に値ごとコピー（DeepCopy）したい
* Javaで使用していたlombokのようにtoStringを自動生成したい
* ネストしたクラス、リストについても対応したい

# 2.  開発環境
* Visual Studio 2022
* .NET6
* ToString.Fody(NuGet）

# 3. コンソールアプリを作成
## 3.1. Visual Studioで新規プロジェクトを作成する
* コンソールアプリ（C#）を選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/2b1fc943-f664-7d4b-3b60-217914712d03.png)

* プロジェクト名は「DeepCopyHelper」で作成する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/d5a754b9-6501-9786-e77e-e03e1133ed2a.png)

* フレームワークは「.NET6」を選択する
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/a1ad3ca1-1a74-9c0e-bb9d-0aa3d928b9e9.png)

* 新規プロジェクトの作成が完了
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/da865a0a-6970-3a31-e52a-164572da1cce.png)

## 3.2. DeepCopyHelperクラスを作成する
* 「新しいフォルダー」 を追加し、「Helpers」フォルダを作る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/150ea23f-d056-03eb-697d-8b7d0a18f311.png)

* Helpersクラスの中に新しい項目を追加で、「クラス」 を追加する
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/04754b91-df3c-019d-dc5d-829d13ce79af.png)

```csharp:DeepCopyHelper.cs
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace DeepCopyHelper.Helpers
{
    public static class DeepCopyHelper
    {
        /// <summary>
        /// DeepCopy
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="param"></param>
        /// <returns></returns>
        public static T DeepCopy<T>(this T param)
        {
            using (MemoryStream memoryStream = new MemoryStream())
            {
                var binaryFormatter = new BinaryFormatter();
                binaryFormatter.Serialize(memoryStream, param);
                memoryStream.Position = 0;

                return (T)binaryFormatter.Deserialize(memoryStream);
            }
        }
    }
}
```

# 4. DeepCopyを検証プログラム作成
## 4.1. ToString.Fodyをインストールする
* NuGetの管理から「ToString.Fody」をインストールする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/6ac0ce01-007e-7448-55a8-f49c9e3a15f4.png)

## 4.2. DTOクラスを作成する
* 「新しいフォルダー」 を追加し、「Dtos」フォルダを作る
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2852081/5756ea8e-caa7-86af-ca36-cce91c14712a.png)

* DTosクラスの中に新しい項目を追加で、「クラス」 を追加する

```csharp:BaseDto.cs
using System;

namespace DeepCopyHelper.Dtos
{
    [Serializable]
    public abstract class BaseDto<T> where T : BaseDto<T>
    {
        public override bool Equals(object obj)
        {
            var t = obj as T;
            if (t == null)
            {
                return false;
            }

            return EqualsCore(t);
        }

        public static bool operator ==(BaseDto<T> obj1, BaseDto<T> obj2)
        {
            return Equals(obj1, obj2);
        }

        public static bool operator !=(BaseDto<T> obj1, BaseDto<T> obj2)
        {
            return !Equals(obj1, obj2);
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        protected abstract bool EqualsCore(T other);
    }
}
```

```csharp:PrpdictDto.cs
using System;
using System.Collections.Generic;
using System.Linq;

namespace DeepCopyHelper.Dtos
{
    [Serializable]
    [ToString]
    public class ProductDto : BaseDto<ProductDto>
    {
        public int ProductId { set; get; }  // 商品ID
        public string ProductName { set; get; } // 商品名
        public List<string> ProductNote { set; get; } // 商品ノート
        public Product2Dto product2Dto { set; get; }    // Product2Dto

        protected override bool EqualsCore(ProductDto other)
        {
            // 商品IDの比較
            if (ProductId != other.ProductId)
            {
                return false;
            }

            // 商品名の比較
            if (ProductName != other.ProductName)
            {
                return false;
            }

            // 商品ノートの比較
            if (!ProductNote.SequenceEqual(other.ProductNote))
            {
                return false;
            }

            // Product2Dtoの比較
            if (product2Dto != other.product2Dto)
            {
                return false;
            }

            return true;
        }
    }
}
```

```csharp:Product2Dto.cs
using System;

namespace DeepCopyHelper.Dtos
{
    [Serializable]
    [ToString]
    public class Product2Dto : BaseDto<Product2Dto>
    {
        public int ProductSummary { set; get; } // 商品合計

        protected override bool EqualsCore(Product2Dto other)
        {
            // 商品合計
            if (ProductSummary != other.ProductSummary)
            {
                return false;
            }

            return true;
        }
    }
}
```

## 4.3. メインプログラムを修正する

```csharp:Program.cs
using DeepCopyHelper.Dtos;
using DeepCopyHelper.Helpers;
using System.Diagnostics;

// ----------------------------------------
// コピー元のDtoクラスを作成
// ----------------------------------------
var productDto = new ProductDto();
// 商品ID
productDto.ProductId = 1;

// 商品名
productDto.ProductName = "ProductName1";

// 商品ノート
List<string> list = new List<string>();
list.Add("Note1");
list.Add("Note2");
list.Add("Note3");
productDto.ProductNote = list;

// Product2Dto
var product2Dto = new Product2Dto();
product2Dto.ProductSummary = 100;
productDto.product2Dto = product2Dto;
// ----------------------------------------

// DeepCopyを実行
var productDtoCopy = productDto.DeepCopy<ProductDto>();

// コピー先の値を一部修正
// productDtoCopy.ProductNote.Remove("Note1");
// productDtoCopy.product2Dto.ProductSummary = 200;

// コピー元、コピー先の値を出力
Debug.WriteLine(productDto.ToString());
Debug.WriteLine(productDtoCopy.ToString());

// コピー元、コピー先の比較
Debug.WriteLine(productDto == productDtoCopy);
```

## 4.4. 検証プログラムを実行する
* コピー先、コピー元の値がコピーされ、想定通りオブジェクトの比較も同じになった
```csharp
{T: "ProductDto", ProductId: 1, ProductName: "ProductName1", ProductNote: ["Note1", "Note2", "Note3"], product2Dto: {T: "Product2Dto", ProductSummary: 100}}
{T: "ProductDto", ProductId: 1, ProductName: "ProductName1", ProductNote: ["Note1", "Note2", "Note3"], product2Dto: {T: "Product2Dto", ProductSummary: 100}}
True
```

# 5. ソースコード
https://github.com/YoshijiGates/CSharp/tree/main/DeepCopyHelper

# 6. 参考文献
https://tomisenblog.com/c-sharp-deepcopy/