はじめに:「シャローコピー」は“速いけど、油断すると元を汚すコピー”
ディープコピーの逆側にあるのが シャローコピー(浅いコピー) です。
一言でいうと、
「入れ物は別だけど、中身(参照先)はそのまま共有するコピー」
です。
だからこそ軽くて速い一方で、
「コピーしたつもりが、元のオブジェクトまで一緒に変わってしまう」
という事故が起きやすいコピーでもあります。
ここでは、まず「シャローコピーとは何か」をしっかり腹落ちさせてから、
コレクションや 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#この User をコピーしたいとします。
シャローコピーは、ざっくり言うとこういう状態です。
var u1 = new User
{
Name = "Alice",
Address = new Address { City = "Tokyo" }
};
// シャローコピーのイメージ
var u2 = new User
{
Name = u1.Name, // 値型や string はそのままコピーで問題になりにくい
Address = u1.Address // ここがポイント:同じ Address インスタンスを指す
};
C#u2.Address は、u1.Address と同じインスタンスです。
つまり、
u2.Address.City = "Osaka";
Console.WriteLine(u1.Address.City); // "Osaka" と表示される
C#となります。
ここでの重要ポイントは、「シャローコピーは“参照型プロパティを新しく作らない”」ということです。
“どこまで別物にしたいか”を意識していないと、簡単に元を汚します。
C# の代表的なシャローコピー手段
代入(=)は“コピー”ではなく“同じものを見るだけ”
まず一番やりがちなのが、単なる代入です。
var u1 = new User { Name = "Alice" };
var u2 = u1; // これはコピーではなく、同じインスタンスを指す別名
C#u2.Name = "Bob"; とすると、u1.Name も "Bob" になります。
これは「シャローコピー」というより、「参照のコピー(別名)」です。
「コピーしたつもりで同じものを触っている」典型パターンなので、
“代入はコピーではない”という感覚をしっかり持っておくことが大事です。
MemberwiseClone は“典型的なシャローコピー”
object.MemberwiseClone() は、
「フィールドをそのままコピーする」シャローコピーです。
public class User
{
public string Name { get; set; } = "";
public Address Address { get; set; } = new Address();
public User ShallowCopy()
{
return (User)this.MemberwiseClone();
}
}
C#この ShallowCopy は、
User 自体は別インスタンス
Name(string)は値としてコピー
Address は同じインスタンスを共有
という状態になります。
ここでの重要ポイントは、「MemberwiseClone は“参照型の中身まではコピーしてくれない”」ということです。
“速いけど危ないコピー”だと理解しておきましょう。
コレクションとシャローコピー
List の ToList は「要素の参照をコピーするだけ」
LINQ でよく使う ToList() も、実はシャローコピーです。
var users = new List<User>
{
new User { Name = "Alice" },
new User { Name = "Bob" }
};
var copied = users.ToList();
C#users と copied は別の List<User> ですが、
中に入っている User インスタンスは同じものです。
copied[0].Name = "Changed";
Console.WriteLine(users[0].Name); // "Changed" と表示される
C#「リストの入れ物だけ増えたが、中身は共有している」という状態です。
これがまさに「コレクションに対するシャローコピー」です。
ここでの重要ポイントは、「ToList() は“要素のディープコピー”は一切していない」ということです。
“リストをコピーしたから安全”と思っていると、普通に元が汚れます。
Select(x => x) もシャローコピー
LINQ でこう書くことがあります。
var copied = users
.Select(x => x) // そのまま返しているだけ
.ToList();
C#これは ToList() と同じく、「入れ物だけ別、中身は同じ」なシャローコピーです。Select(x => x.DeepClone()) のようにしない限り、ディープコピーにはなりません。
シャローコピーを“あえて使う”場面
読み取り専用で使うときは、シャローコピーでも問題にならない
例えば、「DB から取ってきたユーザー一覧を、画面 A と画面 B で別々にソートして表示したい」だけなら、
中身の User を書き換えない前提で、シャローコピーでも十分です。
var baseList = _userRepository.GetAll().ToList();
var byId = baseList
.OrderBy(x => x.Id)
.ToList(); // シャローコピー
var byName = baseList
.OrderBy(x => x.Name)
.ToList(); // シャローコピー
C#ここで byId と byName の中身の User を変更しないなら、
「同じインスタンスを共有している」ことは問題になりません。
ここでの重要ポイントは、「“読み取り専用で使う”と割り切れるなら、シャローコピーは軽くて速い」ということです。
ディープコピーよりもメモリも CPU も安く済みます。
一時的な並べ替え・フィルタだけならシャローコピーで十分
例えば、「一時的にフィルタした結果を画面に出すだけ」のケース。
var filtered = users
.Where(x => x.IsActive)
.ToList(); // シャローコピー
C#この filtered の中身を変更しないなら、
元の users と User インスタンスを共有していても問題ありません。
「元のコレクションを壊さない」ことと
「要素の中身を壊さない」ことは別物です。
ここでの重要ポイントは、「“要素の中身を変更しない”前提なら、シャローコピーは十分安全」ということです。
シャローコピーを“使ってはいけない”典型パターン
編集用コピーをシャローコピーで作ってしまう
一番危険なのは、「編集用にコピーしたつもりでシャローコピーしてしまう」パターンです。
var original = _userRepository.GetAll().ToList();
// 編集用にコピーしたつもり
var editing = original.ToList(); // シャローコピー
C#ここで editing[0].Name = "変更"; とすると、original[0].Name も一緒に変わります。
「キャンセルしたのに元が変わっている」
「別画面で見ているデータも変わっている」
といったバグの原因になります。
ここでの重要ポイントは、「“編集用”“一時的に書き換える”という用途では、シャローコピーは絶対に使ってはいけない」ということです。
その場合はディープコピーが必要です。
シャローコピーとディープコピーをどう使い分けるか
判断の軸は「要素の中身を変えるかどうか」
シンプルに言うと、次のように考えると整理しやすいです。
要素の中身を一切変えない
→ シャローコピー(ToList など)で十分
要素の中身を変える可能性がある
→ ディープコピーが必要(DeepClone など)
LINQ の ToList() や Select(x => x) は、
「シャローコピーである」と意識して使うことが大事です。
ここでの重要ポイントは、「“コピーの目的”をはっきりさせてから、シャローかディープかを選ぶ」ということです。
なんとなく ToList() していると、いつか痛い目を見ます。
まとめ:「シャローコピー・ユーティリティ」は“軽いけど刃物みたいな道具”
シャローコピーの本質は、
入れ物だけ別にして、
中身(参照先)はそのまま共有することで、
軽く・速くコピーを作る
ことです。
押さえておきたいポイントを整理すると、
シャローコピーは「参照型プロパティを新しく作らないコピー」ToList() や Select(x => x) は、要素のシャローコピーでしかない
読み取り専用で使うなら、シャローコピーは軽くて十分実用的
編集用・一時変更用には絶対に使ってはいけない(ディープコピーが必要)
「要素の中身を変えるかどうか」が、シャローかディープかを選ぶ判断軸になる
ここまで腹落ちしていれば、
「なんとなくコピーしている」段階から抜け出して、
“このコピーはシャローか?ディープか?それで本当にいいのか?”と自分に問いながら、
意図を持ってコレクションと LINQ を使い分けられるようになります。
