C# の LINQ で重複を除く Distinct
Distinct はシーケンスから重複を除去するメソッドだ。ユニークな値だけを取り出したいときに使う。
基本的な使い方
同じ値を持つ要素を1つにまとめる。
int[] numbers = { 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 };
var unique = numbers.Distinct();
foreach (var n in unique)
{
Console.Write($"{n} "); // 1 2 3 4
}出現順は維持される。最初に出現したものが残り、2回目以降は除外される。
文字列の重複除去
文字列でも同様に使える。
string[] words = { "apple", "banana", "apple", "cherry", "banana" };
var unique = words.Distinct();
foreach (var word in unique)
{
Console.WriteLine(word);
}
// apple
// banana
// cherry大文字小文字を区別しない比較
デフォルトでは大文字小文字を区別する。区別したくない場合は StringComparer を指定する。
string[] names = { "Alice", "alice", "Bob", "ALICE" };
// デフォルト: 大文字小文字を区別
var distinct1 = names.Distinct();
// Alice, alice, Bob, ALICE(4件)
// 大文字小文字を区別しない
var distinct2 = names.Distinct(StringComparer.OrdinalIgnoreCase);
// Alice, Bob(2件)オブジェクトの重複除去
参照型のオブジェクトでは、デフォルトで参照の等価性が使われる。
var people = new List<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Bob", Age = 30 }
};
var distinct = people.Distinct();
Console.WriteLine(distinct.Count()); // 3(すべて別オブジェクト)同じ内容でも別のインスタンスなら重複とみなされない。
IEqualityComparer を使う
オブジェクトの特定のプロパティで重複判定したい場合は、カスタムの比較クラスを作る。
public class PersonNameComparer : IEqualityComparer<Person>
{
public bool Equals(Person? x, Person? y)
{
if (x == null || y == null) return false;
return x.Name == y.Name;
}
public int GetHashCode(Person obj)
{
return obj.Name?.GetHashCode() ?? 0;
}
}このコンペアラを Distinct に渡す。
var people = new List<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 30 }
};
var distinct = people.Distinct(new PersonNameComparer());
Console.WriteLine(distinct.Count()); // 2(Alice と Bob)DistinctBy(.NET 6 以降)
.NET 6 以降では DistinctBy が使え、カスタムコンペアラなしでプロパティ指定ができる。
var people = new List<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 30 }
};
// 名前で重複除去
var distinctByName = people.DistinctBy(p => p.Name);
Console.WriteLine(distinctByName.Count()); // 2
// 年齢で重複除去
var distinctByAge = people.DistinctBy(p => p.Age);
Console.WriteLine(distinctByAge.Count()); // 2(25歳と30歳)非常に便利なので、.NET 6 以降なら積極的に使おう。
GroupBy で代用
DistinctBy がない環境では、GroupBy で代用できる。
var people = new List<Person>
{
new Person { Name = "Alice", Age = 25 },
new Person { Name = "Alice", Age = 30 },
new Person { Name = "Bob", Age = 30 }
};
// 名前でグループ化し、各グループの最初の要素を取る
var distinctByName = people
.GroupBy(p => p.Name)
.Select(g => g.First());
Console.WriteLine(distinctByName.Count()); // 2実用的な例
タグの一覧取得
var posts = new List<Post>
{
new Post { Title = "記事1", Tags = new[] { "C#", "LINQ" } },
new Post { Title = "記事2", Tags = new[] { "C#", "ASP.NET" } },
new Post { Title = "記事3", Tags = new[] { "LINQ", "Entity Framework" } }
};
var allTags = posts
.SelectMany(p => p.Tags)
.Distinct()
.OrderBy(t => t);
foreach (var tag in allTags)
{
Console.WriteLine(tag);
}
// ASP.NET
// C#
// Entity Framework
// LINQすべての記事からタグを集め、重複を除去して一覧化している。
重複チェック
var ids = new List<int> { 1, 2, 3, 2, 4, 3 };
bool hasDuplicates = ids.Count() != ids.Distinct().Count();
Console.WriteLine(hasDuplicates); // True元の数と Distinct 後の数が違えば重複がある。
Union / Intersect / Except
集合演算のメソッドも重複除去と関連している。
int[] a = { 1, 2, 3, 4 };
int[] b = { 3, 4, 5, 6 };
var union = a.Union(b); // 1, 2, 3, 4, 5, 6(和集合)
var intersect = a.Intersect(b); // 3, 4(積集合)
var except = a.Except(b); // 1, 2(差集合)Union は両方のシーケンスを結合し、自動的に重複を除去する。Concat + Distinct と同じ結果だが、Union の方が意図が明確だ。