Qiita Advent Calendar 初参加です。よろしくお願いします。
最近学習していて面白いな、と感じたオブジェクトの複製についてまとめました。
オブジェクトの複製パターン
オブジェクトの複製には2つのパターンがあります。
-
シャローコピー:簡易コピー
参照のみをコピーし、実体を複製しない。
複製するオブジェクトが値型か参照型かによって、コピー元(先)の変更がコピー先(元)に適用されるかどうかが異なる。
・値型は値がそのまま複製される→ 変更が適用されない
・参照型は参照先のアドレスが複製される→ 変更が適用される
※ただし string型 は変更のたびに別のインスタンスが生成されるため、変更が適用されない
文字列 -C# プログラミング ガイド 文字列の不変性 -
ディープコピー:詳細コピー
実体を複製する。
コピー元(元)を変更しても、コピー先(先)に変更が適用されない。
代入・コピーコンストラクタ・MemberwiseCloneメソッド・シリアライズ...etc など様々な方法で上記のコピーを実装することが可能です。今回はシャローコピーを代入・コピーコンストラクタ・MemberwiseCloneメソッド、ディープコピーをMemberwiseCloneメソッドで実装しました。また、コピー後に値を変更し、コピー元(またはコピー先)に影響があるかどうかを検証しました。
※今回の記事では値型と参照型をメソッド内でコピーするコードがありますが、引数を渡していないため「参照渡し」は関係ありません。
組み込み型メンバを持つ構造体をシャローコピーで複製する
組み込み型をメンバーとして SampleStruct
構造体を作成します。
public struct SampleStruct
{
public int Id { get; set; }
public string Name { get; set; }
public int[] Ids { get; set; }
public string[] Names { get; set; }
// 初期化
public SampleStruct(int id, string name, int[] ids, string[] names)
{
Id = id;
Name = name;
Ids = ids;
Names = names;
}
// コピーコンストラクタ
public SampleStruct(SampleStruct sampleStruct)
{
Id = sampleStruct.Id;
Name = sampleStruct.Name;
Ids = sampleStruct.Ids;
Names = sampleStruct.Names;
}
/// <summary>
/// シャローコピーを実行します。
/// </summary>
/// <returns><see cref="SampleClass2" />が戻ります。</returns>
public SampleStruct SharrowCopy()
{
return (SampleStruct)MemberwiseClone();
}
}
1. 代入
上記で作成した SampleStruct
を「代入」で複製します。
public static void ShallowCopy()
{
var original = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
var copy = original; // 代入
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 10 | 変更が適用されない |
Name | Hello, World! | AAA | 変更が適用されない |
Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
Names | あ, B, C | あ, B, C | 変更が適用された |
2. コピーコンストラクタ
上記で作成した SampleStruct
を「コピーコンストラクタ」で複製します。
public static void ShallowCopy2()
{
var original = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
var copy = new SampleStruct(original); // コピーコンストラクタ
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 10 | 変更が適用されない |
Name | Hello, World! | AAA | 変更が適用されない |
Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
Names | あ, B, C | あ, B, C | 変更が適用された |
3. MemberwiseCloneメソッド
上記で作成した SampleStruct
を「MemberwiseCloneメソッド」で複製します。
public static void ShallowCopy3()
{
var original = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
var copy = original.SharrowCopy(); // MemberwiseCloneメソッドを呼び出す
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 10 | 変更が適用されない |
Name | Hello, World! | AAA | 変更が適用されない |
Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
Names | あ, B, C | あ, B, C | 変更が適用された |
1, 2, 3の実行結果
値型 Id
と string型 Name
はコピー元のみ変更が適用されますが、参照型 Ids
と Names
は両方に変更が適用されます。
構造体は値型のため、複製すると別のインスタンスが生成されます。
組み込み型メンバを持つクラスをシャローコピーで複製する
組み込み型をメンバーとして SampleClass
クラスを作成します。
public class SampleClass
{
public int Id { get; set; }
public string Name { get; set; }
public int[] Ids { get; set; }
public string[] Names { get; set; }
// 初期化
public SampleClass(int id, string name, int[] ids, string[] names)
{
Id = id;
Name = name;
Ids = ids;
Names = names;
}
// コピーコンストラクタ
public SampleClass(SampleClass sampleClass)
{
Id = sampleClass.Id;
Name = sampleClass.Name;
Ids = sampleClass.Ids;
Names = sampleClass.Names;
}
/// <summary>
/// シャローコピーを実行します。
/// </summary>
/// <returns><see cref="SampleClass" />が戻ります。</returns>
public SampleClass SharrowCopy()
{
return (SampleClass)MemberwiseClone();
}
}
1. 代入
上記で作成した SampleClass
を「代入」で複製します。
public static void ShallowCopy()
{
var original = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var copy = original; // 代入
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 99 | 変更が適用された |
Name | Hello, World! | Hello, World! | 変更が適用された |
Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
Names | あ, B, C | あ, B, C | 変更が適用された |
1の実行結果
全ての型に変更が適用されます。
クラスは参照型のため、代入すると参照アドレスが渡され同一インスタンスを参照します。
2. コピーコンストラクタ
上記で作成した SampleClass
を「コピーコンストラクタ」で複製します。
public static void ShallowCopy2()
{
var original = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var copy = new SampleClass(original); // コピーコンストラクタ
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 10 | 変更が適用されない |
Name | Hello, World! | AAA | 変更が適用されない |
Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
Names | あ, B, C | あ, B, C | 変更が適用された |
3. MemberwiseCloneメソッド
上記で作成した SampleClass
を「MemberwiseCloneメソッド」で複製します。
public static void ShallowCopy3()
{
var original = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var copy = original.SharrowCopy(); // MemberwiseCloneメソッドを呼び出す
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 10 | 変更が適用されない |
Name | Hello, World! | AAA | 変更が適用されない |
Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
Names | あ, B, C | あ, B, C | 変更が適用された |
2, 3の実行結果
値型 Id
と string型 Name
はコピー元のみ変更が適用されますが、参照型 Ids
と Names
は両方に変更が適用されます。
コピーコンストラクタ・MemberwiseCloneメソッドで複製すると別のインスタンスが生成されます。
クラスと構造体をメンバを持つ構造体をシャローコピーで複製する
先程使用した SampleStruct
・SampleClass
をメンバーとして SampleStruct2
を作成します。
public struct SampleStruct2
{
public SampleStruct SampleStruct;
public SampleClass SampleClass;
// 初期化
public SampleStruct2(SampleStruct sampleStruct, SampleClass sampleClass)
{
SampleStruct = sampleStruct;
SampleClass = sampleClass;
}
// コピーコンストラクタ
public SampleStruct2(SampleStruct2 sampleStruct2)
{
SampleStruct = sampleStruct2.SampleStruct;
SampleClass = sampleStruct2.SampleClass;
}
/// <summary>
/// シャローコピーを実行します。
/// </summary>
/// <returns><see cref="SampleStruct2" />が戻ります。</returns>
public SampleStruct2 SharrowCopy()
{
return (SampleStruct2)MemberwiseClone();
}
}
1. 代入
上記で作成した SampleStruct2
を「代入」で複製します。
public static void ShallowCopy()
{
// 構造体
var sample1 = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
// クラス
var sample2 = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var original = new SampleStruct2(sample1, sample2);
var copy = original; // 代入
// 構造体の値を変更
original.SampleStruct.Id = 99;
original.SampleStruct.Name = "Hello, " + "World!";
original.SampleStruct.Ids[0] = 9;
original.SampleStruct.Names[0] = "あ";
// クラスの値を変更
original.SampleClass.Id = 99;
original.SampleClass.Name = "Hello, " + "World!";
original.SampleClass.Ids[0] = 9;
original.SampleClass.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
SampleStruct.Id | 99 | 10 | 変更が適用されない |
SampleStruct.Name | Hello, World! | AAA | 変更が適用されない |
SampleStruct.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleStruct.Names | あ, B, C | あ, B, C | 変更が適用された |
SampleClass.Id | 99 | 99 | 変更が適用された |
SampleClass.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleClass.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleClass.Names | あ, B, C | あ, B, C | 変更が適用された |
2. コピーコンストラクタ
上記で作成した SampleStruct2
を「コピーコンストラクタ」で複製します。
public static void ShallowCopy2()
{
// 構造体
var sample1 = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
// クラス
var sample2 = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var original = new SampleStruct2(sample1, sample2);
var copy = new SampleStruct2(original); // コピーコンストラクタ
// 構造体の値を変更
original.SampleStruct.Id = 99;
original.SampleStruct.Name = "Hello, " + "World!";
original.SampleStruct.Ids[0] = 9;
original.SampleStruct.Names[0] = "あ";
// クラスの値を変更
original.SampleClass.Id = 99;
original.SampleClass.Name = "Hello, " + "World!";
original.SampleClass.Ids[0] = 9;
original.SampleClass.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
SampleStruct.Id | 99 | 10 | 変更が適用されない |
SampleStruct.Name | Hello, World! | AAA | 変更が適用されない |
SampleStruct.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleStruct.Names | あ, B, C | あ, B, C | 変更が適用された |
SampleClass.Id | 99 | 99 | 変更が適用された |
SampleClass.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleClass.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleClass.Names | あ, B, C | あ, B, C | 変更が適用された |
3. MemberwiseCloneメソッド
上記で作成した SampleStruct2
を「MemberwiseCloneメソッド」で複製します。
public static void ShallowCopy3()
{
// 構造体
var sample1 = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
// クラス
var sample2 = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var original = new SampleStruct2(sample1, sample2);
var copy = original.SharrowCopy(); // MemberwiseCloneメソッドを呼び出す
// 構造体の値を変更
original.SampleStruct.Id = 99;
original.SampleStruct.Name = "Hello, " + "World!";
original.SampleStruct.Ids[0] = 9;
original.SampleStruct.Names[0] = "あ";
// クラスの値を変更
original.SampleClass.Id = 99;
original.SampleClass.Name = "Hello, " + "World!";
original.SampleClass.Ids[0] = 9;
original.SampleClass.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
SampleStruct.Id | 99 | 10 | 変更が適用されない |
SampleStruct.Name | Hello, World! | AAA | 変更が適用されない |
SampleStruct.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleStruct.Names | あ, B, C | あ, B, C | 変更が適用された |
SampleClass.Id | 99 | 99 | 変更が適用された |
SampleClass.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleClass.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleClass.Names | あ, B, C | あ, B, C | 変更が適用された |
1, 2, 3の実行結果
メンバー内の値型である SampleStruct は、値型 Id
と string型 Name
はコピー元のみ変更が適用されますが、参照型 Ids
と Names
は両方に変更が適用されます。
メンバー内の参照型である SampleClass は、全ての型に変更が適用されます。
構造体は値型のため、複製すると別のインスタンスが生成されます。
クラスと構造体をメンバを持つクラスをシャローコピーで複製する
先程使用した SampleStruct
・SampleClass
をメンバーとして SampleClass2
を作成します。
public class SampleClass2
{
public SampleStruct SampleStruct;
public SampleClass SampleClass;
// 初期化
public SampleClass2(SampleStruct sampleStruct, SampleClass sampleClass)
{
SampleStruct = sampleStruct;
SampleClass = sampleClass;
}
// コピーコンストラクタ
public SampleClass2(SampleClass2 sampleClass2)
{
SampleStruct = sampleClass2.SampleStruct;
SampleClass = sampleClass2.SampleClass;
}
/// <summary>
/// シャローコピーを実行します。
/// </summary>
/// <returns><see cref="SampleClass2" />が戻ります。</returns>
public SampleClass2 SharrowCopy()
{
return (SampleClass2)MemberwiseClone();
}
}
1. 代入
上記で作成した SampleClass2
を「代入」で複製します。
public static void ShallowCopy1()
{
// 構造体
var sample1 = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
// クラス
var sample2 = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var original = new SampleClass2(sample1, sample2);
var copy = original; // 代入
// 構造体の値を変更
original.SampleStruct.Id = 99;
original.SampleStruct.Name = "Hello, " + "World!";
original.SampleStruct.Ids[0] = 9;
original.SampleStruct.Names[0] = "あ";
// クラスの値を変更
original.SampleClass.Id = 99;
original.SampleClass.Name = "Hello, " + "World!";
original.SampleClass.Ids[0] = 9;
original.SampleClass.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
SampleStruct.Id | 99 | 99 | 変更が適用された |
SampleStruct.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleStruct.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleStruct.Names | あ, B, C | あ, B, C | 変更が適用された |
SampleClass.Id | 99 | 99 | 変更が適用された |
SampleClass.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleClass.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleClass.Names | あ, B, C | あ, B, C | 変更が適用された |
1の実行結果
全ての型に変更が適用されます。
クラスは参照型のため、代入すると参照アドレスが渡され同一インスタンスを参照します。
2. コピーコンストラクタ
上記で作成した SampleClass2
を「コピーコンストラクタ」で複製します。
public static void ShallowCopy2()
{
// 構造体
var sample1 = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
// クラス
var sample2 = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var original = new SampleClass2(sample1, sample2);
var copy = new SampleClass2(original); // コピーコンストラクタ
// 構造体の値を変更
original.SampleStruct.Id = 99;
original.SampleStruct.Name = "Hello, " + "World!";
original.SampleStruct.Ids[0] = 9;
original.SampleStruct.Names[0] = "あ";
// クラスの値を変更
original.SampleClass.Id = 99;
original.SampleClass.Name = "Hello, " + "World!";
original.SampleClass.Ids[0] = 9;
original.SampleClass.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
SampleStruct.Id | 99 | 10 | 変更が適用されない |
SampleStruct.Name | Hello, World! | AAA | 変更が適用されない |
SampleStruct.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleStruct.Names | あ, B, C | あ, B, C | 変更が適用された |
SampleClass.Id | 99 | 99 | 変更が適用された |
SampleClass.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleClass.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleClass.Names | あ, B, C | あ, B, C | 変更が適用された |
3. MemberwiseCloneメソッド
上記で作成した SampleClass2
を「MemberwiseCloneメソッド」で複製します。
public static void ShallowCopy3()
{
// 構造体
var sample1 = new SampleStruct(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
// クラス
var sample2 = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C" });
var original = new SampleClass2(sample1, sample2);
var copy = original.SharrowCopy(); // MemberwiseCloneメソッドを呼び出す
// 構造体の値を変更
original.SampleStruct.Id = 99;
original.SampleStruct.Name = "Hello, " + "World!";
original.SampleStruct.Ids[0] = 9;
original.SampleStruct.Names[0] = "あ";
// クラスの値を変更
original.SampleClass.Id = 99;
original.SampleClass.Name = "Hello, " + "World!";
original.SampleClass.Ids[0] = 9;
original.SampleClass.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
SampleStruct.Id | 99 | 10 | 変更が適用されない |
SampleStruct.Name | Hello, World! | AAA | 変更が適用されない |
SampleStruct.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleStruct.Names | あ, B, C | あ, B, C | 変更が適用された |
SampleClass.Id | 99 | 99 | 変更が適用された |
SampleClass.Name | Hello, World! | Hello, World! | 変更が適用された |
SampleClass.Ids | 9, 2, 3 | 9, 2, 3 | 変更が適用された |
SampleClass.Names | あ, B, C | あ, B, C | 変更が適用された |
2, 3の実行結果
メンバー内の値型である SampleStruct は、値型 Id
と string型 Name
はコピー元のみ変更が適用されますが、参照型 Ids
と Names
は両方に変更が適用されます。
メンバー内の参照型である SampleClass は、全ての型に変更が適用されます。
コピーコンストラクタ・MemberwiseCloneメソッドで複製すると別のインスタンスが生成されます。
ディープコピーを実装する
代入・コピーコンストラクタ・MemberwiseCloneメソッドで複製すると、参照型メンバーが必ずシャローコピーになってしまいます。
その為オブジェクトのシャローコピーを作成した後、参照型メンバーをさらに複製する必要があります。
メジャーで簡単な方法
- MemberwiseCloneメソッドを使用し、別のインスタンスを作成します。
- 参照型メンバーがある場合は、そのメンバーを個別に複製します。
1. MemberwiseCloneメソッド
SampleClass
というクラスを用意し、MemberwiseCloneメソッド Array.Cloneメソッド を使用して複製します。
public class SampleClass
{
public int Id { get; set; }
public string Name { get; set; }
public int[] Ids { get; set; }
public string[] Names { get; set; }
// 初期化
public SampleClass(int id, string name, int[] ids, string[] names)
{
Id = id;
Name = name;
Ids = ids;
Names = names;
}
/// <summary>
/// シャローコピーを実行します。
/// </summary>
/// <returns><see cref="SampleClass" />が戻ります。</returns>
public SampleClass SharrowCopy()
{
return (SampleClass) MemberwiseClone();
}
/// <summary>
/// ディープコピーを実行します。
/// </summary>
/// <returns><see cref="SampleClass" />が戻ります。</returns>
public SampleClass DeepCopy()
{
var clone = SharrowCopy();
// Array.Clone の戻り値は Object のためキャストが必要
if (clone.Ids != null)
{
clone.Ids = (int[]) this.Ids.Clone();
}
if (clone.Names != null)
{
clone.Names = (string[]) this.Names.Clone();
}
return clone;
}
}
初期化をし、ディープコピー用のメソッドを呼び出します
public static void DeepCopy()
{
var original = new SampleClass(10, "AAA", new[] { 1, 2, 3 }, new[] { "A", "B", "C"});
var copy = original.DeepCopy();
original.Id = 99;
original.Name = "Hello, " + "World!";
original.Ids[0] = 9;
original.Names[0] = "あ";
}
<original(コピー元)の値を変更したあとの各変数>
original | copy | ||
---|---|---|---|
Id | 99 | 10 | 変更が適用されない |
Name | Hello, World! | AAA | 変更が適用されない |
Ids | 9, 2, 3 | 1, 2, 3 | 変更が適用されない |
Names | あ, B, C | A, B, C | 変更が適用されない |
コピー元の変更が、コピー先に適用されていないことが分かります。
まとめ
型の種類 | シャローコピー | ディープコピー |
---|---|---|
値型 | 別のインスタンスを参照 | 別のインスタンスを参照 |
参照型 | 同一インスタンスを参照 | 別のインスタンスを参照 |
-
値型 - 構造体:シャローコピー(代入・コピーコンストラクタ・MemberwiseCloneメソッド)
コピー先がコピー元と別のインスタンスを参照している。
メンバにstring型以外の参照型が存在する場合、同一インスタンスを参照しているため、変更が適用される。
-
参照型 - 注意点
同じ参照型であっても、string型は変更するたびに別のインスタンスが生成される。 -
参照型 - クラス:シャローコピー(代入)
コピー先がコピー元のインスタンスを参照している。
そのため、クラスメンバに値型が存在しても変更が適用される。
-
参照型 - クラス:シャローコピー(コピーコンストラクタ・MemberwiseCloneメソッド)
コピー先がコピー元と別のインスタンスを参照している。
クラスメンバにstring型以外の参照型が存在する場合、同一インスタンスを参照しているため、変更が適用される。
クラスメンバが値型でも、その種類が構造体の場合、さらにそのメンバが値型か参照型かによって変更が適用されるかどうかが異なる。
記事内で使用したサンプルコードはこちら▶ CSharpDuplicateObjectsSample
参考URL
最後まで見て頂きありがとうございました。