C#のUnionとは?LINQで重複を除いて結合する使い方をサンプル付きで解説
はじめに
C#で複数のリストや配列を1つにまとめたいとき、単純に結合するだけならConcatが使えます。しかし、「同じ値は1つだけ残したい」「重複を除いて結合したい」という場合は、LINQのUnionが便利です。
Unionを使うと、2つのコレクションを結合しながら、重複する要素を自動的に取り除けます。たとえば、[1, 2, 3]と[3, 4, 5]をUnionで結合すると、結果は[1, 2, 3, 4, 5]になります。
この記事では、C#のUnionとは何か、基本構文、Listや配列での使い方、オブジェクトの重複判定、UnionByとの違い、よくあるエラーまで、サンプルコード付きでわかりやすく解説します。
1. C#のUnionとは?LINQで重複を除いて結合するメソッド
1-1. Unionは2つのコレクションを結合し、重複を除外するLINQメソッド
C#のUnionは、LINQで提供されている集合演算用のメソッドです。2つのシーケンスを結合し、重複する要素を除いた結果を返します。
C#var result = first.Union(second);
たとえば、次の2つのリストがあるとします。
C#var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
1, 2, 3, 4, 5
3は両方のリストに含まれていますが、Unionの結果では1回だけ出力されます。Microsoft Learnでも、Unionは2つのシーケンスの集合和を生成し、列挙時には先にfirst、次にsecondを処理して、まだ返されていない要素だけを返すメソッドとして説明されています。Microsoft Learn
1-2. C#のUnionでできること
Unionを使うと、主に次のような処理ができます。
C#var allIds = userIdsFromA.Union(userIdsFromB);
このように、複数のデータから重複しない一覧を作成できます。
具体的には、次のような用途に向いています。
| やりたいこと | Unionでできること |
|---|---|
| 2つのリストをまとめたい | まとめた結果を取得できる |
| 重複を除きたい | 同じ値は1つだけ残せる |
| 配列同士を結合したい | 配列にも使用できる |
| オブジェクトを比較したい | 比較条件を指定すれば可能 |
| IDなど特定キーで結合したい | UnionByや比較クラスで対応できる |
Unionは、「結合」と「重複排除」を同時に行いたいときに使うLINQメソッドです。
1-3. Unionが使われる代表的な場面
Unionは、実務でもよく使われます。
たとえば、次のような場面です。
C#var internalUsers = new List<string> { "佐藤", "鈴木", "田中" };
var externalUsers = new List<string> { "田中", "山田", "高橋" };
var allUsers = internalUsers.Union(externalUsers);
Console.WriteLine(string.Join(", ", allUsers));
実行結果は次のとおりです。
佐藤, 鈴木, 田中, 山田, 高橋
社内ユーザー一覧と外部ユーザー一覧をまとめるとき、同じ名前が重複していてもUnionなら1つにまとめられます。
ほかにも、次のような場面で使われます。
| 場面 | 使用例 |
|---|---|
| ユーザーIDの統合 | 複数システムのユーザーIDを重複なしでまとめる |
| タグ一覧の作成 | 複数の記事タグを1つのタグ一覧にする |
| 権限一覧の統合 | 複数ロールの権限をまとめる |
| 商品カテゴリの結合 | 複数店舗のカテゴリを重複なしで表示する |
| 検索結果の統合 | 複数条件の検索結果をまとめる |
2. C#のUnionの基本構文
2-1. Unionの書き方
Unionの基本構文は次のとおりです。
C#var result = collection1.Union(collection2);
collection1とcollection2には、同じ型のコレクションを指定します。
C#var numbers1 = new List<int> { 1, 2, 3 };
var numbers2 = new List<int> { 3, 4, 5 };
var result = numbers1.Union(numbers2);
この場合、resultには1, 2, 3, 4, 5が含まれます。
比較方法を指定したい場合は、次のようにIEqualityComparer<T>を渡すこともできます。
C#var result = collection1.Union(collection2, comparer);
たとえば、文字列の大文字・小文字を区別せずに結合したい場合は、StringComparer.OrdinalIgnoreCaseを使えます。
C#var list1 = new List<string> { "apple", "banana" };
var list2 = new List<string> { "APPLE", "orange" };
var result = list1.Union(list2, StringComparer.OrdinalIgnoreCase);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
apple, banana, orange
2-2. using System.Linqが必要
UnionはLINQのメソッドなので、使用するにはSystem.Linq名前空間が必要です。
C#using System.Linq;
一般的なコンソールアプリや.NET 6以降のプロジェクトでは、暗黙的なusingによって明示的に書かなくても使える場合があります。しかし、プロジェクト設定や古い環境によっては、次のように明示的に追加する必要があります。
C#using System;
using System.Collections.Generic;
using System.Linq;
var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
Unionが認識されない場合は、まずusing System.Linq;があるか確認しましょう。
2-3. Unionの戻り値はIEnumerable<T>
Unionの戻り値は、IEnumerable<T>です。
C#IEnumerable<int> result = list1.Union(list2);
IEnumerable<T>は、foreachで順番に取り出せるデータの並びを表します。Unionを呼び出した時点で必ずしもすぐにリストが作られるわけではありません。
C#var result = list1.Union(list2);
foreach (var number in result)
{
Console.WriteLine(number);
}
このように、foreachで列挙できます。
List<T>として扱いたい場合は、ToList()を使います。
C#List<int> resultList = list1.Union(list2).ToList();
配列として扱いたい場合は、ToArray()を使います。
C#int[] resultArray = list1.Union(list2).ToArray();
2-4. Unionは元のコレクションを変更しない
Unionは、元のリストや配列を直接変更しません。新しい結果を返すメソッドです。
C#var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", list1));
Console.WriteLine(string.Join(", ", list2));
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3
3, 4, 5
1, 2, 3, 4, 5
list1とlist2は変更されず、resultだけが結合結果になります。
そのため、結果を保存したい場合は、必ず変数に代入します。
C#var merged = list1.Union(list2).ToList();
3. C#のUnionの基本的な使い方をサンプルコードで解説
3-1. int型のListをUnionで結合する例
まずは、int型のListをUnionで結合する基本例です。
C#using System;
using System.Collections.Generic;
using System.Linq;
var list1 = new List<int> { 1, 2, 3, 4 };
var list2 = new List<int> { 3, 4, 5, 6 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
1, 2, 3, 4, 5, 6
3と4は両方のリストに含まれていますが、結果には1回だけ表示されます。
Unionは、数値の重複を除いて1つの一覧にまとめたいときに便利です。
3-2. string型のListをUnionで結合する例
次に、string型のListを結合する例です。
C#using System;
using System.Collections.Generic;
using System.Linq;
var fruits1 = new List<string> { "apple", "banana", "orange" };
var fruits2 = new List<string> { "banana", "grape", "melon" };
var result = fruits1.Union(fruits2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
apple, banana, orange, grape, melon
bananaは重複しているため、1つだけ残ります。
ただし、文字列の大文字・小文字は標準では区別されます。
C#var fruits1 = new List<string> { "apple" };
var fruits2 = new List<string> { "APPLE" };
var result = fruits1.Union(fruits2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
apple, APPLE
大文字・小文字を区別したくない場合は、比較方法を指定します。
C#var result = fruits1.Union(fruits2, StringComparer.OrdinalIgnoreCase);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
apple
3-3. 配列同士をUnionで結合する例
UnionはList<T>だけでなく、配列にも使えます。
C#using System;
using System.Linq;
var array1 = new[] { 10, 20, 30 };
var array2 = new[] { 30, 40, 50 };
var result = array1.Union(array2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
10, 20, 30, 40, 50
配列同士でも、重複する30は1つだけになります。
配列の結果として受け取りたい場合は、ToArray()を使います。
C#int[] mergedArray = array1.Union(array2).ToArray();
3-4. ToListやToArrayで結果を変換する方法
Unionの結果はIEnumerable<T>なので、そのままforeachで使えます。
C#var result = list1.Union(list2);
foreach (var item in result)
{
Console.WriteLine(item);
}
ただし、後続処理でList<T>として使いたい場合は、ToList()で変換します。
C#var mergedList = list1.Union(list2).ToList();
mergedList.Add(7);
配列として使いたい場合は、ToArray()を使います。
C#var mergedArray = list1.Union(list2).ToArray();
Console.WriteLine(mergedArray.Length);
Unionの結果に対して、さらにOrderByやWhereなどのLINQメソッドを続けることもできます。
C#var result = list1
.Union(list2)
.Where(x => x >= 3)
.OrderBy(x => x)
.ToList();
このように、Unionは他のLINQメソッドと組み合わせて使いやすい点も特徴です。
4. Unionで重複が除外される仕組み
4-1. Unionは同じ値を1つだけ残す
Unionは、2つのコレクションを順番に確認し、すでに登場した値と同じ値は結果に追加しません。
C#var list1 = new List<int> { 1, 2, 2, 3 };
var list2 = new List<int> { 3, 3, 4 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3, 4
ここで注目したいのは、同じリスト内の重複も除外される点です。
list1の中に2が2回ありますが、結果では1回だけです。list2の中に3が複数回あっても、結果では1回だけです。
つまり、Unionは「2つのコレクションを結合したあとに、重複を取り除く」ようなイメージで使えます。
4-2. 先に出現した要素の順序が優先される
Unionは、基本的に最初のコレクションを先に処理し、その後に2つ目のコレクションを処理します。まだ結果に含まれていない要素だけが返されます。Microsoft Learn
C#var list1 = new List<int> { 3, 1, 2 };
var list2 = new List<int> { 2, 4, 1, 5 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
3, 1, 2, 4, 5
list1の3, 1, 2が先に出力され、その後、list2のうちまだ出ていない4, 5が追加されます。
ただし、並び順を明確に制御したい場合は、OrderByを使うのがおすすめです。
C#var result = list1.Union(list2).OrderBy(x => x);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3, 4, 5
4-3. 大文字・小文字の違いは別要素として扱われる
文字列をUnionで結合する場合、標準の比較では大文字・小文字の違いは別の値として扱われます。
C#var list1 = new List<string> { "CSharp", "LINQ" };
var list2 = new List<string> { "csharp", "Union" };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
CSharp, LINQ, csharp, Union
CSharpとcsharpは文字が似ていますが、大文字・小文字が違うため別要素として残ります。
大文字・小文字を無視したい場合は、StringComparer.OrdinalIgnoreCaseを指定します。
C#var result = list1.Union(list2, StringComparer.OrdinalIgnoreCase);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
CSharp, LINQ, Union
CSharpが先に出現しているため、csharpは重複として除外されます。
4-4. nullを含む場合のUnionの挙動
Unionは、nullを含むコレクションでも使えます。nullも1つの値として扱われるため、重複するnullは1つだけ残ります。
C#var list1 = new List<string?> { "apple", null, "banana" };
var list2 = new List<string?> { null, "orange" };
var result = list1.Union(list2);
foreach (var item in result)
{
Console.WriteLine(item ?? "(null)");
}
実行結果は次のとおりです。
apple
(null)
banana
orange
nullは両方のリストに含まれていますが、結果では1回だけ出力されます。
ただし、nullを含む可能性がある場合は、後続処理でitem.Lengthのように直接アクセスするとNullReferenceExceptionが発生する可能性があります。
C#foreach (var item in result)
{
if (item != null)
{
Console.WriteLine(item.Length);
}
}
Union自体はnullを扱えますが、その後の処理ではnullチェックを忘れないようにしましょう。
5. オブジェクトのListをUnionで結合する方法
5-1. クラスのインスタンスをUnionするときの注意点
intやstringのような型では、Unionの重複判定は直感的に動作します。しかし、自作クラスのオブジェクトをUnionする場合は注意が必要です。
たとえば、次のようなUserクラスがあるとします。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
次のコードを見てみましょう。
C#var users1 = new List<User>
{
new User { Id = 1, Name = "佐藤" },
new User { Id = 2, Name = "鈴木" }
};
var users2 = new List<User>
{
new User { Id = 2, Name = "鈴木" },
new User { Id = 3, Name = "田中" }
};
var result = users1.Union(users2);
foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
一見すると、Id = 2のユーザーは重複として除外されそうです。しかし、自作クラスでは標準の比較が参照ベースになるため、別インスタンスであれば別の要素として扱われることがあります。
つまり、同じIdとNameを持っていても、別々にnewされたオブジェクトは「同じ」と判定されない場合があります。
5-2. EqualsとGetHashCodeを実装して重複判定する
自作クラスでUnionの重複判定を期待通りに動かすには、EqualsとGetHashCodeを実装する方法があります。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public override bool Equals(object? obj)
{
if (obj is not User other)
{
return false;
}
return Id == other.Id && Name == other.Name;
}
public override int GetHashCode()
{
return HashCode.Combine(Id, Name);
}
}
この状態でUnionを使うと、IdとNameが同じオブジェクトは重複として扱われます。
C#var users1 = new List<User>
{
new User { Id = 1, Name = "佐藤" },
new User { Id = 2, Name = "鈴木" }
};
var users2 = new List<User>
{
new User { Id = 2, Name = "鈴木" },
new User { Id = 3, Name = "田中" }
};
var result = users1.Union(users2);
foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
実行結果は次のようになります。
1: 佐藤
2: 鈴木
3: 田中
EqualsとGetHashCodeを正しく実装することで、Unionの重複判定をカスタマイズできます。
5-3. IEqualityComparer<T>を使って比較条件を指定する
クラス自体を変更したくない場合は、IEqualityComparer<T>を使う方法があります。
たとえば、UserのIdが同じなら重複とみなす比較クラスを作ります。
C#public class UserIdComparer : IEqualityComparer<User>
{
public bool Equals(User? x, User? y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.Id == y.Id;
}
public int GetHashCode(User obj)
{
return obj.Id.GetHashCode();
}
}
この比較クラスをUnionに渡します。
C#var result = users1.Union(users2, new UserIdComparer());
foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
IEqualityComparer<T>を使うと、クラス本体を変更せずに比較条件を外部から指定できます。
たとえば、ある場面ではIdで比較し、別の場面ではNameで比較したい場合にも便利です。
5-4. IDや名前など特定プロパティで重複を除くサンプル
IDで重複を除きたい場合の完全なサンプルは次のとおりです。
C#using System;
using System.Collections.Generic;
using System.Linq;
public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
public class UserIdComparer : IEqualityComparer<User>
{
public bool Equals(User? x, User? y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.Id == y.Id;
}
public int GetHashCode(User obj)
{
return obj.Id.GetHashCode();
}
}
var users1 = new List<User>
{
new User { Id = 1, Name = "佐藤" },
new User { Id = 2, Name = "鈴木" }
};
var users2 = new List<User>
{
new User { Id = 2, Name = "鈴木太郎" },
new User { Id = 3, Name = "田中" }
};
var result = users1.Union(users2, new UserIdComparer());
foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
実行結果は次のようになります。
1: 佐藤
2: 鈴木
3: 田中
users2のId = 2は、すでにusers1に存在するため除外されます。この場合、先に出現したusers1側の鈴木が残ります。
名前で重複を除きたい場合は、EqualsとGetHashCodeの比較対象をNameに変更します。
C#public class UserNameComparer : IEqualityComparer<User>
{
public bool Equals(User? x, User? y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.Name == y.Name;
}
public int GetHashCode(User obj)
{
return obj.Name.GetHashCode();
}
}
このように、Unionでオブジェクトを扱う場合は「何をもって同じと判断するか」を明確にすることが重要です。
6. UnionByを使って特定のキーで重複を除く方法
6-1. UnionByとは
UnionByは、特定のキーを指定して2つのシーケンスを結合し、重複を除外するLINQメソッドです。Unionでは要素そのものを比較しますが、UnionByではIdやNameなど、指定したキーをもとに比較できます。
C#var result = users1.UnionBy(users2, user => user.Id);
このコードでは、Userオブジェクト全体ではなく、user.Idをキーとして重複判定します。
Microsoft Learnでも、UnionByはキー選択関数に従って2つのシーケンスの集合和を生成するメソッドとして説明されています。Microsoft Learn
6-2. UnionとUnionByの違い
UnionとUnionByの違いは、重複判定の対象です。
| メソッド | 重複判定の対象 | 主な用途 |
|---|---|---|
| Union | 要素そのもの | int、string、比較実装済みオブジェクト |
| UnionBy | 指定したキー | IdやNameなど特定プロパティで比較したい場合 |
たとえば、UserクラスのIdだけを見て重複判定したい場合、Unionでは比較クラスが必要になることがあります。一方、UnionByならラムダ式でキーを指定できます。
C#var result = users1.UnionBy(users2, user => user.Id);
コードが短くなり、意図もわかりやすくなります。
6-3. UnionByでIDをキーにして結合するサンプル
UnionByでIdをキーにして結合するサンプルです。
C#using System;
using System.Collections.Generic;
using System.Linq;
public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
var users1 = new List<User>
{
new User { Id = 1, Name = "佐藤" },
new User { Id = 2, Name = "鈴木" }
};
var users2 = new List<User>
{
new User { Id = 2, Name = "鈴木太郎" },
new User { Id = 3, Name = "田中" }
};
var result = users1.UnionBy(users2, user => user.Id);
foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
実行結果は次のとおりです。
1: 佐藤
2: 鈴木
3: 田中
Id = 2のユーザーは両方に存在しますが、先に出現したusers1側の鈴木が残ります。
名前をキーにしたい場合は、次のように書けます。
C#var result = users1.UnionBy(users2, user => user.Name);
複数のプロパティをキーにしたい場合は、匿名型を使えます。
C#var result = users1.UnionBy(users2, user => new { user.Id, user.Name });
このように、UnionByを使うと、オブジェクトの重複判定を簡潔に書けます。
6-4. UnionByが使えない環境での代替方法
UnionByが使えない環境では、主に次の方法で代替できます。
1つ目は、IEqualityComparer<T>を使う方法です。
C#var result = users1.Union(users2, new UserIdComparer());
2つ目は、ConcatとGroupByを組み合わせる方法です。
C#var result = users1
.Concat(users2)
.GroupBy(user => user.Id)
.Select(group => group.First());
このコードでは、まずConcatで2つのリストをそのまま結合し、その後GroupByでIdごとにグループ化し、各グループの先頭だけを取得しています。
C#foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
UnionByが使えない場合でも、このようにConcat、GroupBy、Selectを組み合わせれば、特定プロパティで重複を除いた結合を実現できます。
7. Unionと似たLINQメソッドの違い
7-1. UnionとConcatの違い
UnionとConcatは、どちらも2つのコレクションを結合するメソッドです。しかし、重複を除くかどうかが違います。
| メソッド | 重複の扱い |
|---|---|
| Union | 重複を除く |
| Concat | 重複を残す |
C#var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var unionResult = list1.Union(list2);
var concatResult = list1.Concat(list2);
Console.WriteLine(string.Join(", ", unionResult));
Console.WriteLine(string.Join(", ", concatResult));
実行結果は次のとおりです。
1, 2, 3, 4, 5
1, 2, 3, 3, 4, 5
Unionは3を1つだけ残しますが、Concatは両方の3を残します。Microsoft Learnでも、Concatは重複を含めてすべての要素を返し、Unionは一意の値だけを返す点が説明されています。Microsoft Learn+1
7-2. UnionとDistinctの違い
Distinctは、1つのコレクション内の重複を取り除くメソッドです。
C#var list = new List<int> { 1, 2, 2, 3, 3, 4 };
var result = list.Distinct();
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
1, 2, 3, 4
一方、Unionは2つのコレクションを結合しながら重複を取り除きます。
C#var result = list1.Union(list2);
違いをまとめると次のとおりです。
| メソッド | 対象 | 役割 |
|---|---|---|
| Distinct | 1つのコレクション | その中の重複を除く |
| Union | 2つのコレクション | 結合しながら重複を除く |
ConcatしてからDistinctすることで、Unionに近い処理を書くこともできます。
C#var result = list1.Concat(list2).Distinct();
ただし、2つのコレクションを重複なしで結合したいなら、通常はUnionのほうが意図が伝わりやすいです。
7-3. UnionとIntersectの違い
Intersectは、2つのコレクションに共通して含まれる要素だけを取得するメソッドです。
C#var list1 = new List<int> { 1, 2, 3, 4 };
var list2 = new List<int> { 3, 4, 5, 6 };
var result = list1.Intersect(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
3, 4
Unionは両方の要素をまとめますが、Intersectは共通部分だけを取得します。
| メソッド | 意味 | 結果例 |
|---|---|---|
| Union | 和集合 | 1, 2, 3, 4, 5, 6 |
| Intersect | 積集合 | 3, 4 |
重複なしで全体をまとめたい場合はUnion、両方に含まれるものだけを取りたい場合はIntersectを使います。
7-4. UnionとExceptの違い
Exceptは、最初のコレクションから2つ目のコレクションに含まれる要素を除外するメソッドです。
C#var list1 = new List<int> { 1, 2, 3, 4 };
var list2 = new List<int> { 3, 4, 5, 6 };
var result = list1.Except(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
1, 2
list1のうち、list2に含まれる3と4が除外されます。
| メソッド | 役割 |
|---|---|
| Union | 両方をまとめて重複を除く |
| Except | 片方からもう片方に含まれる要素を除く |
Unionは「足す」イメージ、Exceptは「引く」イメージです。
7-5. UnionとJoinの違い
Joinは、2つのコレクションをキーで関連付けて結合するメソッドです。
たとえば、ユーザー一覧と部署一覧を部署IDで結合するような場合に使います。
C#var users = new[]
{
new { Id = 1, Name = "佐藤", DepartmentId = 10 },
new { Id = 2, Name = "鈴木", DepartmentId = 20 }
};
var departments = new[]
{
new { Id = 10, Name = "営業部" },
new { Id = 20, Name = "開発部" }
};
var result = users.Join(
departments,
user => user.DepartmentId,
department => department.Id,
(user, department) => new
{
UserName = user.Name,
DepartmentName = department.Name
}
);
foreach (var item in result)
{
Console.WriteLine($"{item.UserName}: {item.DepartmentName}");
}
実行結果は次のとおりです。
佐藤: 営業部
鈴木: 開発部
Unionは同じ型のデータを重複なしでまとめるメソッドです。一方、Joinは関連する別のデータをキーで結びつけるメソッドです。
| メソッド | 主な目的 |
|---|---|
| Union | 同種のデータを重複なしでまとめる |
| Join | 関連するデータ同士をキーで結合する |
名前は似ていますが、用途は大きく異なります。
8. C#のUnionを使うときの注意点
8-1. 重複判定は型や比較方法によって変わる
Unionの重複判定は、型の等価比較に依存します。
intやstringのような基本的な型では、値が同じかどうかで比較されます。
C#var list1 = new List<int> { 1, 2 };
var list2 = new List<int> { 2, 3 };
var result = list1.Union(list2);
この場合、2は重複として除外されます。
一方、自作クラスでは、何をもって同じと判断するかを明示しないと、期待通りに重複が除外されないことがあります。
C#var result = users1.Union(users2);
このコードで同じIdのユーザーを重複とみなしたい場合は、EqualsとGetHashCodeを実装するか、IEqualityComparer<T>やUnionByを使いましょう。
8-2. Unionは遅延実行される
Unionは遅延実行されます。つまり、Unionを呼び出した時点では、まだ実際の処理が完了していない場合があります。実際に処理されるのは、foreachで列挙したり、ToList()やToArray()で結果を確定したりするときです。
C#var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 4, 5 };
var result = list1.Union(list2);
list1.Add(6);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
1, 2, 3, 6, 4, 5
Unionを呼び出した後にlist1へ追加した6も結果に含まれます。これは、resultを列挙するタイミングで実際の処理が行われるためです。
呼び出した時点の結果を固定したい場合は、ToList()を使います。
C#var result = list1.Union(list2).ToList();
list1.Add(6);
Console.WriteLine(string.Join(", ", result));
この場合、resultには6は含まれません。
8-3. 大量データで使う場合のパフォーマンス
Unionは重複判定を行うため、単純な結合であるConcatよりも処理コストがかかる場合があります。
C#var result = largeList1.Union(largeList2);
大量データを扱う場合は、次の点に注意しましょう。
| 注意点 | 内容 |
|---|---|
| 重複判定のコスト | 要素数が多いほど比較回数や内部管理の負荷が増える |
| 比較処理の重さ | EqualsやGetHashCodeが重いと処理全体が遅くなる |
| メモリ使用量 | 重複管理のための内部構造が使われる |
| 必要な処理か確認 | 重複を残してよいならConcatのほうがシンプル |
特にオブジェクトのUnionでは、GetHashCodeの実装が重要です。適切なハッシュコードを返さないと、パフォーマンスが悪化したり、重複判定が不自然になったりすることがあります。
8-4. データベースに対するLINQで使う場合の注意点
LINQには、メモリ上のコレクションを扱うLINQ to Objectsと、データベースなどに対するLINQがあります。
C#var result = db.Users
.Where(x => x.IsActive)
.Union(db.Users.Where(x => x.IsAdmin));
Entity Framework Coreなどでデータベースに対してUnionを使う場合、SQLに変換されて実行されることがあります。ただし、すべてのC#の比較処理がSQLに変換できるわけではありません。
特に、独自のIEqualityComparer<T>を使ったUnionや、複雑なメソッド呼び出しを含む比較は、データベース側で実行できない場合があります。Microsoft Learnでも、IQueryable<T>などのデータソースでは式ツリーやプロバイダーの制限があるため、データソースのドキュメント確認が必要とされています。Microsoft Learn
データベースに対して使う場合は、次の点を意識しましょう。
C#var query = db.Users
.Select(x => new { x.Id, x.Name })
.Union(
db.ArchivedUsers.Select(x => new { x.Id, x.Name })
);
このように、SQLに変換しやすいシンプルな形にするのがおすすめです。
9. C#のUnionでよくあるエラーと対処法
9-1. 型が一致しない場合のエラー
Unionでは、基本的に2つのコレクションの要素型が一致している必要があります。
次のコードはエラーになります。
C#var numbers = new List<int> { 1, 2, 3 };
var texts = new List<string> { "4", "5" };
var result = numbers.Union(texts);
intのリストとstringのリストをそのままUnionすることはできません。
対処法は、型をそろえることです。
C#var numbers = new List<int> { 1, 2, 3 };
var texts = new List<string> { "4", "5" };
var result = numbers
.Select(x => x.ToString())
.Union(texts);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3, 4, 5
または、文字列を数値に変換してから結合します。
C#var result = numbers.Union(texts.Select(int.Parse));
ただし、int.Parseは変換できない文字列があると例外になるため、入力値には注意が必要です。
9-2. System.Linqを追加していない場合
Unionが見つからない場合、System.Linqが不足している可能性があります。
C#var result = list1.Union(list2);
次のようなエラーが出る場合があります。
'List<int>' does not contain a definition for 'Union'
この場合は、ファイルの先頭に次のusingを追加します。
C#using System.Linq;
必要に応じて、次のように書きます。
C#using System;
using System.Collections.Generic;
using System.Linq;
LINQの拡張メソッドを使うには、System.Linq名前空間を参照できる状態にしておきましょう。
9-3. オブジェクトの重複が期待通りに除外されない場合
自作クラスのリストでUnionを使ったとき、同じ内容のオブジェクトが重複して残ることがあります。
C#var users1 = new List<User>
{
new User { Id = 1, Name = "佐藤" }
};
var users2 = new List<User>
{
new User { Id = 1, Name = "佐藤" }
};
var result = users1.Union(users2);
この場合、IdとNameが同じでも、別インスタンスとして扱われて重複が除外されないことがあります。
対処法は次のいずれかです。
C#var result = users1.Union(users2, new UserIdComparer());
または、UnionByを使います。
C#var result = users1.UnionBy(users2, user => user.Id);
あるいは、クラスにEqualsとGetHashCodeを実装します。
C#public override bool Equals(object? obj)
{
return obj is User other && Id == other.Id;
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
オブジェクトのUnionでは、「どのプロパティで同じと判断するか」を明確にすることが重要です。
9-4. Unionの結果がすぐに反映されないと感じる場合
Unionの結果を変数に入れたのに、思ったタイミングで処理されていないように感じることがあります。
C#var result = list1.Union(list2);
これは、Unionが遅延実行されるためです。結果が実際に作られるのは、列挙したタイミングです。
C#foreach (var item in result)
{
Console.WriteLine(item);
}
すぐに結果を確定したい場合は、ToList()やToArray()を使います。
C#var result = list1.Union(list2).ToList();
これで、その時点の結果がList<T>として確定します。
10. C#のUnionに関するよくある質問
10-1. Unionは重複を必ず削除しますか?
Unionは、比較上「同じ」と判定された要素を重複として除外します。
C#var list1 = new List<int> { 1, 2, 2 };
var list2 = new List<int> { 2, 3 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3
ただし、自作クラスの場合は、標準の比較では期待通りに重複が削除されない場合があります。その場合は、EqualsとGetHashCode、IEqualityComparer<T>、またはUnionByを使いましょう。
10-2. Unionで順番は保証されますか?
Unionは、最初のコレクションを先に処理し、次に2つ目のコレクションを処理します。そのうえで、まだ返していない要素だけを返します。Microsoft Learn
C#var list1 = new List<int> { 5, 1, 3 };
var list2 = new List<int> { 3, 2, 1, 4 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のようになります。
5, 1, 3, 2, 4
ただし、並び順を明確に指定したい場合は、OrderByを使いましょう。
C#var result = list1.Union(list2).OrderBy(x => x);
10-3. 3つ以上のリストをUnionできますか?
できます。Unionを連続して呼び出せば、3つ以上のリストも結合できます。
C#var list1 = new List<int> { 1, 2 };
var list2 = new List<int> { 2, 3 };
var list3 = new List<int> { 3, 4 };
var result = list1
.Union(list2)
.Union(list3);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3, 4
リストが多数ある場合は、Aggregateを使う方法もあります。
C#var lists = new List<List<int>>
{
new List<int> { 1, 2 },
new List<int> { 2, 3 },
new List<int> { 3, 4 }
};
var result = lists.Aggregate((current, next) => current.Union(next).ToList());
Console.WriteLine(string.Join(", ", result));
ただし、可読性を考えると、少数のリストならUnionをつなげて書くほうがわかりやすいです。
10-4. Unionで重複を残したい場合はどうすればよいですか?
重複を残したい場合は、UnionではなくConcatを使います。
C#var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var result = list1.Concat(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3, 3, 4, 5
Unionは重複を除くため、同じ値を複数残したい場面には向いていません。
使い分けは次のように考えるとわかりやすいです。
| やりたいこと | 使うメソッド |
|---|---|
| 重複を除いて結合したい | Union |
| 重複を残して結合したい | Concat |
| 1つのリスト内の重複だけ除きたい | Distinct |
10-5. UnionとSQLのUNIONは同じ意味ですか?
考え方は似ています。どちらも、複数の結果をまとめて重複を除くという意味があります。
C#のLINQでは次のように書きます。
C#var result = list1.Union(list2);
SQLでは次のように書きます。
SQLSELECT Id, Name FROM Users
UNION
SELECT Id, Name FROM ArchivedUsers;
どちらも重複を除いた結果を返す点は似ています。
ただし、完全に同じものではありません。C#のUnionはLINQのメソッドであり、メモリ上のコレクションにも使えます。一方、SQLのUNIONはデータベース上で使う集合演算です。
また、LINQ to ObjectsではC#の等価比較が使われますが、データベースに対するLINQではSQLに変換されて実行される場合があります。そのため、比較方法や実行場所に違いが出ることがあります。
まとめ
C#のUnionは、2つのコレクションを結合し、重複を除いた結果を取得できるLINQメソッドです。
C#var result = list1.Union(list2);
intやstringのリスト、配列などに簡単に使えます。
C#var list1 = new List<int> { 1, 2, 3 };
var list2 = new List<int> { 3, 4, 5 };
var result = list1.Union(list2);
Console.WriteLine(string.Join(", ", result));
実行結果は次のとおりです。
1, 2, 3, 4, 5
Unionを使うときのポイントは次のとおりです。
| ポイント | 内容 |
|---|---|
| 重複を除いて結合する | 同じ値は1つだけ残る |
| 戻り値はIEnumerable<T> | 必要に応じてToList()やToArray()を使う |
| 元のコレクションは変更されない | 新しい結果として取得する |
| オブジェクトでは比較方法に注意 | Equals、GetHashCode、IEqualityComparer<T>、UnionByを使う |
| 重複を残したいならConcat | UnionではなくConcatを使う |
特定のプロパティで重複を除きたい場合は、UnionByが便利です。
C#var result = users1.UnionBy(users2, user => user.Id);
C#でリストや配列を重複なしで結合したいときは、まずUnionを検討しましょう。シンプルな値の結合ならUnion、オブジェクトの特定キーで重複を除きたいならUnionByやIEqualityComparer<T>を使うと、読みやすく保守しやすいコードになります。

