【C#】LINQのSequenceEqualで2つのリストが完全に一致するか判定する

アプリケーション開発において、2つのリスト(配列やコレクション)の内容が同一であるかを検証したい場面は頻繁に発生します。例えば、データ移行前後の整合性チェックや、単体テストにおける期待値と実測値の比較などです。

C#のLINQに含まれるSequenceEqualメソッドを使用することで、2つのシーケンスが「同じ要素を」「同じ順序で」持っているかを判定できます。ただし、扱うデータ型(値型か参照型か)によって判定の挙動が異なるため、正しい理解が必要です。

ここでは、基本データ型の比較と、カスタムクラスを用いたオブジェクトの比較における注意点、そしてモダンなC#での解決策について解説します。


目次

SequenceEqualメソッドの基本的な挙動

SequenceEqualは、2つのコレクションを先頭から順に比較し、以下の条件をすべて満たす場合にtrueを返します。

  1. 要素数が等しいこと。
  2. 対応するインデックスの要素同士が等しいこと(型ごとの等価判定による)。

実践的なコード例:数値データの比較

以下のコードは、システム設定として保持している「許可された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を採用することで、この等価判定の問題をシンプルに解決できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次