IEquatable<T>インターフェースで実装されるEqualsメソッドは、==, !=(<>) といった等価・不等価演算子の役割と似たものですが、両者は全く独立したものであり、このインターフェイスを実装したからといって等価演算子を用いた比較が出来るようになるわけではありません。 等価演算子を用いて比較するには、別途演算子をオーバーロードしなければなりません。 また逆に、等価演算子・不等価演算子をオーバーロードしたからといってList.Containsメソッドで要素の有無をチェックしたり、Dictionaryのキーとして使えるようになるというわけでもありません。 List.Containsメソッドで要素の有無を調べられるようにするには少なくともEqualsメソッド、Dictionaryのキーとして使用するにはEqualsメソッドとGetHashCodeメソッドの両方が適切に実装されている必要があります。

しかし、独自のクラスを構築する際、IEquatable<T>等による比較と等価演算子による比較の両方が出来るようにしたい場合も当然出てきます。

以下の例では、IEquatable<T>の実装と等価・不等価演算子のオーバーロードを行った構造体を作成しています。 この構造体では、等価演算子で等価性の定義と比較処理を実装し、IEquatable<T>.Equalsメソッドではオーバーロードした等価演算子を使って結果を返すようにしています。

using System;
using System.Collections.Generic;

struct Point : IEquatable<Point> {
  int x, y;

  public Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }

  // IEquatable<Point>.Equalsの実装
  public bool Equals(Point other)
  {
    // 実際の比較はオーバーロードした等価演算子で行う
    return this == other;
  }

  // Object.Equalsをオーバーライド
  public override bool Equals(object obj)
  {
    if (obj is Point)
      // 型がPointならオーバーロードした等価演算子を使って比較
      return this == (Point)obj;
    else
      // 異なる型の場合はfalse
      return false;
  }

  // 等価演算子 == のオーバーロード
  public static bool operator == (Point l, Point r)
  {
    // フィールドxとyが等しければ二つのPointは等しいものとする
    return l.x == r.x && l.y == r.y;
  }

  // 不等価演算子 == のオーバーロード
  public static bool operator != (Point l, Point r)
  {
    // 等価演算子 == の結果を否定した結果を返す
    return !(l == r);
    // 当然、次のようにしても可
    //return l.x != r.x || l.y != r.y;
  }

  public override int GetHashCode()
  {
    return x.GetHashCode() ^ y.GetHashCode();
  }

  public override string ToString()
  {
    return string.Format("({0}, {1})", x, y);
  }
}

class Sample {
  static void Main()
  {
    List<Point> list = new List<Point>(new Point[] {
      new Point(1, 0),
      new Point(0, 1),
      new Point(0, 0),
    });

    Point zero = new Point(0, 0);

    // オーバーロードした等価・不等価演算子のテスト
    Console.WriteLine("{0} {2} {1}", zero, list[0], zero == list[0] ? "==" : "!=");
    Console.WriteLine("{0} {2} {1}", zero, list[1], zero == list[1] ? "==" : "!=");
    Console.WriteLine("{0} {2} {1}", zero, list[2], zero != list[2] ? "!=" : "==");
    Console.WriteLine();

    // IEquatable<Point>.EqualsとオーバーライドしたObject.Equalsのテスト
    Console.WriteLine("{0} {2} {1}", zero, list[0], zero.Equals(list[0]) ? "Equals" : "Not Equals");
    Console.WriteLine("{0} {2} {1}", zero, list[2], zero.Equals(list[2]) ? "Equals" : "Not Equals");
    Console.WriteLine();
    Console.WriteLine("{0} {2} {1}", zero, 0, zero.Equals(0) ? "Equals" : "Not Equals");
    Console.WriteLine("{0} {2} {1}", zero, "null", zero.Equals(null) ? "Equals" : "Not Equals");
    Console.WriteLine();

    Console.WriteLine("Contains {0} = {1}", zero, list.Contains(zero));
    Console.WriteLine("Contains {0} = {1}", new Point(1, 1), list.Contains(new Point(1, 1)));
  }
}
実行結果
(0, 0) != (1, 0)
(0, 0) != (0, 1)
(0, 0) == (0, 0)

(0, 0) Not Equals (1, 0)
(0, 0) Equals (0, 0)

(0, 0) Not Equals 0
(0, 0) Not Equals null

Contains (0, 0) = True
Contains (1, 1) = False
Imports System
Imports System.Collections.Generic

Structure Point
  Implements IEquatable(Of Point)

  Dim x As Integer
  Dim y As Integer

  Public Sub New(ByVal x As Integer, ByVal y As Integer)
    MyClass.x = x
    MyClass.y = y
  End Sub

  ' IEquatable(Of Point).Equalsの実装
  Public Function Equals(ByVal other As Point) As Boolean Implements IEquatable(Of Point).Equals
    ' 実際の比較はオーバーロードした等価演算子で行う
    Return Me = other
  End Function

  ' Object.Equalsをオーバーライド
  Public Overrides Function Equals(ByVal obj As Object) As Boolean
    If TypeOf obj Is Point Then
      ' 型がPointならオーバーロードした等価演算子を使って比較
      Return Me = CType(obj, Point)
    Else
      ' 異なる型の場合はFalse
      Return False
    End If
  End Function

  ' 等価演算子 = のオーバーロード
  Public Shared Operator = (ByVal l As Point, ByVal r As Point) As Boolean
    ' フィールドxとyが等しければ二つのPointは等しいものとする
    Return l.x = r.x AndAlso l.y = r.y
  End Operator

  ' 等価演算子 <> のオーバーロード
  Public Shared Operator <> (ByVal l As Point, ByVal r As Point) As Boolean
    ' 等価演算子 = の結果を否定した結果を返す
    Return Not (l = r)
    ' 当然、次のようにしても可
    'Return l.x <> r.x OrElse l.y <> r.y
  End Operator

  Public Overrides Function GetHashCode() As Integer
    Return x.GetHashCode() Xor y.GetHashCode()
  End Function

  Public Overrides Function ToString() As String
    Return String.Format("({0}, {1})", x, y)
  End Function
End Structure

Class Sample
  Shared Sub Main()
    Dim list As New List(Of Point)(New Point() { _
      New Point(1, 0), _
      New Point(0, 1), _
      New Point(0, 0) _
    })

    Dim zero As New Point(0, 0)

    ' オーバーロードした等価・不等価演算子のテスト
    Console.WriteLine("{0} {2} {1}", zero, list(0), If(zero = list(0), "=", "<>"))
    Console.WriteLine("{0} {2} {1}", zero, list(1), If(zero = list(1), "=", "<>"))
    Console.WriteLine("{0} {2} {1}", zero, list(2), If(zero <> list(2), "<>", "="))
    Console.WriteLine()

    ' IEquatable<Point>.EqualsとオーバーライドしたObject.Equalsのテスト
    Console.WriteLine("{0} {2} {1}", zero, list(0), If(zero.Equals(list(0)), "Equals", "Not Equals"))
    Console.WriteLine("{0} {2} {1}", zero, list(2), If(zero.Equals(list(2)), "Equals", "Not Equals"))
    Console.WriteLine()
    Console.WriteLine("{0} {2} {1}", zero, 0, If(zero.Equals(0), "Equals", "Not Equals"))
    Console.WriteLine("{0} {2} {1}", zero, "Nothing", If(zero.Equals(Nothing), "Equals", "Not Equals"))
    Console.WriteLine()

    Console.WriteLine("Contains {0} = {1}", zero, list.Contains(zero))
    Console.WriteLine("Contains {0} = {1}", New Point(1, 1), list.Contains(New Point(1, 1)))
  End Sub
End Class
実行結果
(0, 0) <> (1, 0)
(0, 0) <> (0, 1)
(0, 0) = (0, 0)

(0, 0) Not Equals (1, 0)
(0, 0) Equals (0, 0)

(0, 0) Not Equals 0
(0, 0) Equals Nothing

Contains (0, 0) = True
Contains (1, 1) = False

なお、この例では構造体での等価演算子のオーバーロードを行いましたが、参照型でのオーバーロードを行う場合は x == y が参照の等価性(つまり同一のインスタンス)を表すのか、インスタンスの持つ値の等価性を表すのか曖昧になる場合がある点に注意が必要です。

もう一つ別の例を挙げます。 次の例において、Point構造体では等価・不等価演算子のオーバーロードのみを行い、キーとしての比較で必要となる処理はEqualityComparer<T>クラスを継承した別のクラスPointComparerで実装しています。

using System;
using System.Collections.Generic;

struct Point {
  int x, y;

  public Point(int x, int y)
  {
    this.x = x;
    this.y = y;
  }

  // 等価演算子 == のオーバーロード
  public static bool operator == (Point l, Point r)
  {
    // フィールドxとyが等しければ二つのPointは等しいものとする
    return l.x == r.x && l.y == r.y;
  }

  // 不等価演算子 == のオーバーロード
  public static bool operator != (Point l, Point r)
  {
    // 等価演算子 == の結果を否定した結果を返す
    return !(l == r);
  }

  public override int GetHashCode()
  {
    return x.GetHashCode() ^ y.GetHashCode();
  }

  public override string ToString()
  {
    return string.Format("({0}, {1})", x, y);
  }
}

// Point構造体のためのEqualityComparer
class PointComparer : EqualityComparer<Point> {
  public override bool Equals(Point a, Point b)
  {
    // オーバーロードした等価演算子を使って比較
    return a == b;
  }

  public override int GetHashCode(Point obj)
  {
    return obj.GetHashCode();
  }
}

class Sample {
  static void Main()
  {
    // ある座標とその座標の名前のディクショナリ
    Dictionary<Point, string> dict = new Dictionary<Point, string>(new PointComparer());

    Point o = new Point(0, 0);
    Point a = new Point(1, 0);
    Point b = new Point(0, 1);
    Point c = new Point(1, 1);

    dict[o] = "O";
    dict[a] = "A";
    dict[b] = "B";

    Console.WriteLine("ContainsKey {0} = {1}", b, dict.ContainsKey(b));
    Console.WriteLine("ContainsKey {0} = {1}", c, dict.ContainsKey(c));

    foreach (KeyValuePair<Point, string> pair in dict) {
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value);
    }
  }
}
Imports System
Imports System.Collections.Generic

Structure Point
  Dim x As Integer
  Dim y As Integer

  Public Sub New(ByVal x As Integer, ByVal y As Integer)
    MyClass.x = x
    MyClass.y = y
  End Sub

  ' 等価演算子 = のオーバーロード
  Public Shared Operator = (ByVal l As Point, ByVal r As Point) As Boolean
    ' フィールドxとyが等しければ二つのPointは等しいものとする
    Return l.x = r.x AndAlso l.y = r.y
  End Operator

  ' 等価演算子 <> のオーバーロード
  Public Shared Operator <> (ByVal l As Point, ByVal r As Point) As Boolean
    ' 等価演算子 = の結果を否定した結果を返す
    Return Not (l = r)
  End Operator

  Public Overrides Function GetHashCode() As Integer
    Return x.GetHashCode() Xor y.GetHashCode()
  End Function

  Public Overrides Function ToString() As String
    Return String.Format("({0}, {1})", x, y)
  End Function
End Structure

' Point構造体のためのEqualityComparer
Class PointComparer
  Inherits EqualityComparer(Of Point)

  Public Overrides Function Equals(ByVal a As Point, ByVal b As Point) As Boolean
    ' オーバーロードした等価演算子を使って比較
    Return a = b
  End Function

  Public Overrides Function GetHashCode(ByVal obj As Point) As Integer
    Return obj.GetHashCode()
  End Function
End Class

Class Sample
  Shared Sub Main()
    ' ある座標とその座標の名前のディクショナリ
    Dim dict As New Dictionary(Of Point, String)(New PointComparer())

    Dim o As New Point(0, 0)
    Dim a As New Point(1, 0)
    Dim b As New Point(0, 1)
    Dim c As New Point(1, 1)

    dict(o) = "O"
    dict(a) = "A"
    dict(b) = "B"

    Console.WriteLine("ContainsKey {0} = {1}", b, dict.ContainsKey(b))
    Console.WriteLine("ContainsKey {0} = {1}", c, dict.ContainsKey(c))

    For Each pair As KeyValuePair(Of Point, String) In dict
      Console.WriteLine("{0} => {1}", pair.Key, pair.Value)
    Next
  End Sub
End Class
実行結果
ContainsKey (0, 1) = True
ContainsKey (1, 1) = False
(0, 0) => O
(1, 0) => A
(0, 1) => B