はじめに:「マージ」は“バラバラの情報を一つの流れにまとめる技”
業務システムでは、こういう状況がよく出てきます。
「今年分のデータと昨年分のデータを一緒に扱いたい」
「マスタと明細をくっつけて、見やすい形にしたい」
「複数のリストを一つにまとめて処理したい」
こういう「別々のコレクションを一つにまとめる」ことを、ここではざっくり「マージ」と呼びます。
C# / LINQ では、目的に応じていくつかのマージ方法があります。
単純に「くっつける」
重複を除いて「統合する」
キーを使って「結合する」
この3つを押さえると、業務で必要なマージのほとんどは書けるようになります。
単純マージ:Concat と AddRange で「ただくっつける」
List 同士をそのままつなげるイメージ
まずは一番シンプルな「マージ」です。
「A のリスト」と「B のリスト」を、順番に並べて一つにします。
using System;
using System.Collections.Generic;
using System.Linq;
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5 };
var merged = list1.Concat(list2).ToList();
Console.WriteLine(string.Join(", ", merged)); // 1, 2, 3, 4, 5
C#ここでの重要ポイントは、「Concat は“単に後ろにくっつけるだけ”で、重複もそのまま残る」ということです。
「今年分のログ+昨年分のログ」のように、「全部まとめて時系列で処理したい」ときに向いています。
List のメソッド AddRange との違い
同じことを AddRange でも書けます。
var merged = new List<int>(list1);
merged.AddRange(list2);
C#Concat は「新しい列を返す LINQ メソッド」、AddRange は「既存の List に要素を追加するメソッド」です。
「元のリストを変えたくない」なら Concat、
「この List にどんどん足していきたい」なら AddRange、という使い分けになります。
統合マージ:Union で「重複を除いて一つにする」
重複を除いたマージ
「A と B の両方に同じ値があるときは、1回だけでいい」というマージもよくあります。
このときに使うのが Union です。
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var merged = list1.Union(list2).ToList();
Console.WriteLine(string.Join(", ", merged)); // 1, 2, 3, 4, 5
C#Concat なら 1,2,3,3,4,5 になりますが、Union は「集合」として扱うので、3 が一つにまとまります。
ここでの重要ポイントは、「Union は“重複を除いたマージ”」だということです。
「顧客コード一覧を統合したい」「権限一覧を統合したい」など、
「同じものが複数あっても意味がない」場面で使います。
オブジェクトの Union で気をつけること
オブジェクトのリストに対して Union を使うときは、
「何をもって同じとみなすか」を決める必要があります。
public class Customer
{
public string Code { get; set; } = "";
public string Name { get; set; } = "";
}
C#この場合、単純に Union すると「参照が同じかどうか」で判定されてしまうので、IEqualityComparer<T> を実装するか、Select でコードだけにしてから Union する、などの工夫が必要になります。
初心者のうちは、「値型や string の Union はそのまま使える」「オブジェクトの Union は一段難しい」と覚えておくと安全です。
キーマージ:Join で「マスタと明細をくっつける」
マスタと明細を結合するイメージ
業務で一番「マージっぽい」のは、
「コードだけ持っている明細」と「コード→名称を持っているマスタ」をくっつけるパターンです。
商品マスタと売上明細を例にします。
public class Product
{
public string Code { get; set; } = "";
public string Name { get; set; } = "";
}
public class Sale
{
public string ProductCode { get; set; } = "";
public int Quantity { get; set; }
}
C#var products = new List<Product>
{
new Product { Code = "A001", Name = "りんご" },
new Product { Code = "B002", Name = "バナナ" },
};
var sales = new List<Sale>
{
new Sale { ProductCode = "A001", Quantity = 3 },
new Sale { ProductCode = "B002", Quantity = 2 },
};
C#「売上明細に商品名をくっつけたい」とき、Join を使います。
using System.Linq;
var merged = sales
.Join(
products,
s => s.ProductCode, // 明細側のキー
p => p.Code, // マスタ側のキー
(s, p) => new // 結合後の形
{
ProductCode = s.ProductCode,
ProductName = p.Name,
Quantity = s.Quantity
})
.ToList();
C#ここでの重要ポイントは、「Join は“キーを指定して、2つのコレクションを結合する”」ということです。
第1引数:結合したい相手のコレクション
第2引数:左側(呼び出し元)のキーを取り出す式
第3引数:右側(引数)のキーを取り出す式
第4引数:結合結果をどう形にするか
という並びになっています。
Join の注意点:「片方にしかないデータ」は落ちる
Join は「内部結合(inner join)」なので、
両方にキーが存在するものだけが結果に出てきます。
「売上にはあるけど、マスタにない商品コード」
「マスタにはあるけど、売上には出てこない商品」
こういうものは、Join の結果には含まれません。
業務的に「マスタにないコードは異常」とみなすならそれでよく、
「マスタにないコードも一応見たい」なら、GroupJoin や「左外部結合」の書き方が必要になります。
左外部マージ:GroupJoin+SelectMany で「片方だけのデータも残す」
「売上にはあるがマスタにない」も見たい場合
「売上明細は全部見たい。商品名が取れないものは“名称不明”にしたい」
こういうときは、GroupJoin を使って「左外部結合」を書きます。
var merged = sales
.GroupJoin(
products,
s => s.ProductCode,
p => p.Code,
(s, ps) => new { Sale = s, Products = ps })
.SelectMany(
x => x.Products.DefaultIfEmpty(),
(x, p) => new
{
ProductCode = x.Sale.ProductCode,
ProductName = p?.Name ?? "名称不明",
Quantity = x.Sale.Quantity
})
.ToList();
C#少し難しく見えますが、やっていることはこうです。
売上1件に対して、「対応する商品マスタの集合」をくっつける(GroupJoin)
商品が見つからない場合は「空集合」になるので、DefaultIfEmpty() で null 1件にする
売上×商品(または売上×null)に展開して、最終形に投影する
ここでの重要ポイントは、「GroupJoin + SelectMany + DefaultIfEmpty で“左外部結合”を表現できる」ということです。
最初は丸暗記で構いません。「売上は全部残したいときの定型句」として覚えておくと、実務でかなり役立ちます。
実務で意識してほしい「マージ設計」の考え方
まずは日本語で「どうマージしたいか」を言葉にする
コードを書く前に、必ず言葉で整理してみてください。
単に A と B をつなげたいだけなのか
A と B の重複を除いて統合したいのか
A のコードと B のコードを使って、情報をくっつけたいのか
A は全部残したいのか、両方にあるものだけでいいのか
これが決まれば、選ぶメソッドもほぼ決まります。
ただつなげる → Concat / AddRange
重複を除いて統合 → Union
キーで結合(両方にあるものだけ) → Join
キーで結合(片方は全部残したい) → GroupJoin + SelectMany
という対応です。
「片方にしかないデータ」をどう扱うかを最初に決める
マージで一番事故りやすいのが、「片方にしかないデータ」の扱いです。
マスタにないコードは、業務的に異常として落とすのか
それとも、「名称不明」として残すのか
売上に出てこないマスタは、今回の集計では無視していいのか
ここを決めずに Join だけで書くと、「たまたまマスタにないコードがあったときに、気づかないまま集計から消える」という怖い状態になります。
まとめ:「マージユーティリティ」は“バラバラな世界を一つにするための道具箱”
コレクション・LINQ の「マージ」の本質は、
「別々に存在しているデータを、
業務の意図に沿った一つの流れに組み立て直す」
ことです。
押さえておきたいのは、
単純マージ:Concat(順番にくっつける)
統合マージ:Union(重複を除いてまとめる)
キーマージ:Join(両方にあるキーだけ結合)
左外部マージ:GroupJoin + SelectMany(片方は全部残す)
そして何より、
「片方にしかないデータをどう扱うか」を、
コードを書く前に日本語で決めておくこと。
ここまで腹落ちしていれば、
「とりあえず for で2つのリストを回して if で頑張る」スタイルから卒業して、
“業務の意図がそのまま読める LINQ ベースのマージ処理”を書けるようになります。

