はじめに:「ディープコピー」は“元データを絶対に汚したくないときの保険”
業務コードを書いていると、こういうことが起きがちです。
「編集用にコピーしたつもりが、元のデータまで変わってしまった」
「画面でキャンセルしたのに、なぜか元のオブジェクトが書き変わっている」
原因のほとんどは、「コピーしたつもりで参照を共有している」ことです。
これを防ぐための考え方・テクニックが ディープコピー(深いコピー) です。
ここでは、
浅いコピーと深いコピーの違い
オブジェクト 1 個のディープコピー
コレクション+LINQでのディープコピー
実務で使いやすいディープコピー用ユーティリティの形
を、初心者向けにかみ砕いて説明していきます。
浅いコピーと深いコピーの違いをまず腹落ちさせる
浅いコピー:入れ物だけ別、 中身は同じものを指している
クラスのインスタンスをコピーするとき、
「プロパティの値(参照)だけをそのままコピーする」のが 浅いコピー です。
public class Address
{
public string City { get; set; } = "";
}
public class User
{
public string Name { get; set; } = "";
public Address Address { get; set; } = new Address();
}
C#var u1 = new User
{
Name = "Alice",
Address = new Address { City = "Tokyo" }
};
var u2 = u1; // これはただの参照コピー(同じものを見る)
C#u2.Address.City = "Osaka"; とすると、u1.Address.City も "Osaka" になります。
「コピーしたつもりで、同じインスタンスを触っている」状態です。
MemberwiseClone() を使ったコピーも、基本は「浅いコピー」です。
深いコピー:中身(子オブジェクト)まで丸ごと別インスタンスにする
ディープコピー(深いコピー) は、
User も別インスタンス
User.Address も別インスタンス
その中の値だけ同じ
という状態を作ることです。
var u1 = new User
{
Name = "Alice",
Address = new Address { City = "Tokyo" }
};
var u2 = new User
{
Name = u1.Name,
Address = new Address { City = u1.Address.City }
};
C#この場合、u2.Address.City = "Osaka"; としても、u1.Address.City は "Tokyo" のままです。
ここでの重要ポイントは、「“参照型プロパティをどう扱うか”が浅いコピーと深いコピーの分かれ目」ということです。
ディープコピーでは、参照型も“中身ごとコピー”します。
オブジェクト 1 個のディープコピーをどう設計するか
IDeepCloneable のようなインターフェースを用意する
ディープコピーをきれいに扱うために、
「自分自身をディープコピーできる」ことを表すインターフェースを用意するのが定番です。
public interface IDeepCloneable<T>
{
T DeepClone();
}
C#User に実装してみます。
public class User : IDeepCloneable<User>
{
public string Name { get; set; } = "";
public Address Address { get; set; } = new Address();
public User DeepClone()
{
return new User
{
Name = this.Name,
Address = new Address
{
City = this.Address.City
}
};
}
}
C#Address も必要なら同じように DeepClone を持たせます。
ここでの重要ポイントは、「“ディープコピーの責任”をクラス自身に持たせると、外から安全にコピーできる」ということです。
外側のコードは「DeepClone を呼ぶだけ」で済みます。
コレクション+LINQ でディープコピーする
「1 要素の DeepClone × LINQ の Select」で一気にコピー
IDeepCloneable<T> を実装したオブジェクトのコレクションがあるとします。
var users = new List<User>
{
new User { Name = "Alice", Address = new Address { City = "Tokyo" } },
new User { Name = "Bob", Address = new Address { City = "Osaka" } },
};
C#これを「ディープコピーした新しいリスト」にしたいとき、LINQ を使うととても素直に書けます。
var clonedUsers = users
.Select(u => u.DeepClone())
.ToList();
C#clonedUsers の中身は、users の各要素をディープコピーしたものです。clonedUsers[0].Address.City を変えても、users[0].Address.City には影響しません。
ここでの重要ポイントは、「“1 要素の DeepClone”さえ定義しておけば、コレクションのディープコピーは LINQ の Select で一発」ということです。
ディープコピー用の拡張メソッドを用意する
DeepCloneAll のようなユーティリティにまとめる
毎回 Select(x => x.DeepClone()) と書くのが面倒なら、
拡張メソッドにしてしまうと読みやすくなります。
using System.Collections.Generic;
using System.Linq;
public static class DeepCloneExtensions
{
public static List<T> DeepCloneAll<T>(this IEnumerable<IDeepCloneable<T>> source)
where T : IDeepCloneable<T>
{
return source
.Select(x => x.DeepClone())
.ToList();
}
}
C#使い方はこうです。
List<User> clonedUsers = users.DeepCloneAll();
C#ここでの重要ポイントは、「メソッド名に “Deep” を入れておくことで、“これは参照を共有しないコピーだ”と一目で分かる」ということです。ToList との違いが明確になります。
実務での使いどころ 1:編集前のスナップショットを取る
画面編集用に「元データのコピー」を持ちたい
例えば、ユーザー情報を編集する画面で、
読み込んだ元データ
画面で編集中のデータ
を分けて持ちたいケースがあります。
User original = _userRepository.GetById(id);
User editing = original.DeepClone();
C#画面では editing をいじり、
「保存」ボタンで DB に反映し、
「キャンセル」なら捨てる、という流れです。
コレクションでも同じです。
List<User> originalList = _userRepository.GetAll().ToList();
List<User> editingList = originalList.DeepCloneAll();
C#ここでの重要ポイントは、「“キャンセルしても元に戻る”を保証したいときは、必ずディープコピーを取る」ということです。
浅いコピーだと、キャンセルしても元が汚れている、という悲劇が起きます。
実務での使いどころ 2:一時的な変形・ソート・フィルタ
元のコレクションを壊さずに、別バージョンを作りたい
例えば、「画面 A では ID 順、画面 B では名前順で表示したい」ようなケース。
var baseList = _userRepository.GetAll().ToList();
var byId = baseList.DeepCloneAll().OrderBy(x => x.Id).ToList();
var byName = baseList.DeepCloneAll().OrderBy(x => x.Name).ToList();
C#元の baseList はそのまま、byId と byName はそれぞれ独立したディープコピーです。
ここでの重要ポイントは、「“元のコレクションを基準に、複数のバリエーションを作る”ときは、ディープコピーしてから加工する」ということです。
そうしないと、「片方の画面での変更が、もう片方に影響する」事故が起きます。
ディープコピーの注意点と割り切り
なんでもかんでもディープコピーすると重い
ディープコピーは、「オブジェクトの数 × プロパティの数」だけコピー処理が走ります。
ネストが深いオブジェクトや、大量のデータに対して無闇にディープコピーすると、
メモリも CPU もそれなりに食います。
なので、
本当に元を守りたいところだけディープコピーする
読み取り専用でいいところは Immutable や ReadOnlyCollection で守る
そもそも設計として「書き換えない」方向に寄せる
といったバランス感覚が大事です。
ここでの重要ポイントは、「ディープコピーは“最後の保険”であって、乱発するものではない」ということです。
「ここだけは絶対に元を汚したくない」という場所に絞って使うと、効果的です。
まとめ:「ディープコピー・ユーティリティ」は“元データを守るための最後の盾”
ディープコピーの本質は、
参照を共有してしまうと危険な場面で、
オブジェクトやコレクションを“中身ごと別物”として複製し、
元データを絶対に汚さないようにする
ことです。
押さえておきたいポイントを整理すると、
浅いコピーは「参照をコピーするだけ」、深いコピーは「子オブジェクトまで別インスタンス」
1 要素の DeepClone をクラス自身に実装しておくと、LINQ の Select でコレクションを一気にディープコピーできるDeepCloneAll のような拡張メソッドにすると、意図がコードに乗って読みやすい
編集前スナップショットや、複数バリエーションのリストを作るときに特に有効
コストもかかるので、「ここだけは守りたい」という場所に絞って使う
ここまで理解できていれば、
「なんとなく new して代入しているだけ」の段階から抜け出して、
“どこで参照を共有し、どこでディープコピーするか”を意識して設計できるようになります。
