.NET Framework 4より、Tuple<T1, T2>Tuple<T1, T2, T3>などのタプル(組、Tuple)が導入されました。 これに伴い、構造を比較するためのインターフェイスIStructuralComparableおよびIStructuralEquatableが導入され、タプル型やArrayクラスで実装されています。

IStructuralEquatable

IStructuralEquatable(System.Collections名前空間)は構造上の等価性を比較するメソッドを提供するインターフェイスです。 例えば、配列を一つの構造体ととらえたとき、同一の要素数(長さ)の配列は同じ構造を持っていると考えることが出来、構造内の個々の要素が等しければ構造的に等しいと定義することが出来ます。

構造の比較を行うIStructuralEquatable.Equalsメソッドは、比較対象となるオブジェクトと、等価性の比較を行うためのIEqualityComparerの二つを引数に取ります。 .NET Framework 4以降では、ArrayクラスはIStructuralEquatableインターフェイスを実装しているため、構造の比較が行えるようになっています。 ただし、IStructuralEquatableのメソッドは明示的な実装となっているため、IStructuralEquatableにキャストしない限りメソッドを呼び出すことは出来ません。 次の例は、配列同士をIStructuralEquatable.Equalsメソッドで比較する例です。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    int[] arr1 = new int[] {0, 1, 2};
    int[] arr2 = new int[] {0, 1, 2};
    int[] arr3 = new int[] {0, 1, 3};
    int[] arr4 = new int[] {0, 1, 2, 3};

    Console.WriteLine("Array.Equals arr1, arr2 : {0}", arr1.Equals(arr2));
    Console.WriteLine("Array.Equals arr1, arr3 : {0}", arr1.Equals(arr3));
    Console.WriteLine("Array.Equals arr1, arr4 : {0}", arr1.Equals(arr4));

    IStructuralEquatable st1 = arr1;

    Console.WriteLine("IStructuralEquatable.Equals arr1, arr2 : {0}",
                      st1.Equals(arr2, StructuralComparisons.StructuralEqualityComparer));
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr3 : {0}",
                      st1.Equals(arr3, StructuralComparisons.StructuralEqualityComparer));
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr4 : {0}",
                      st1.Equals(arr4, StructuralComparisons.StructuralEqualityComparer));
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim arr1() As Integer = New Integer() {0, 1, 2}
    Dim arr2() As Integer = New Integer() {0, 1, 2}
    Dim arr3() As Integer = New Integer() {0, 1, 3}
    Dim arr4() As Integer = New Integer() {0, 1, 2, 3}

    Console.WriteLine("Array.Equals arr1, arr2 : {0}", arr1.Equals(arr2))
    Console.WriteLine("Array.Equals arr1, arr3 : {0}", arr1.Equals(arr3))
    Console.WriteLine("Array.Equals arr1, arr4 : {0}", arr1.Equals(arr4))

    Dim st1 As IStructuralEquatable = arr1

    Console.WriteLine("IStructuralEquatable.Equals arr1, arr2 : {0}",
                      st1.Equals(arr2, StructuralComparisons.StructuralEqualityComparer))
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr3 : {0}",
                      st1.Equals(arr3, StructuralComparisons.StructuralEqualityComparer))
    Console.WriteLine("IStructuralEquatable.Equals arr1, arr4 : {0}",
                      st1.Equals(arr4, StructuralComparisons.StructuralEqualityComparer))
  End Sub
End Class
実行結果
Array.Equals arr1, arr2 : False
Array.Equals arr1, arr3 : False
Array.Equals arr1, arr4 : False
IStructuralEquatable.Equals arr1, arr2 : True
IStructuralEquatable.Equals arr1, arr3 : False
IStructuralEquatable.Equals arr1, arr4 : False

Array.Equalsメソッドはオーバーライドされていないためオブジェクト同士の参照の比較しか行いませんが、Array.IStructuralEquatable.Equalsメソッドでは構造の比較を行うようになっているため、上記のような結果となります。 要素数とすべての要素が等しければ構造上等しいとなるのに対し、要素数またはいずれかの要素が異なれば構造上異なると判断されます。

Arrayとは異なり、TupleのEqualsメソッドは構造の比較を行うようになっているため、必ずしもIStructuralEquatableにキャストする必要はありません。

using System;

class Sample {
  static void Main()
  {
    Tuple<int, int, int> t1 = Tuple.Create(0, 1, 2);
    Tuple<int, int, int> t2 = Tuple.Create(0, 1, 2);
    Tuple<int, int, int> t3 = Tuple.Create(0, 1, 3);
    Tuple<int, int, int, int> t4 = Tuple.Create(0, 1, 2, 3);
    Tuple<int, double, int> t5 = Tuple.Create(0, 1.0, 2);

    Console.WriteLine("Tuple.Equals t1, t2 : {0}", t1.Equals(t2));
    Console.WriteLine("Tuple.Equals t1, t3 : {0}", t1.Equals(t3));
    Console.WriteLine("Tuple.Equals t1, t4 : {0}", t1.Equals(t4));
    Console.WriteLine("Tuple.Equals t1, t5 : {0}", t1.Equals(t5));
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim t1 As Tuple(Of Integer, Integer, Integer) = Tuple.Create(0, 1, 2)
    Dim t2 As Tuple(Of Integer, Integer, Integer) = Tuple.Create(0, 1, 2)
    Dim t3 As Tuple(Of Integer, Integer, Integer) = Tuple.Create(0, 1, 3)
    Dim t4 As Tuple(Of Integer, Integer, Integer, Integer) = Tuple.Create(0, 1, 2, 3)
    Dim t5 As Tuple(Of Integer, Double, Integer) = Tuple.Create(0, 1.0, 2)

    Console.WriteLine("Tuple.Equals t1, t2 : {0}", t1.Equals(t2))
    Console.WriteLine("Tuple.Equals t1, t3 : {0}", t1.Equals(t3))
    Console.WriteLine("Tuple.Equals t1, t4 : {0}", t1.Equals(t4))
    Console.WriteLine("Tuple.Equals t1, t5 : {0}", t1.Equals(t5))
  End Sub
End Class
実行結果
Tuple.Equals t1, t2 : True
Tuple.Equals t1, t3 : False
Tuple.Equals t1, t4 : False
Tuple.Equals t1, t5 : False

なお、System.Collections名前空間やSystem.Collections.Generic名前空間にあるList<T>などのコレクションクラスにはIStructuralEquatableが実装されていないため、この様な比較を行うことは出来ません。 要素数を調べ、個々の要素に対して等しいかどうか比較を行う必要があります。

IStructuralComparable

IStructuralComparable(System.Collections名前空間)はIStructuralEquatableと同様に構造上の大小関係を比較するメソッドを提供するインターフェイスです。

構造の比較を行うIStructuralComparable.CompareToメソッドは、比較対象となるオブジェクトと、大小関係の比較を行うためのIComparerの二つを引数に取ります。

StructuralComparisons.StructuralComparerプロパティを参照すると、構造の比較を行うIComparerを取得することが出来ます。 次の例では、ジャグ配列を作成し、Array.Sortメソッドでソートしています。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    int[][] arr = new int[][] {
      new int[] {1, 1, 0},
      new int[] {0, 0, 0},
      new int[] {0, 1, 1},
      new int[] {0, 1, 0},
      new int[] {0, 0, 1},
      new int[] {1, 0, 0},
      new int[] {1, 1, 1},
      new int[] {1, 0, 1},
    };

    Console.WriteLine("[before sort]");

    Print(arr);

    Console.WriteLine("[after sort]");

    Array.Sort(arr, StructuralComparisons.StructuralComparer);
    // StructuralComparisons.StructuralComparerを指定しない場合、InvalidOperationExceptionがスローされる
    // Array.Sort(arr);

    Print(arr);
  }

  static void Print(int[][] arr)
  {
    foreach (int[] a in arr) {
      Console.WriteLine("{{{0}}}", string.Join(", ", a));
    }
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim arr()() As Integer = New Integer()() { _
      New Integer() {1, 1, 0}, _
      New Integer() {0, 0, 0}, _
      New Integer() {0, 1, 1}, _
      New Integer() {0, 1, 0}, _
      New Integer() {0, 0, 1}, _
      New Integer() {1, 0, 0}, _
      New Integer() {1, 1, 1}, _
      New Integer() {1, 0, 1} _
    }

    Console.WriteLine("[before sort]")

    Print(arr)

    Console.WriteLine("[after sort]")

    Array.Sort(arr, StructuralComparisons.StructuralComparer)
    ' StructuralComparisons.StructuralComparerを指定しない場合、InvalidOperationExceptionがスローされる
    ' Array.Sort(arr);

    Print(arr)
  End Sub

  Shared Sub Print(ByVal arr()() As Integer)
    For Each a As Integer() In arr
      Console.WriteLine("{{{0}}}", string.Join(", ", a))
    Next
  End Sub
End Class
実行結果
[before sort]
{1, 1, 0}
{0, 0, 0}
{0, 1, 1}
{0, 1, 0}
{0, 0, 1}
{1, 0, 0}
{1, 1, 1}
{1, 0, 1}
[after sort]
{0, 0, 0}
{0, 0, 1}
{0, 1, 0}
{0, 1, 1}
{1, 0, 0}
{1, 0, 1}
{1, 1, 0}
{1, 1, 1}

なお、StructuralComparisons.StructuralComparerを使ってソート出来るのは、ジャグ配列内の各配列の長さが等しい場合に限られる点に注意が必要です。 各配列の長さが異なるジャグ配列のソートについてはジャグ配列・多次元配列のソートで解説しています。

一方、Arrayとは異なり、TupleのCompareToメソッドは構造の比較を行うようになっているため、必ずしもStructuralComparisons.StructuralComparerを指定する必要はありません。

using System;
using System.Collections;

class Sample {
  static void Main()
  {
    Tuple<int, string>[] arr = new Tuple<int, string>[] {
      Tuple.Create(1, "Eve"),
      Tuple.Create(4, "Dave"),
      Tuple.Create(2, "Alice"),
      Tuple.Create(0, "Charlie"),
      Tuple.Create(3, "Bob"),
    };

    Console.WriteLine("[before sort]");

    Print(arr);

    Console.WriteLine("[after sort]");

    Array.Sort(arr);

    Print(arr);

    Console.WriteLine("[after sort (StructuralComparisons.StructuralComparer)]");

    Array.Sort(arr, StructuralComparisons.StructuralComparer);

    Print(arr);
  }

  static void Print(Tuple<int, string>[] arr)
  {
    foreach (Tuple<int, string> e in arr) {
      Console.WriteLine(e);
    }
  }
}
Imports System
Imports System.Collections

Class Sample
  Shared Sub Main()
    Dim arr() As Tuple(Of Integer, String) = New Tuple(Of Integer, String)() { _
      Tuple.Create(1, "Eve"), _
      Tuple.Create(4, "Dave"), _
      Tuple.Create(2, "Alice"), _
      Tuple.Create(0, "Charlie"), _
      Tuple.Create(3, "Bob") _
    }

    Console.WriteLine("[before sort]")

    Print(arr)

    Console.WriteLine("[after sort]")

    Array.Sort(arr)

    Print(arr)

    Console.WriteLine("[after sort (StructuralComparisons.StructuralComparer)]")

    Array.Sort(arr, StructuralComparisons.StructuralComparer)

    Print(arr)
  End Sub

  Shared Sub Print(ByVal arr() As Tuple(Of Integer, String))
    For Each e As Tuple(Of Integer, String) In arr
      Console.WriteLine(e)
    Next
  End Sub
End Class
実行結果
[before sort]
(1, Eve)
(4, Dave)
(2, Alice)
(0, Charlie)
(3, Bob)
[after sort]
(0, Charlie)
(1, Eve)
(2, Alice)
(3, Bob)
(4, Dave)
[after sort (StructuralComparisons.StructuralComparer)]
(0, Charlie)
(1, Eve)
(2, Alice)
(3, Bob)
(4, Dave)