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