C# Tips | コレクション・LINQ:重複除去

C# C#
スポンサーリンク

はじめに:「重複除去」は“データをきれいに整えるフィルター”

業務システムでは、同じ ID のレコードが二重に入っていたり、同じメールアドレスが何度も出てきたりと、「重複データ」がよく紛れ込みます。
この重複をきれいに取り除くのが「重複除去(重複排除)」です。

C# では、LINQ を使うと「重複を消す」処理をかなりシンプルに書けます。
ここでは、初心者向けに

  • 一番基本の Distinct
  • 「特定のキーで重複除去」するパターン
  • 自作クラスの重複除去

という順で、かみ砕いて説明していきます。


基本:Distinct で「まったく同じ値」を重複除去する

int や string の重複を消す

まずは一番シンプルなパターンです。
Distinct() は、「同じ値が複数あるなら、1つだけ残す」というメソッドです。

using System;
using System.Collections.Generic;
using System.Linq;

var numbers = new List<int> { 1, 2, 2, 3, 3, 3, 4 };

var distinct = numbers.Distinct();

foreach (var n in distinct)
{
    Console.WriteLine(n); // 1, 2, 3, 4
}
C#

string でも同じです。

var names = new List<string> { "Alice", "Bob", "Alice", "Charlie" };

var distinctNames = names.Distinct();

foreach (var n in distinctNames)
{
    Console.WriteLine(n); // Alice, Bob, Charlie
}
C#

ここでの重要ポイントは、「Distinct() は“要素そのもの”が同じかどうかで判断する」ということです。
intstring のような“値型・基本的な参照型”は、これだけで十分なことが多いです。


応用:特定のキーで重複除去したい(ID だけ見たい)

「同じユーザーIDなら1件にまとめたい」というケース

業務では、「オブジェクト全体が同じかどうか」ではなく、
「UserId が同じなら重複とみなしたい」といったケースがよくあります。

例えば、こんなクラスがあるとします。

public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
}
C#

データがこうだったとします。

var users = new List<User>
{
    new User { Id = 1, Name = "Alice" },
    new User { Id = 1, Name = "Alice (duplicate)" },
    new User { Id = 2, Name = "Bob" },
};
C#

このとき、「Id が 1 のユーザーは 1 件にしたい」という重複除去をしたいわけです。

GroupBy + First で「キーごとに1件だけ残す」

LINQ には「キーでグループ化する」GroupBy があるので、
これを使って「同じ Id ごとにまとめて、その中の先頭だけ取る」という書き方ができます。

var distinctUsers = users
    .GroupBy(u => u.Id)
    .Select(g => g.First());
C#

GroupBy(u => u.Id) で「Id ごとにグループ化」し、
Select(g => g.First()) で「各グループの先頭要素だけ取る」というイメージです。

ここでの重要ポイントは、「重複除去=“キーごとに1件だけ代表を残す”」と考えることです。
GroupBy は「キーごとにまとめる」、First は「代表を1つ選ぶ」、という役割になります。


もう一歩:Distinct に「比較ルール」を教えてあげる

IEqualityComparer<T> を使って「何をもって同じとするか」を定義する

Distinct() は、第二引数に「比較ルール(IEqualityComparer<T>)」を渡すことができます。
これを使うと、「User は Id が同じなら同じとみなす」といったルールを Distinct に直接教えられます。

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();
    }
}
C#

この比較クラスを使って、Distinct を呼びます。

var distinctUsers = users.Distinct(new UserIdComparer());
C#

これで、「Id が同じ User は重複とみなして 1 件だけ残す」という動きになります。

ここでの重要ポイントは、「Equals と GetHashCode をセットで定義する」ことです。
Equals で「同じかどうか」、GetHashCode で「同じものは同じハッシュ値」を返すようにします。
この2つが一致していないと、Distinct の結果がおかしくなる可能性があります。


実務での使い分けのイメージ

単純な型(int, string)はそのまま Distinct

ID の一覧、メールアドレスの一覧など、
「値そのものが重複しているかどうか」を見たいだけなら、
素直に Distinct() で十分です。

var ids = new[] { 1, 1, 2, 3, 3 };
var distinctIds = ids.Distinct(); // 1, 2, 3
C#

オブジェクトは「何をもって同じとするか」を決める

User, Order, Product などのオブジェクトでは、
「ID が同じなら同じ」「メールアドレスが同じなら同じ」など、
“同一性の基準”を自分で決める必要があります。

そのときの選択肢は、ざっくり言うとこの2つです。

  • GroupBy(key)Select(g => g.First())
  • Distinct(new CustomComparer())

どちらも「キーごとに1件だけ残す」という意味では同じですが、
GroupBy は「キーとグループの両方を使いたいとき」、
Distinct は「単に重複を消したいだけのとき」に向いています。


まとめ:「重複除去ユーティリティ」は“データを信頼できる形に整えるフィルター”

重複除去の本質は、
「同じ意味のデータが何度も出てこないように、1件にまとめる」ことです。

C# / LINQ では、

  • Distinct() で「値そのものが同じもの」を重複除去できる
  • GroupBy(key).Select(First) で「キーごとに1件だけ代表を残す」ことができる
  • Distinct(new Comparer) で「オブジェクトの“同一性ルール”を自分で定義して重複除去できる」

という道具が揃っています。

大事なのは、「何をもって“同じ”とみなすか」を先に言葉で決めてから、
それをコード(キーや comparer)に落とし込むことです。
そこさえブレなければ、LINQ の重複除去はとても強力な“業務ユーティリティ”になります。

タイトルとURLをコピーしました