C#の重複チェック完全ガイド|List・配列をLINQ/HashSetで判定・削除する方法
はじめに
C#でListや配列を扱っていると、「同じ値が含まれていないか確認したい」「重複しているデータだけ取り出したい」「重複を削除して一意な値だけにしたい」という場面がよくあります。
たとえば、ユーザーID、メールアドレス、商品コード、CSVから読み込んだデータ、画面から入力された値などは、重複があると登録エラーや集計ミスの原因になります。
C#の重複チェックには、主に次の方法があります。
C#Distinct()
GroupBy()
HashSet<T>
ToHashSet()
DistinctBy()
IEqualityComparer<T>
少量データならLINQを使うと簡潔に書けます。一方、大量データを高速に判定したい場合はHashSetが向いています。
この記事では、C#の重複チェックについて、List・配列・オブジェクトを対象に、判定・抽出・削除の方法を実践的なコード付きで解説します。
1. C#で重複チェックを行う基本
1-1. 重複チェックとは何を判定する処理か
C#における重複チェックとは、コレクション内に同じ値や同じ条件に該当するデータが複数存在するかを確認する処理です。
たとえば、次のListには"A"が2回登場しています。
C#var codes = new List<string> { "A", "B", "C", "A" };
この場合、"A"は重複している値です。
単純な文字列や数値であれば、値そのものを比較すれば重複を判定できます。
C#var numbers = new List<int> { 1, 2, 3, 2 };
この場合は2が重複しています。
一方、クラスやオブジェクトの場合は、「何を基準に同じとみなすか」を決める必要があります。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}
このようなUserクラスでは、次のような基準が考えられます。
C#// Idが同じなら重複
// Nameが同じなら重複
// IdとNameの両方が同じなら重複
つまり、C#の重複チェックでは「どの値を見て重複と判断するか」を最初に明確にすることが重要です。
1-2. List・配列・オブジェクトで重複が発生するケース
Listや配列では、同じ値が複数回追加されることで重複が発生します。
C#var list = new List<string>();
list.Add("apple");
list.Add("banana");
list.Add("apple");
この例では、"apple"が重複しています。
配列でも同様です。
C#string[] names = { "佐藤", "鈴木", "佐藤", "田中" };
この場合、"佐藤"が重複しています。
オブジェクトのListでは、見た目は同じデータでも、インスタンスが異なるため、そのままでは重複と判定されないことがあります。
C#var users = new List<User>
{
new User { Id = 1, Name = "Alice" },
new User { Id = 1, Name = "Alice" }
};
この2つのUserオブジェクトは、IdもNameも同じですが、別々のインスタンスです。そのため、Distinct()をそのまま使っても期待どおりに重複削除されない場合があります。
オブジェクトの重複チェックでは、GroupBy()やDistinctBy()、IEqualityComparer<T>を使って、比較基準を明示するのが一般的です。
1-3. 重複を「判定する」「抽出する」「削除する」の違い
重複チェックといっても、実際には目的がいくつかに分かれます。
まず、「重複があるか判定する」処理です。
C#var list = new List<int> { 1, 2, 3, 2 };
bool hasDuplicate = list.Count != list.Distinct().Count();
Console.WriteLine(hasDuplicate); // True
これは、重複が存在するかどうかだけを知りたい場合に使います。
次に、「重複している値を抽出する」処理です。
C#var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
foreach (var item in duplicates)
{
Console.WriteLine(item); // 2
}
これは、どの値が重複しているのかを知りたい場合に使います。
最後に、「重複を削除する」処理です。
C#var uniqueList = list.Distinct().ToList();
foreach (var item in uniqueList)
{
Console.WriteLine(item); // 1, 2, 3
}
これは、重複を取り除いて一意な値だけにしたい場合に使います。
同じ「C# 重複チェック」でも、目的によって使うメソッドが変わります。
1-4. LINQとHashSetの使い分け早見表
C#で重複チェックを行う場合、よく使われるのがLINQとHashSetです。
LINQはコードが読みやすく、Distinct()やGroupBy()を使えば簡単に重複チェックできます。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
一方、HashSetは重複判定が高速で、大量データのチェックに向いています。
C#var set = new HashSet<int>();
foreach (var item in list)
{
if (!set.Add(item))
{
Console.WriteLine("重複あり");
break;
}
}
目安としては、次のように使い分けるとよいでしょう。
C#// 重複を削除したい
var unique = list.Distinct().ToList();
// 重複している値を知りたい
var duplicates = list.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
// 高速に重複があるかだけ判定したい
var set = new HashSet<int>();
bool hasDuplicate = list.Any(x => !set.Add(x));
少量データや可読性を重視する場合はLINQ、大量データや速度を重視する場合はHashSetを選ぶのがおすすめです。
2. Listの重複チェック方法
2-1. Listに重複があるか判定する基本コード
Listに重複があるか確認する基本的な方法は、元の件数と重複削除後の件数を比較することです。
C#var list = new List<string> { "A", "B", "C", "A" };
bool hasDuplicate = list.Count != list.Distinct().Count();
Console.WriteLine(hasDuplicate); // True
Distinct()は重複を取り除いた結果を返します。その件数が元のListの件数より少なければ、重複があったと判断できます。
重複がない場合は、元の件数とDistinct()後の件数が同じになります。
C#var list = new List<string> { "A", "B", "C" };
bool hasDuplicate = list.Count != list.Distinct().Count();
Console.WriteLine(hasDuplicate); // False
この方法は簡単で読みやすいため、Listの重複チェックでよく使われます。
2-2. DistinctとCountを使って重複を判定する
Distinct()とCount()を使う方法は、数値のListでも同じように使えます。
C#var numbers = new List<int> { 10, 20, 30, 20 };
bool hasDuplicate = numbers.Count != numbers.Distinct().Count();
if (hasDuplicate)
{
Console.WriteLine("重複があります");
}
else
{
Console.WriteLine("重複はありません");
}
結果は次のようになります。
重複があります
この書き方は、重複の有無だけを知りたい場合に便利です。
ただし、Distinct()は重複を除いた新しい列挙結果を作るため、大量データではHashSetを使ったほうが効率的な場合があります。
また、Distinct()は既定の等価比較を使います。文字列であれば同じ文字列かどうか、数値であれば同じ数値かどうかで判断されます。
C#var words = new List<string> { "Apple", "apple" };
bool hasDuplicate = words.Count != words.Distinct().Count();
Console.WriteLine(hasDuplicate); // False
この例では、"Apple"と"apple"は大文字小文字が異なるため、別の値として扱われます。
大文字小文字を区別せずに判定したい場合は、StringComparer.OrdinalIgnoreCaseを指定します。
C#bool hasDuplicate = words.Count != words
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
Console.WriteLine(hasDuplicate); // True
2-3. GroupByを使って重複している値を抽出する
重複があるかどうかだけでなく、どの値が重複しているのかを知りたい場合はGroupBy()を使います。
C#var list = new List<string> { "A", "B", "C", "A", "B" };
var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
foreach (var item in duplicates)
{
Console.WriteLine(item);
}
実行結果は次のとおりです。
A
B
GroupBy(x => x)で値ごとにグループ化し、Where(g => g.Count() > 1)で2件以上あるグループだけを抽出しています。
重複している値と件数を一緒に取得したい場合は、次のように書けます。
C#var duplicateItems = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => new
{
Value = g.Key,
Count = g.Count()
})
.ToList();
foreach (var item in duplicateItems)
{
Console.WriteLine($"{item.Value}: {item.Count}件");
}
実行結果です。
A: 2件
B: 2件
重複データをログ出力したり、エラーメッセージに表示したりする場合は、この方法が便利です。
2-4. Any・Countを使った重複チェックの注意点
Any()とCount()を使って、次のように重複チェックを書くこともできます。
C#var list = new List<string> { "A", "B", "C", "A" };
bool hasDuplicate = list.Any(x => list.Count(y => y == x) > 1);
Console.WriteLine(hasDuplicate); // True
このコードでも重複の有無は判定できます。
しかし、この方法は要素ごとにList全体を数え直すため、データ件数が多いと処理が遅くなりやすいです。
C#list.Any(x => list.Count(y => y == x) > 1)
この書き方は、Listの要素数が少ない場合には問題ありませんが、大量データでは避けたほうがよいでしょう。
重複の有無だけを判定するなら、HashSetを使った次の書き方が効率的です。
C#var set = new HashSet<string>();
bool hasDuplicate = list.Any(x => !set.Add(x));
Console.WriteLine(hasDuplicate); // True
HashSet.Add()は、すでに同じ値が存在する場合にfalseを返します。そのため、!set.Add(x)がtrueになった時点で重複が見つかったと判断できます。
2-5. Listから重複を削除して一意な値だけ残す方法
Listから重複を削除して、一意な値だけを残したい場合はDistinct()を使います。
C#var list = new List<string> { "A", "B", "C", "A", "B" };
var uniqueList = list.Distinct().ToList();
foreach (var item in uniqueList)
{
Console.WriteLine(item);
}
実行結果です。
A
B
C
Distinct()は重複を削除しますが、元のList自体は変更しません。新しいListを作りたい場合はToList()を使います。
元の変数に上書きしたい場合は、次のようにします。
C#list = list.Distinct().ToList();
大文字小文字を区別せずに重複削除したい場合は、比較方法を指定します。
C#var list = new List<string> { "Apple", "apple", "Banana" };
var uniqueList = list
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
foreach (var item in uniqueList)
{
Console.WriteLine(item);
}
実行結果は次のようになります。
Apple
Banana
最初に登場した"Apple"が残り、後から登場した"apple"は重複として削除されます。
3. 配列の重複チェック方法
3-1. 配列に重複があるか判定する基本コード
配列でも、Listと同じようにDistinct()とLengthを使って重複チェックできます。
C#int[] numbers = { 1, 2, 3, 2 };
bool hasDuplicate = numbers.Length != numbers.Distinct().Count();
Console.WriteLine(hasDuplicate); // True
配列の場合、要素数はCountではなくLengthで取得します。
文字列配列でも同じです。
C#string[] names = { "Alice", "Bob", "Alice" };
bool hasDuplicate = names.Length != names.Distinct().Count();
Console.WriteLine(hasDuplicate); // True
配列の重複チェックでも、LINQを使う場合は次の名前空間が必要です。
C#using System.Linq;
最近のC#プロジェクトでは暗黙的に利用できることもありますが、エラーになる場合はusing System.Linq;を追加してください。
3-2. 配列をLINQで重複チェックする方法
配列をLINQで重複チェックする場合、基本的な考え方はListと同じです。
C#string[] codes = { "A001", "A002", "A003", "A001" };
bool hasDuplicate = codes.Length != codes.Distinct().Count();
if (hasDuplicate)
{
Console.WriteLine("重複するコードがあります");
}
Distinct()は配列に対しても使えます。
また、重複している値を確認したい場合はGroupBy()を使います。
C#var duplicates = codes
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToArray();
foreach (var code in duplicates)
{
Console.WriteLine(code);
}
実行結果です。
A001
Listの場合はToList()、配列として取得したい場合はToArray()を使います。
3-3. 配列から重複する要素を抽出する方法
配列から重複している要素だけを取り出すには、GroupBy()を使います。
C#int[] numbers = { 1, 2, 3, 2, 4, 3, 5 };
var duplicates = numbers
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToArray();
foreach (var number in duplicates)
{
Console.WriteLine(number);
}
実行結果です。
2
3
重複している値と件数を取得する場合は、匿名型にして取り出します。
C#var duplicateDetails = numbers
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => new
{
Value = g.Key,
Count = g.Count()
})
.ToArray();
foreach (var item in duplicateDetails)
{
Console.WriteLine($"{item.Value}: {item.Count}回");
}
実行結果です。
2: 2回
3: 2回
配列データを検証するときは、重複している値だけでなく、件数も出力すると原因を特定しやすくなります。
3-4. 配列から重複を削除して新しい配列を作る方法
配列から重複を削除して新しい配列を作るには、Distinct()のあとにToArray()を呼び出します。
C#int[] numbers = { 1, 2, 3, 2, 4, 3 };
int[] uniqueNumbers = numbers.Distinct().ToArray();
foreach (var number in uniqueNumbers)
{
Console.WriteLine(number);
}
実行結果です。
1
2
3
4
元の配列は変更されません。
C#Console.WriteLine(numbers.Length); // 6
Console.WriteLine(uniqueNumbers.Length); // 4
C#の配列は固定長のため、既存の配列から要素を直接削除するというより、重複を削除した新しい配列を作るのが一般的です。
文字列配列でも同じように書けます。
C#string[] fruits = { "apple", "banana", "apple", "orange" };
string[] uniqueFruits = fruits.Distinct().ToArray();
3-5. 文字列配列・数値配列での実装例
文字列配列で重複チェックを行う例です。
C#string[] emails =
{
"user1@example.com",
"user2@example.com",
"user1@example.com"
};
bool hasDuplicateEmail = emails.Length != emails.Distinct().Count();
if (hasDuplicateEmail)
{
Console.WriteLine("メールアドレスが重複しています");
}
メールアドレスの大文字小文字を区別せずにチェックしたい場合は、次のようにします。
C#bool hasDuplicateEmail = emails.Length != emails
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
数値配列で重複しているIDを抽出する例です。
C#int[] userIds = { 101, 102, 103, 101, 104, 102 };
var duplicateIds = userIds
.GroupBy(id => id)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToArray();
foreach (var id in duplicateIds)
{
Console.WriteLine($"重複ID: {id}");
}
実行結果です。
重複ID: 101
重複ID: 102
文字列配列でも数値配列でも、基本はDistinct()で削除、GroupBy()で抽出、HashSetで高速判定と覚えておくと便利です。
4. LINQを使った重複チェックと重複削除
4-1. Distinctで重複を削除する
Distinct()は、C#で重複削除を行う代表的なLINQメソッドです。
C#var values = new List<int> { 1, 2, 2, 3, 3, 4 };
var uniqueValues = values.Distinct().ToList();
foreach (var value in uniqueValues)
{
Console.WriteLine(value);
}
実行結果です。
1
2
3
4
Distinct()は、同じ値を1つにまとめて返します。Listでも配列でも使用できます。
C#string[] tags = { "C#", "LINQ", "C#", "HashSet" };
var uniqueTags = tags.Distinct().ToArray();
文字列の比較では、大文字小文字を区別する点に注意が必要です。
C#var words = new List<string> { "csharp", "CSharp" };
var uniqueWords = words.Distinct().ToList();
Console.WriteLine(uniqueWords.Count); // 2
大文字小文字を区別したくない場合は、比較方法を指定します。
C#var uniqueWords = words
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
Console.WriteLine(uniqueWords.Count); // 1
4-2. GroupByで重複データを見つける
GroupBy()は、値ごとにデータをグループ化するメソッドです。重複している値を見つけるときに非常に便利です。
C#var values = new List<string> { "A", "B", "A", "C", "B", "D" };
var duplicates = values
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
foreach (var value in duplicates)
{
Console.WriteLine(value);
}
実行結果です。
A
B
GroupBy()を使うと、重複している値だけでなく、その値が何回出現したかも取得できます。
C#var result = values
.GroupBy(x => x)
.Select(g => new
{
Value = g.Key,
Count = g.Count()
})
.ToList();
foreach (var item in result)
{
Console.WriteLine($"{item.Value}: {item.Count}");
}
すべての出現件数を確認したい場合にも役立ちます。
4-3. Countで重複件数を取得する
重複している件数を取得したい場合は、GroupBy()とCount()を組み合わせます。
C#var values = new List<string> { "A", "B", "A", "C", "B", "A" };
var duplicateCounts = values
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => new
{
Value = g.Key,
Count = g.Count()
})
.ToList();
foreach (var item in duplicateCounts)
{
Console.WriteLine($"{item.Value} は {item.Count} 回出現しています");
}
実行結果です。
A は 3 回出現しています
B は 2 回出現しています
重複している値の種類数を知りたい場合は、次のように書けます。
C#int duplicateKindCount = values
.GroupBy(x => x)
.Count(g => g.Count() > 1);
Console.WriteLine(duplicateKindCount); // 2
重複している要素の余分な件数を知りたい場合は、次のように計算できます。
C#int extraDuplicateCount = values.Count - values.Distinct().Count();
Console.WriteLine(extraDuplicateCount); // 3
この例では、元データは6件、一意な値は3件なので、重複によって余分になっている件数は3件です。
4-4. WhereとSelectで重複要素だけを取り出す
Where()とSelect()を使うと、重複している要素だけを柔軟に取り出せます。
C#var values = new List<string> { "A", "B", "A", "C", "B", "D" };
var duplicates = values
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
Where()で条件を指定し、Select()で必要な値だけを取り出しています。
重複グループそのものを扱いたい場合は、Select()せずにそのまま使うこともできます。
C#var duplicateGroups = values
.GroupBy(x => x)
.Where(g => g.Count() > 1);
foreach (var group in duplicateGroups)
{
Console.WriteLine($"値: {group.Key}");
foreach (var item in group)
{
Console.WriteLine($" - {item}");
}
}
オブジェクトの場合は、特定のプロパティを基準にできます。
C#var duplicateUsers = users
.GroupBy(u => u.Id)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
この例では、Idが重複しているユーザーIDだけを取得しています。
4-5. LINQで重複チェックするメリット・デメリット
LINQで重複チェックするメリットは、コードが短く読みやすいことです。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
このように、1行で重複の有無を判定できます。
また、GroupBy()を使えば、重複している値や件数も簡単に取得できます。
C#var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
一方で、LINQは処理内容が隠れやすく、データ件数が多い場合にはパフォーマンスに注意が必要です。
特に次のようなコードは、要素ごとに全体を検索するため効率が悪くなりやすいです。
C#bool hasDuplicate = list.Any(x => list.Count(y => y == x) > 1);
少量データや業務画面の入力チェック程度であればLINQで十分です。しかし、大量データの処理や高速な重複判定が必要な場合は、HashSetを使うとよいでしょう。
5. HashSetを使った高速な重複チェック
5-1. HashSetで重複を判定する基本コード
HashSet<T>は、重複しない値の集合を扱うコレクションです。同じ値を追加しようとすると、追加に失敗します。
この性質を利用すると、高速に重複チェックできます。
C#var list = new List<int> { 1, 2, 3, 2 };
var set = new HashSet<int>();
bool hasDuplicate = false;
foreach (var item in list)
{
if (!set.Add(item))
{
hasDuplicate = true;
break;
}
}
Console.WriteLine(hasDuplicate); // True
HashSet.Add()は、値を追加できた場合にtrueを返します。すでに同じ値が存在する場合はfalseを返します。
つまり、Add()がfalseになった時点で、その値は重複していると判断できます。
5-2. Addの戻り値を使って重複を検出する
HashSetを使った重複チェックは、Any()と組み合わせると簡潔に書けます。
C#var list = new List<string> { "A", "B", "C", "A" };
var set = new HashSet<string>();
bool hasDuplicate = list.Any(x => !set.Add(x));
Console.WriteLine(hasDuplicate); // True
このコードでは、Listを先頭から順に確認し、すでにHashSetに存在する値が出てきた時点でtrueになります。
重複している最初の値を取得したい場合は、次のように書けます。
C#var set = new HashSet<string>();
string? duplicate = list.FirstOrDefault(x => !set.Add(x));
if (duplicate != null)
{
Console.WriteLine($"最初に見つかった重複: {duplicate}");
}
ただし、FirstOrDefault()は値型の場合に既定値が返るため、厳密に判定したい場合は注意が必要です。
C#var numbers = new List<int> { 0, 1, 2, 2 };
var set = new HashSet<int>();
int duplicate = numbers.FirstOrDefault(x => !set.Add(x));
Console.WriteLine(duplicate); // 2
値型で「見つからなかった場合」と「重複値が0だった場合」を区別したい場合は、別途フラグを持つと安全です。
5-3. ToHashSetでListや配列から重複を削除する
ToHashSet()を使うと、Listや配列から重複を削除してHashSetを作成できます。
C#var list = new List<string> { "A", "B", "A", "C" };
var set = list.ToHashSet();
foreach (var item in set)
{
Console.WriteLine(item);
}
HashSetは重複を持たないため、結果的に一意な値だけが残ります。
配列でも同じように使えます。
C#int[] numbers = { 1, 2, 2, 3 };
var uniqueSet = numbers.ToHashSet();
ただし、HashSetは順序を目的としたコレクションではありません。元の順序を保持したい場合は、Distinct()を使ってListや配列に変換するほうがわかりやすいです。
C#var uniqueList = list.Distinct().ToList();
大文字小文字を区別しないHashSetを作りたい場合は、比較方法を指定します。
C#var list = new List<string> { "Apple", "apple", "Banana" };
var set = list.ToHashSet(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(set.Count); // 2
5-4. 大量データではLINQよりHashSetが向いている理由
大量データで重複チェックを行う場合、HashSetが向いている理由は、値の存在確認が高速だからです。
たとえば、Listに対して毎回Contains()やCount()で検索すると、データ件数が増えるほど処理が重くなります。
C#bool hasDuplicate = list.Any(x => list.Count(y => y == x) > 1);
このような書き方は、要素ごとにList全体を調べるため、大量データでは効率が悪くなります。
一方、HashSetはすでに追加済みの値かどうかを効率よく確認できます。
C#var set = new HashSet<int>();
bool hasDuplicate = list.Any(x => !set.Add(x));
さらに、重複が見つかった時点で処理を終了できるため、「重複があるかどうかだけ知りたい」場合に特に有効です。
C#foreach (var item in list)
{
if (!set.Add(item))
{
Console.WriteLine("重複あり");
break;
}
}
大量のCSVデータ、ログデータ、マスタデータなどを処理する場合は、HashSetを使った重複チェックを検討するとよいでしょう。
5-5. HashSetを使うときの注意点
HashSetを使うときは、いくつか注意点があります。
まず、HashSetは同じ値を複数保持できません。そのため、重複件数を数えたい場合には向いていません。
C#var set = new HashSet<string> { "A", "B", "A" };
Console.WriteLine(set.Count); // 2
重複件数を知りたい場合は、GroupBy()を使うほうが適しています。
C#var counts = list
.GroupBy(x => x)
.Select(g => new
{
Value = g.Key,
Count = g.Count()
})
.ToList();
次に、HashSetは順序の保持を目的としたコレクションではありません。元データの順番を重視する場合は、Distinct()を使うか、結果をListに変換する方法を検討します。
C#var uniqueList = list.Distinct().ToList();
また、文字列の比較では大文字小文字の扱いに注意が必要です。
C#var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
set.Add("ABC");
Console.WriteLine(set.Add("abc")); // False
このように比較ルールを指定しないと、期待と異なる重複判定になる場合があります。
6. オブジェクト・クラスの重複チェック
6-1. 特定のプロパティを基準に重複チェックする方法
オブジェクトの重複チェックでは、特定のプロパティを基準にすることが多いです。
たとえば、次のようなUserクラスがあるとします。
C#public class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Email { get; set; } = "";
}
ユーザーIDが重複しているか確認するには、Select()でIDだけを取り出して重複チェックできます。
C#var users = new List<User>
{
new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
new User { Id = 2, Name = "Bob", Email = "bob@example.com" },
new User { Id = 1, Name = "Charlie", Email = "charlie@example.com" }
};
bool hasDuplicateId = users.Select(u => u.Id).Count()
!= users.Select(u => u.Id).Distinct().Count();
Console.WriteLine(hasDuplicateId); // True
より読みやすくするなら、IDのListを一度変数に入れます。
C#var ids = users.Select(u => u.Id).ToList();
bool hasDuplicateId = ids.Count != ids.Distinct().Count();
メールアドレスを基準にする場合は、次のようにします。
C#bool hasDuplicateEmail = users.Select(u => u.Email).Count()
!= users.Select(u => u.Email).Distinct().Count();
6-2. GroupByでIDや名前の重複を判定する
オブジェクトの重複を抽出したい場合は、GroupBy()が便利です。
C#var duplicateIds = users
.GroupBy(u => u.Id)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
foreach (var id in duplicateIds)
{
Console.WriteLine($"重複ID: {id}");
}
重複しているユーザー情報そのものを取得したい場合は、グループから要素を取り出します。
C#var duplicateUsers = users
.GroupBy(u => u.Id)
.Where(g => g.Count() > 1)
.SelectMany(g => g)
.ToList();
foreach (var user in duplicateUsers)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
名前を基準に重複チェックする場合は、GroupBy(u => u.Name)とします。
C#var duplicateNames = users
.GroupBy(u => u.Name)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
複数のプロパティを基準にする場合は、匿名型を使います。
C#var duplicates = users
.GroupBy(u => new { u.Name, u.Email })
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
この例では、NameとEmailの両方が同じ場合に重複と判断します。
6-3. DistinctByでプロパティ単位の重複を削除する
.NET 6以降では、DistinctBy()を使うと特定のプロパティを基準に重複削除できます。
C#var uniqueUsers = users
.DistinctBy(u => u.Id)
.ToList();
このコードでは、Idが同じユーザーは重複とみなされ、最初に登場したユーザーだけが残ります。
たとえば、次のようなデータがあるとします。
C#var users = new List<User>
{
new User { Id = 1, Name = "Alice" },
new User { Id = 2, Name = "Bob" },
new User { Id = 1, Name = "Charlie" }
};
DistinctBy(u => u.Id)を使うと、Id = 1のうち最初のAliceが残り、後から出てきたCharlieは削除されます。
C#var uniqueUsers = users.DistinctBy(u => u.Id).ToList();
foreach (var user in uniqueUsers)
{
Console.WriteLine($"{user.Id}: {user.Name}");
}
実行結果です。
1: Alice
2: Bob
メールアドレスを基準にする場合は、次のようにします。
C#var uniqueUsers = users
.DistinctBy(u => u.Email)
.ToList();
DistinctBy()は、オブジェクトの特定プロパティを基準にした重複削除で非常に便利です。
6-4. IEqualityComparerで独自ルールの重複判定を行う
Distinct()でオブジェクトの重複を判定したい場合は、IEqualityComparer<T>を使って独自の比較ルールを定義できます。
C#public class UserComparer : IEqualityComparer<User>
{
public bool Equals(User? x, User? y)
{
if (x == null || y == null)
{
return false;
}
return x.Id == y.Id;
}
public int GetHashCode(User obj)
{
return obj.Id.GetHashCode();
}
}
この比較クラスでは、Idが同じなら同じユーザーと判断します。
使い方は次のとおりです。
C#var uniqueUsers = users
.Distinct(new UserComparer())
.ToList();
複数プロパティで比較する場合は、Equals()とGetHashCode()の両方に同じ基準を反映します。
C#public class UserNameEmailComparer : IEqualityComparer<User>
{
public bool Equals(User? x, User? y)
{
if (x == null || y == null)
{
return false;
}
return x.Name == y.Name && x.Email == y.Email;
}
public int GetHashCode(User obj)
{
return HashCode.Combine(obj.Name, obj.Email);
}
}
IEqualityComparer<T>を使うと、同じ比較ルールを複数箇所で再利用できるため、実務でも便利です。
6-5. 大文字小文字を区別しない重複チェック
文字列の重複チェックでは、大文字小文字を区別するかどうかが重要です。
C#var emails = new List<string>
{
"USER@example.com",
"user@example.com"
};
bool hasDuplicate = emails.Count != emails.Distinct().Count();
Console.WriteLine(hasDuplicate); // False
この例では、大文字小文字が異なるため、別の文字列として扱われます。
大文字小文字を区別しない場合は、StringComparer.OrdinalIgnoreCaseを使います。
C#bool hasDuplicate = emails.Count != emails
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
Console.WriteLine(hasDuplicate); // True
HashSetでも同じように指定できます。
C#var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
bool hasDuplicate = emails.Any(email => !set.Add(email));
Console.WriteLine(hasDuplicate); // True
オブジェクトのプロパティで大文字小文字を区別しない場合は、GroupBy()で正規化する方法もあります。
C#var duplicateEmails = users
.GroupBy(u => u.Email.Trim().ToLowerInvariant())
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
ただし、メールアドレスやユーザーIDを比較する場合は、前後スペースや全角半角などの扱いも考慮する必要があります。
7. 重複チェックの実践パターン
7-1. 入力値リストに同じ値が含まれていないか確認する
画面やフォームから入力された値に重複がないか確認する例です。
C#var inputCodes = new List<string>
{
"A001",
"A002",
"A003",
"A001"
};
bool hasDuplicate = inputCodes.Count != inputCodes.Distinct().Count();
if (hasDuplicate)
{
Console.WriteLine("入力値に重複があります。");
}
前後スペースを無視したい場合は、Trim()で正規化してからチェックします。
C#var normalizedCodes = inputCodes
.Select(x => x.Trim())
.ToList();
bool hasDuplicate = normalizedCodes.Count != normalizedCodes.Distinct().Count();
大文字小文字も無視したい場合は、StringComparer.OrdinalIgnoreCaseを指定します。
C#bool hasDuplicate = normalizedCodes.Count != normalizedCodes
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
入力値の重複チェックでは、ユーザーが意図せずスペースを入れているケースが多いため、正規化してから判定するのがおすすめです。
7-2. CSVやExcelから読み込んだデータの重複を検出する
CSVやExcelから読み込んだデータでは、商品コードや社員番号などが重複していないか確認することがあります。
C#public class Product
{
public string Code { get; set; } = "";
public string Name { get; set; } = "";
}
商品コードの重複を検出する例です。
C#var products = new List<Product>
{
new Product { Code = "P001", Name = "商品A" },
new Product { Code = "P002", Name = "商品B" },
new Product { Code = "P001", Name = "商品C" }
};
var duplicateCodes = products
.GroupBy(p => p.Code.Trim())
.Where(g => g.Count() > 1)
.Select(g => new
{
Code = g.Key,
Count = g.Count()
})
.ToList();
foreach (var item in duplicateCodes)
{
Console.WriteLine($"商品コード {item.Code} が {item.Count} 件あります。");
}
実行結果です。
商品コード P001 が 2 件あります。
CSVやExcelでは、見た目が同じでも前後スペースが混じっていることがあります。そのため、Trim()で不要なスペースを除去してから重複チェックすると安全です。
7-3. ユーザーIDやメールアドレスの重複をチェックする
ユーザー登録処理では、ユーザーIDやメールアドレスの重複チェックが重要です。
C#var users = new List<User>
{
new User { Id = 1, Name = "Alice", Email = "alice@example.com" },
new User { Id = 2, Name = "Bob", Email = "bob@example.com" },
new User { Id = 3, Name = "Carol", Email = "ALICE@example.com" }
};
メールアドレスの重複を大文字小文字を区別せずにチェックする例です。
C#var emails = users
.Select(u => u.Email.Trim())
.ToList();
bool hasDuplicateEmail = emails.Count != emails
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
if (hasDuplicateEmail)
{
Console.WriteLine("メールアドレスが重複しています。");
}
重複しているメールアドレスを取得する場合は、正規化してGroupBy()します。
C#var duplicateEmails = users
.GroupBy(u => u.Email.Trim().ToLowerInvariant())
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
foreach (var email in duplicateEmails)
{
Console.WriteLine($"重複メールアドレス: {email}");
}
ユーザーIDの重複チェックでは、数値であればそのまま比較できます。
C#bool hasDuplicateId = users.Select(u => u.Id).Count()
!= users.Select(u => u.Id).Distinct().Count();
7-4. DB登録前にC#側で重複をチェックする
データベースに登録する前に、C#側で入力データ内の重複をチェックすることがあります。
C#var requestItems = new List<string>
{
"A001",
"A002",
"A001"
};
var duplicates = requestItems
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
if (duplicates.Any())
{
Console.WriteLine("登録データ内に重複があります。");
}
ただし、C#側の重複チェックだけでDBの整合性を完全に保証することはできません。
たとえば、C#側でチェックした直後に、別の処理が同じデータをDBに登録する可能性があります。そのため、重要な一意制約はDB側でも設定するべきです。
C#側のチェックは、主にユーザーにわかりやすいエラーメッセージを返すために使います。
C#if (duplicates.Any())
{
var message = "重複しているコード: " + string.Join(", ", duplicates);
Console.WriteLine(message);
}
DB側では、ユニーク制約やインデックスを使って最終的な重複登録を防ぎます。
7-5. 重複データをログ出力・エラーメッセージ表示する
実務では、重複があるかどうかだけでなく、「何が重複しているか」をログや画面に表示することが多いです。
C#var codes = new List<string> { "A001", "A002", "A001", "A003", "A002" };
var duplicates = codes
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => new
{
Code = g.Key,
Count = g.Count()
})
.ToList();
if (duplicates.Any())
{
foreach (var item in duplicates)
{
Console.WriteLine($"コード {item.Code} が {item.Count} 件重複しています。");
}
}
実行結果です。
コード A001 が 2 件重複しています。
コード A002 が 2 件重複しています。
エラーメッセージとしてまとめたい場合は、string.Join()を使います。
C#var duplicateCodes = duplicates.Select(x => x.Code);
string message = "重複しているコードがあります: "
+ string.Join(", ", duplicateCodes);
Console.WriteLine(message);
実行結果です。
重複しているコードがあります: A001, A002
ログ出力では、重複値だけでなく件数も出すと調査しやすくなります。
8. 重複チェックのパフォーマンス比較
8-1. LINQ・HashSet・ループ処理の処理速度の違い
C#の重複チェックには複数の方法がありますが、処理速度は書き方によって変わります。
たとえば、次のLINQコードは簡潔です。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
重複の有無を判定するだけなら十分読みやすいコードです。
一方、HashSetを使うと、重複が見つかった時点で処理を止められます。
C#var set = new HashSet<int>();
bool hasDuplicate = list.Any(x => !set.Add(x));
ループで書くと、処理内容が明確になります。
C#var set = new HashSet<int>();
bool hasDuplicate = false;
foreach (var item in list)
{
if (!set.Add(item))
{
hasDuplicate = true;
break;
}
}
避けたいのは、要素ごとに全体を検索するような処理です。
C#bool hasDuplicate = list.Any(x => list.Count(y => y == x) > 1);
この方法は少量データでは問題になりにくいですが、件数が多い場合は処理が重くなりやすいです。
8-2. 少量データに向いている方法
少量データであれば、LINQを使った重複チェックがおすすめです。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
コードが短く、意図もわかりやすいため、保守しやすいです。
重複している値を取得したい場合も、GroupBy()で十分です。
C#var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
業務画面の入力チェック、数十件程度のデータ検証、設定値の確認などであれば、可読性を優先してLINQを使うとよいでしょう。
8-3. 大量データに向いている方法
大量データでは、HashSetを使った重複チェックが向いています。
C#var set = new HashSet<string>();
bool hasDuplicate = list.Any(x => !set.Add(x));
この方法は、重複が見つかった時点で判定を終了できます。
たとえば、100万件のデータの中で早い段階に重複が見つかる場合、すべての件数を最後まで調べる必要がありません。
重複している値をすべて取得したい場合は、GroupBy()を使うか、Dictionaryで件数を数える方法もあります。
C#var counts = new Dictionary<string, int>();
foreach (var item in list)
{
if (!counts.ContainsKey(item))
{
counts[item] = 0;
}
counts[item]++;
}
var duplicates = counts
.Where(x => x.Value > 1)
.Select(x => x.Key)
.ToList();
大量データでは、可読性だけでなく、メモリ使用量や処理時間も考慮する必要があります。
8-4. 可読性と速度のバランスを考えた選び方
実務では、必ずしも最速のコードが最適とは限りません。読みやすく、意図が伝わりやすいコードであることも重要です。
少量データであれば、次のようなLINQで十分です。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
重複値を表示したい場合は、GroupBy()がわかりやすいです。
C#var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
大量データで、重複の有無だけを高速に判定したい場合はHashSetを使います。
C#var set = new HashSet<int>();
bool hasDuplicate = list.Any(x => !set.Add(x));
判断基準としては、次のように考えるとよいでしょう。
C#// 読みやすさ重視
list.Count != list.Distinct().Count();
// 重複値の一覧が必要
list.GroupBy(x => x).Where(g => g.Count() > 1);
// 高速判定が必要
var set = new HashSet<T>();
list.Any(x => !set.Add(x));
8-5. 実務でおすすめの重複チェック方法
実務でおすすめなのは、目的に応じて方法を分けることです。
重複があるかだけ知りたい場合は、少量データならDistinct()、大量データならHashSetです。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
C#var set = new HashSet<string>();
bool hasDuplicate = list.Any(x => !set.Add(x));
重複している値を取得したい場合は、GroupBy()を使います。
C#var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
オブジェクトの特定プロパティで重複削除したい場合は、DistinctBy()が便利です。
C#var uniqueUsers = users.DistinctBy(u => u.Id).ToList();
独自ルールで重複判定したい場合は、IEqualityComparer<T>を使います。
C#var uniqueUsers = users.Distinct(new UserComparer()).ToList();
基本的には、まずLINQで読みやすく書き、処理件数が多くなったり速度が問題になったりしたらHashSetやDictionaryを検討するのが現実的です。
9. C#の重複チェックでよくあるエラーと注意点
9-1. Distinctしてもオブジェクトの重複が削除されない原因
オブジェクトに対してDistinct()を使っても、期待どおりに重複削除されないことがあります。
C#var users = new List<User>
{
new User { Id = 1, Name = "Alice" },
new User { Id = 1, Name = "Alice" }
};
var uniqueUsers = users.Distinct().ToList();
Console.WriteLine(uniqueUsers.Count); // 2
このように、IdやNameが同じでも、別インスタンスとして扱われるため、重複削除されない場合があります。
特定のプロパティを基準に重複削除したい場合は、DistinctBy()を使います。
C#var uniqueUsers = users.DistinctBy(u => u.Id).ToList();
または、GroupBy()で代表データを取得します。
C#var uniqueUsers = users
.GroupBy(u => u.Id)
.Select(g => g.First())
.ToList();
独自の比較ルールを使いたい場合は、IEqualityComparer<T>を実装します。
9-2. nullを含むListや配列の重複チェック
Listや配列にnullが含まれる場合でも、Distinct()やGroupBy()は使えます。
C#var list = new List<string?> { "A", null, "B", null };
bool hasDuplicate = list.Count != list.Distinct().Count();
Console.WriteLine(hasDuplicate); // True
この例では、nullが2回登場しているため重複と判定されます。
ただし、Trim()やToLower()などを使う場合は、nullに注意が必要です。
C#var normalized = list
.Select(x => x.Trim())
.ToList();
このコードは、xがnullの場合に例外が発生します。
安全に処理するには、null条件演算子やnull合体演算子を使います。
C#var normalized = list
.Select(x => x?.Trim() ?? "")
.ToList();
nullと空文字を同じ扱いにするか、別の値として扱うかは、業務ルールに合わせて決める必要があります。
9-3. 文字列の大文字小文字・前後スペースによる判定ミス
文字列の重複チェックでは、大文字小文字や前後スペースによる判定ミスがよくあります。
C#var codes = new List<string>
{
"ABC",
"abc",
" ABC "
};
この3つを同じ値として扱いたい場合は、正規化してから重複チェックします。
C#var normalizedCodes = codes
.Select(x => x.Trim())
.ToList();
bool hasDuplicate = normalizedCodes.Count != normalizedCodes
.Distinct(StringComparer.OrdinalIgnoreCase)
.Count();
Console.WriteLine(hasDuplicate); // True
ToLower()を使って正規化することもできます。
C#var normalizedCodes = codes
.Select(x => x.Trim().ToLowerInvariant())
.ToList();
bool hasDuplicate = normalizedCodes.Count != normalizedCodes.Distinct().Count();
ただし、比較だけが目的であれば、StringComparer.OrdinalIgnoreCaseを使うほうが意図が明確です。
9-4. 重複削除後に元の順序を保持したい場合
重複削除後に元の順序を保持したい場合は、Distinct()を使うのが簡単です。
C#var list = new List<string> { "B", "A", "C", "A", "B" };
var uniqueList = list.Distinct().ToList();
foreach (var item in uniqueList)
{
Console.WriteLine(item);
}
実行結果です。
B
A
C
最初に登場した順番で一意な値が残ります。
HashSetを使う場合、重複判定には便利ですが、順序を意識するなら最終的な結果の扱いに注意が必要です。
順序を保持しながらHashSetで重複削除するなら、次のようにListに追加していきます。
C#var set = new HashSet<string>();
var uniqueList = new List<string>();
foreach (var item in list)
{
if (set.Add(item))
{
uniqueList.Add(item);
}
}
この方法なら、初回登場順を保ったまま重複を削除できます。
9-5. DBの重複チェックとC#側チェックの役割の違い
C#側の重複チェックとDB側の重複チェックは、役割が異なります。
C#側の重複チェックは、主に入力データの検証や、ユーザーにわかりやすいエラーメッセージを返すために使います。
C#if (duplicates.Any())
{
Console.WriteLine("入力データに重複があります。");
}
一方、DB側の重複チェックは、データの整合性を守るために必要です。
たとえば、ユーザーIDやメールアドレスを一意にしたい場合、DBにユニーク制約を設定することで、同じ値の登録を防げます。
C#側でチェックしても、同時実行によって別の処理が同じデータを先に登録する可能性があります。そのため、重要な重複防止はDB側で保証し、C#側では事前チェックやエラー表示を行うのが基本です。
実務では、次のように考えるとよいでしょう。
C#// C#側: 入力データ内の重複をチェック
// DB側: 最終的な一意性を制約で保証
C#側とDB側の両方で重複チェックを行うことで、使いやすさと安全性を両立できます。
10. C#の重複チェックに関するよくある質問
10-1. C#でListに重複があるか確認するには?
Listに重複があるか確認するには、元の件数とDistinct()後の件数を比較します。
C#var list = new List<int> { 1, 2, 3, 2 };
bool hasDuplicate = list.Count != list.Distinct().Count();
Console.WriteLine(hasDuplicate); // True
大量データで高速に確認したい場合は、HashSetを使います。
C#var set = new HashSet<int>();
bool hasDuplicate = list.Any(x => !set.Add(x));
Console.WriteLine(hasDuplicate); // True
少量データならDistinct()、大量データならHashSetを使うのがおすすめです。
10-2. C#で配列の重複を削除するには?
配列の重複を削除するには、Distinct()のあとにToArray()を使います。
C#int[] numbers = { 1, 2, 2, 3, 1 };
int[] uniqueNumbers = numbers.Distinct().ToArray();
foreach (var number in uniqueNumbers)
{
Console.WriteLine(number);
}
実行結果です。
1
2
3
文字列配列でも同じように使えます。
C#string[] names = { "Alice", "Bob", "Alice" };
string[] uniqueNames = names.Distinct().ToArray();
大文字小文字を区別しない場合は、比較方法を指定します。
C#string[] uniqueNames = names
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToArray();
10-3. LINQで重複している値だけ取得するには?
LINQで重複している値だけ取得するには、GroupBy()、Where()、Select()を組み合わせます。
C#var list = new List<string> { "A", "B", "A", "C", "B" };
var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
foreach (var item in duplicates)
{
Console.WriteLine(item);
}
実行結果です。
A
B
重複件数も取得したい場合は、次のようにします。
C#var duplicateDetails = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => new
{
Value = g.Key,
Count = g.Count()
})
.ToList();
ログ出力やエラーメッセージ表示では、この形が便利です。
10-4. HashSetで重複チェックするメリットは?
HashSetで重複チェックするメリットは、重複判定が高速で、重複を見つけた時点で処理を終了できることです。
C#var set = new HashSet<string>();
bool hasDuplicate = list.Any(x => !set.Add(x));
HashSet.Add()は、値を追加できた場合にtrue、すでに存在する場合にfalseを返します。そのため、戻り値を使って重複を検出できます。
ループで書くと、次のようになります。
C#var set = new HashSet<string>();
bool hasDuplicate = false;
foreach (var item in list)
{
if (!set.Add(item))
{
hasDuplicate = true;
break;
}
}
大量データで「重複があるかどうかだけ」を調べたい場合は、HashSetが特に向いています。
10-5. オブジェクトの特定プロパティで重複チェックするには?
オブジェクトの特定プロパティで重複チェックするには、GroupBy()を使います。
C#var duplicateIds = users
.GroupBy(u => u.Id)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
この例では、Idが重複しているユーザーを検出しています。
重複があるかどうかだけを判定する場合は、次のように書けます。
C#bool hasDuplicateId = users.Select(u => u.Id).Count()
!= users.Select(u => u.Id).Distinct().Count();
特定プロパティを基準に重複削除したい場合は、DistinctBy()が便利です。
C#var uniqueUsers = users
.DistinctBy(u => u.Id)
.ToList();
複数プロパティで重複チェックする場合は、匿名型を使います。
C#var duplicates = users
.GroupBy(u => new { u.Name, u.Email })
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
まとめ
C#で重複チェックを行う方法は、目的によって使い分けることが大切です。
Listや配列に重複があるか判定するだけなら、Distinct()とCount()を比較する方法が簡単です。
C#bool hasDuplicate = list.Count != list.Distinct().Count();
重複している値を抽出したい場合は、GroupBy()を使います。
C#var duplicates = list
.GroupBy(x => x)
.Where(g => g.Count() > 1)
.Select(g => g.Key)
.ToList();
重複を削除して一意な値だけ残したい場合は、Distinct()を使います。
C#var uniqueList = list.Distinct().ToList();
大量データで高速に重複の有無を判定したい場合は、HashSetが有効です。
C#var set = new HashSet<int>();
bool hasDuplicate = list.Any(x => !set.Add(x));
オブジェクトの特定プロパティで重複チェックしたい場合は、GroupBy()やDistinctBy()を使います。
C#var uniqueUsers = users.DistinctBy(u => u.Id).ToList();
また、文字列の重複チェックでは、大文字小文字、前後スペース、nullの扱いに注意が必要です。
C#var normalized = list
.Select(x => x?.Trim() ?? "")
.ToList();
C#の重複チェックでは、次のように考えると実装しやすくなります。
C#// 重複があるか判定する
Distinct() + Count()
HashSet.Add()
// 重複している値を抽出する
GroupBy() + Where()
// 重複を削除する
Distinct()
DistinctBy()
ToHashSet()
// 独自ルールで判定する
IEqualityComparer<T>
少量データではLINQを使って読みやすく書き、大量データではHashSetを使って高速に判定するのがおすすめです。目的に応じて適切な方法を選べば、List・配列・オブジェクトの重複チェックを効率よく実装できます。

