アプリケーション開発において、2つのリスト(配列やコレクション)の内容が同一であるかを検証したい場面は頻繁に発生します。例えば、データ移行前後の整合性チェックや、単体テストにおける期待値と実測値の比較などです。
C#のLINQに含まれるSequenceEqualメソッドを使用することで、2つのシーケンスが「同じ要素を」「同じ順序で」持っているかを判定できます。ただし、扱うデータ型(値型か参照型か)によって判定の挙動が異なるため、正しい理解が必要です。
ここでは、基本データ型の比較と、カスタムクラスを用いたオブジェクトの比較における注意点、そしてモダンなC#での解決策について解説します。
SequenceEqualメソッドの基本的な挙動
SequenceEqualは、2つのコレクションを先頭から順に比較し、以下の条件をすべて満たす場合にtrueを返します。
- 要素数が等しいこと。
- 対応するインデックスの要素同士が等しいこと(型ごとの等価判定による)。
実践的なコード例:数値データの比較
以下のコードは、システム設定として保持している「許可されたIDリスト」と、外部からインポートしたIDリストが一致しているかを確認する例です。intのような値型の場合、値そのものが比較されます。
using System;
using System.Linq;
namespace DataValidation
{
class Program
{
static void Main()
{
// 期待するIDのシーケンス
var authorizedIds = new[] { 101, 102, 103, 104, 105 };
// 検証対象のシーケンス(正しい順序と値を持つ)
var importedIds = new[] { 101, 102, 103, 104, 105 };
// 検証対象のシーケンス(順序が異なる)
var shuffledIds = new[] { 105, 104, 103, 102, 101 };
// 1. 完全一致する場合
bool isExactMatch = authorizedIds.SequenceEqual(importedIds);
Console.WriteLine($"完全一致: {isExactMatch}");
// 2. 要素は同じだが順序が異なる場合
// SequenceEqualは順序も厳密にチェックするため false になります
bool isOrderMatch = authorizedIds.SequenceEqual(shuffledIds);
Console.WriteLine($"順序違い: {isOrderMatch}");
}
}
}
実行結果
完全一致: True
順序違い: False
参照型(クラス)比較時の注意点
SequenceEqualを使用する際、最も注意が必要なのはリストの中身が自作クラス(参照型)の場合です。
通常のクラス(class)では、等価性の判定はデフォルトで「参照の比較(メモリアドレスが同じか)」となります。そのため、プロパティの値がすべて同じであっても、インスタンス(newしたもの)が異なれば「等しくない」と判定されます。
解決策:record型による値ベースの等価性
C# 9.0で導入されたrecord型を使用すると、この問題をスマートに解決できます。record型は、自動的に値ベースの等価性(プロパティの値がすべて同じなら等しいとみなす)を提供するため、SequenceEqualでの比較が意図通りに機能します。
以下のコードは、商品データのリストを比較する例です。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ProductComparison
{
// C# 9.0以降の record 型を使用
// これにより、参照ではなく「全プロパティの値」で等価判定が行われます。
public record Product(string Code, string Name, decimal Price);
class Program
{
static void Main()
{
// マスタデータ
var masterData = new List<Product>
{
new Product("P001", "ワイヤレスイヤホン", 12000m),
new Product("P002", "スマートウォッチ", 25000m)
};
// 比較対象データ(マスタとは別のインスタンスを作成)
// プロパティの値はマスタと完全に同じ
var comparisonData = new List<Product>
{
new Product("P001", "ワイヤレスイヤホン", 12000m),
new Product("P002", "スマートウォッチ", 25000m)
};
// SequenceEqualによる比較
// Productが class だった場合、ここは False になります。
// Productが record なので、値の一致を見て True になります。
bool areListsEqual = masterData.SequenceEqual(comparisonData);
Console.WriteLine($"リストの内容は一致していますか?: {areListsEqual}");
}
}
}
実行結果
リストの内容は一致していますか?: True
技術的なポイント
1. IEqualityComparerの使用
もし、既存の設計上の制約でrecord型を使用できず、かつクラス側でEqualsメソッドをオーバーライドできない場合は、SequenceEqualの第2引数にIEqualityComparer<T>の実装クラスを渡すことで、比較ロジックを外部から注入できます。
2. 要素の順序を無視したい場合
「順序は問わず、集合として同じ要素を持っているか」を知りたい場合は、SequenceEqualは適していません。その場合は、事前にOrderByでソートしてからSequenceEqualを行うか、HashSetを利用して集合比較(SetEquals)を行うアプローチが有効です。
まとめ
SequenceEqualは、リストの順序と内容の完全一致を検証するためのメソッドです。数値や文字列などの基本的な型では直感的に動作しますが、自作クラスのリストを比較する場合は「参照の比較」か「値の比較」かを意識する必要があります。現代的なC#開発においては、データの保持を目的とする型にrecordを採用することで、この等価判定の問題をシンプルに解決できます。
