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);

collection1collection2には、同じ型のコレクションを指定します。

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

list1list2は変更されず、resultだけが結合結果になります。

そのため、結果を保存したい場合は、必ず変数に代入します。

C#
var merged = list1.Union(list2).ToList();

3. C#のUnionの基本的な使い方をサンプルコードで解説

3-1. int型のListをUnionで結合する例

まずは、int型のListUnionで結合する基本例です。

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

34は両方のリストに含まれていますが、結果には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で結合する例

UnionList<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の結果に対して、さらにOrderByWhereなどの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

list13, 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

CSharpcsharpは文字が似ていますが、大文字・小文字が違うため別要素として残ります。

大文字・小文字を無視したい場合は、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するときの注意点

intstringのような型では、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のユーザーは重複として除外されそうです。しかし、自作クラスでは標準の比較が参照ベースになるため、別インスタンスであれば別の要素として扱われることがあります。

つまり、同じIdNameを持っていても、別々にnewされたオブジェクトは「同じ」と判定されない場合があります。

5-2. EqualsとGetHashCodeを実装して重複判定する

自作クラスでUnionの重複判定を期待通りに動かすには、EqualsGetHashCodeを実装する方法があります。

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を使うと、IdNameが同じオブジェクトは重複として扱われます。

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: 田中

EqualsGetHashCodeを正しく実装することで、Unionの重複判定をカスタマイズできます。

5-3. IEqualityComparer<T>を使って比較条件を指定する

クラス自体を変更したくない場合は、IEqualityComparer<T>を使う方法があります。

たとえば、UserIdが同じなら重複とみなす比較クラスを作ります。

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: 田中

users2Id = 2は、すでにusers1に存在するため除外されます。この場合、先に出現したusers1側の鈴木が残ります。

名前で重複を除きたい場合は、EqualsGetHashCodeの比較対象を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ではIdNameなど、指定したキーをもとに比較できます。

C#
var result = users1.UnionBy(users2, user => user.Id);

このコードでは、Userオブジェクト全体ではなく、user.Idをキーとして重複判定します。

Microsoft Learnでも、UnionByはキー選択関数に従って2つのシーケンスの集合和を生成するメソッドとして説明されています。Microsoft Learn

6-2. UnionとUnionByの違い

UnionUnionByの違いは、重複判定の対象です。

メソッド重複判定の対象主な用途
Union要素そのものintstring、比較実装済みオブジェクト
UnionBy指定したキーIdNameなど特定プロパティで比較したい場合

たとえば、UserクラスのIdだけを見て重複判定したい場合、Unionでは比較クラスが必要になることがあります。一方、UnionByならラムダ式でキーを指定できます。

C#
var result = users1.UnionBy(users2, user => user.Id);

コードが短くなり、意図もわかりやすくなります。

6-3. UnionByでIDをキーにして結合するサンプル

UnionByIdをキーにして結合するサンプルです。

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つ目は、ConcatGroupByを組み合わせる方法です。

C#
var result = users1
.Concat(users2)
.GroupBy(user => user.Id)
.Select(group => group.First());

このコードでは、まずConcatで2つのリストをそのまま結合し、その後GroupByIdごとにグループ化し、各グループの先頭だけを取得しています。

C#
foreach (var user in result)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}

UnionByが使えない場合でも、このようにConcatGroupBySelectを組み合わせれば、特定プロパティで重複を除いた結合を実現できます。

7. Unionと似たLINQメソッドの違い

7-1. UnionとConcatの違い

UnionConcatは、どちらも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

Union3を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);

違いをまとめると次のとおりです。

メソッド対象役割
Distinct1つのコレクションその中の重複を除く
Union2つのコレクション結合しながら重複を除く

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に含まれる34が除外されます。

メソッド役割
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の重複判定は、型の等価比較に依存します。

intstringのような基本的な型では、値が同じかどうかで比較されます。

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のユーザーを重複とみなしたい場合は、EqualsGetHashCodeを実装するか、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);

大量データを扱う場合は、次の点に注意しましょう。

注意点内容
重複判定のコスト要素数が多いほど比較回数や内部管理の負荷が増える
比較処理の重さEqualsGetHashCodeが重いと処理全体が遅くなる
メモリ使用量重複管理のための内部構造が使われる
必要な処理か確認重複を残してよいなら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);

この場合、IdNameが同じでも、別インスタンスとして扱われて重複が除外されないことがあります。

対処法は次のいずれかです。

C#
var result = users1.Union(users2, new UserIdComparer());

または、UnionByを使います。

C#
var result = users1.UnionBy(users2, user => user.Id);

あるいは、クラスにEqualsGetHashCodeを実装します。

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

ただし、自作クラスの場合は、標準の比較では期待通りに重複が削除されない場合があります。その場合は、EqualsGetHashCodeIEqualityComparer<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では次のように書きます。

SQL
SELECT 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);

intstringのリスト、配列などに簡単に使えます。

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()を使う
元のコレクションは変更されない新しい結果として取得する
オブジェクトでは比較方法に注意EqualsGetHashCodeIEqualityComparer<T>UnionByを使う
重複を残したいならConcatUnionではなくConcatを使う

特定のプロパティで重複を除きたい場合は、UnionByが便利です。

C#
var result = users1.UnionBy(users2, user => user.Id);

C#でリストや配列を重複なしで結合したいときは、まずUnionを検討しましょう。シンプルな値の結合ならUnion、オブジェクトの特定キーで重複を除きたいならUnionByIEqualityComparer<T>を使うと、読みやすく保守しやすいコードになります。