IComparable<T>インターフェースやIComparer<T>インターフェイスで実装されるCompareToメソッド・Compareメソッドは、<
, >
, <=
, >=
といった比較演算子の役割と似たものですが、両者は全く独立したものであり、このインターフェイスを実装したからといって比較演算子を用いた比較が出来るようになるわけではありません。 比較演算子を用いて比較するには、別途演算子をオーバーロードしなければなりません。 また逆に、比較演算子をオーバーロードしたからといってソートが出来るようになるというわけでもありません。 ソートするには型がIComparable<T>やIComparableなどのインターフェイスを実装している必要があります。
しかし、独自のクラスを構築する際、IComparable<T>等による比較(さらにソート)と比較演算子による比較の両方が出来るようにしたい場合も当然出てきます。
以下の例では、IComparable<T>の実装と比較演算子のオーバーロードを行ったクラスを作成しています。 このクラスでは、まずIComparable<T>で大小関係の定義と比較処理を実装し、オーバーロードした比較演算子ではIComparable<T>.CompareToを呼び出してその結果を使うようにしています。
using System;
using System.Collections.Generic;
class Account : IComparable<Account> {
int id;
string name;
public Account(int id, string name)
{
this.id = id;
this.name = name;
}
// IComparable<Account>.CompareToの実装
public int CompareTo(Account other)
{
// 引数がnullの場合はArgumentNullExceptionをスローする
if (other == null) throw new ArgumentNullException("other");
// フィールドidの値の大小関係をCompareToの戻り値とする
return this.id - other.id;
}
// 比較演算子 < のオーバーロード
public static bool operator < (Account x, Account y)
{
// 被比較対象がnullの場合はArgumentNullExceptionをスローする
if (x == null) throw new ArgumentNullException("x");
// IComparable<Account>.CompareToの実装を使って比較した結果を返す
return x.CompareTo(y) < 0;
}
// 比較演算子 <= のオーバーロード
public static bool operator <= (Account x, Account y)
{
// 被比較対象がnullの場合はArgumentNullExceptionをスローする
if (x == null) throw new ArgumentNullException("x");
// IComparable<Account>.CompareToの実装を使って比較した結果を返す
return x.CompareTo(y) <= 0;
}
// 比較演算子 > のオーバーロード
public static bool operator > (Account x, Account y)
{
// 左辺と右辺を入れ替え比較演算子 < を使って結果を返す
return y < x;
// 比較演算子 <= の結果を逆転して返すやり方でも可
//return !(x <= y);
}
// 比較演算子 >= のオーバーロード
public static bool operator >= (Account x, Account y)
{
// 左辺と右辺を入れ替え比較演算子 <= を使って結果を返す
return y <= x;
// 比較演算子 < の結果を逆転して返すやり方でも可
//return !(x < y);
}
public override string ToString()
{
return string.Format("{0}:{1}", id, name);
}
}
class Sample {
static void Main()
{
// オーバーロードした比較演算子のテスト
Account x = new Account(0, "Charlie");
Account y = new Account(1, "Eve");
Account z = new Account(1, "Alice");
// 異なる値の場合
Console.WriteLine("{0} {2} {1}", x, y, x < y ? "<" : ">=");
Console.WriteLine("{0} {2} {1}", x, y, x <= y ? "<=" : ">");
Console.WriteLine("{0} {2} {1}", x, y, x > y ? ">" : "<=");
Console.WriteLine("{0} {2} {1}", x, y, x >= y ? ">=" : "<");
Console.WriteLine();
// 同値の場合
Console.WriteLine("{0} {2} {1}", y, z, y < z ? "<" : ">=");
Console.WriteLine("{0} {2} {1}", y, z, y <= z ? "<=" : ">");
Console.WriteLine("{0} {2} {1}", y, z, y > z ? ">" : "<=");
Console.WriteLine("{0} {2} {1}", y, z, y >= z ? ">=" : "<");
}
}
Imports System
Imports System.Collections.Generic
Class Account
Implements IComparable(Of Account)
Dim id As Integer
Dim name As String
Public Sub New(ByVal id As Integer, ByVal name As String)
MyClass.id = id
MyClass.name = name
End Sub
' IComparable(Of Account).CompareToの実装
Public Function CompareTo(ByVal other As Account) As Integer Implements IComparable(Of Account).CompareTo
' 引数がNothingの場合はArgumentNullExceptionをスローする
If other Is Nothing Then Throw New ArgumentNullException("other")
' フィールドidの値の大小関係をCompareToの戻り値とする
Return MyClass.id - other.id
End Function
' 比較演算子 < のオーバーロード
Public Shared Operator < (ByVal x As Account, ByVal y As Account) As Boolean
' 被比較対象がNothingの場合はArgumentNullExceptionをスローする
If x Is Nothing Then Throw New ArgumentNullException("x")
' IComparable(Of Account).CompareToの実装を使って比較した結果を返す
Return x.CompareTo(y) < 0
End Operator
' 比較演算子 <= のオーバーロード
Public Shared Operator <= (ByVal x As Account, ByVal y As Account) As Boolean
' 被比較対象がNothingの場合はArgumentNullExceptionをスローする
If x Is Nothing Then Throw New ArgumentNullException("x")
' IComparable(Of Account).CompareToの実装を使って比較した結果を返す
Return x.CompareTo(y) <= 0
End Operator
' 比較演算子 > のオーバーロード
Public Shared Operator > (ByVal x As Account, ByVal y As Account) As Boolean
' 左辺と右辺を入れ替え比較演算子 < を使って結果を返す
'Return y < x
' 比較演算子 <= の結果を逆転して返すやり方でも可
Return Not (x <= y)
End Operator
' 比較演算子 >= のオーバーロード
Public Shared Operator >= (ByVal x As Account, ByVal y As Account) As Boolean
' 左辺と右辺を入れ替え比較演算子 <= を使って結果を返す
'Return y <= x
' 比較演算子 < の結果を逆転して返すやり方でも可
Return Not (x < y)
End Operator
Public Overrides Function ToString() As String
Return String.Format("{0}:{1}", id, name)
End Function
End Class
Class Sample
Shared Sub Main()
' オーバーロードした比較演算子のテスト
Dim x As New Account(0, "Charlie")
Dim y As New Account(1, "Eve")
Dim z As New Account(1, "Alice")
' 異なる値の場合
Console.WriteLine("{0} {2} {1}", x, y, If(x < y, "<", ">="))
Console.WriteLine("{0} {2} {1}", x, y, If(x <= y, "<=", ">"))
Console.WriteLine("{0} {2} {1}", x, y, If(x > y, ">", "<="))
Console.WriteLine("{0} {2} {1}", x, y, If(x >= y, ">=", "<"))
Console.WriteLine()
' 同値の場合
Console.WriteLine("{0} {2} {1}", y, z, If(y < z, "<", ">="))
Console.WriteLine("{0} {2} {1}", y, z, If(y <= z, "<=", ">"))
Console.WriteLine("{0} {2} {1}", y, z, If(y > z, ">", "<="))
Console.WriteLine("{0} {2} {1}", y, z, If(y >= z, ">=", "<"))
End Sub
End Class
0:Charlie < 1:Eve 0:Charlie <= 1:Eve 0:Charlie <= 1:Eve 0:Charlie < 1:Eve 1:Eve >= 1:Alice 1:Eve <= 1:Alice 1:Eve <= 1:Alice 1:Eve >= 1:Alice
なお、この例ではnull/Nothingと比較しようとした場合はArgumentNullExceptionをスローするようにしていますが、文字列型(string)のようにnull/Nothingは他のすべての値よりも小さいというように実装することも出来ます。
もう一つ別の例を挙げます。 次の例において、Accountクラスでは比較演算子のオーバーロードのみを行い、ソートに必要となる処理はComparer<T>クラスを継承した別のクラスAccountComparerで実装しています。
using System;
using System.Collections.Generic;
class Account {
int id;
string name;
public Account(int id, string name)
{
this.id = id;
this.name = name;
}
// 比較演算子 < のオーバーロード
public static bool operator < (Account x, Account y)
{
// 被比較対象・比較対象がnullの場合はArgumentNullExceptionをスローする
if (x == null) throw new ArgumentNullException("x");
if (y == null) throw new ArgumentNullException("y");
// フィールドidの値の大小関係に基づいて結果を返す
return x.id < y.id;
}
// 比較演算子 <= のオーバーロード
public static bool operator <= (Account x, Account y)
{
// 被比較対象・比較対象がnullの場合はArgumentNullExceptionをスローする
if (x == null) throw new ArgumentNullException("x");
if (y == null) throw new ArgumentNullException("y");
// フィールドidの値の大小関係に基づいて結果を返す
return x.id <= y.id;
}
// 比較演算子 > のオーバーロード
public static bool operator > (Account x, Account y)
{
// 左辺と右辺を入れ替え比較演算子 < を使って結果を返す
return y < x;
// 比較演算子 <= の結果を逆転して返すやり方でも可
//return !(x <= y);
}
// 比較演算子 >= のオーバーロード
public static bool operator >= (Account x, Account y)
{
// 左辺と右辺を入れ替え比較演算子 <= を使って結果を返す
return y <= x;
// 比較演算子 < の結果を逆転して返すやり方でも可
//return !(x < y);
}
public override string ToString()
{
return string.Format("{0}:{1}", id, name);
}
}
// Accountクラスを並べ替えるComparer
class AccountComparer : Comparer<Account> {
public override int Compare(Account x, Account y)
{
// オーバーロードした比較演算子を使って比較
if (x < y)
return -1;
else if (x > y)
return 1;
else // if (x == y)
return 0;
}
}
class Sample {
static void Main()
{
Account[] arr = new Account[] {
new Account(1, "Eve"),
new Account(4, "Dave"),
new Account(2, "Alice"),
new Account(0, "Charlie"),
new Account(3, "Bob"),
};
Console.WriteLine("[before sort]");
Print(arr);
Console.WriteLine("[after sort]");
// AccountComparerを使ってソート
Array.Sort(arr, new AccountComparer());
Print(arr);
}
static void Print(IEnumerable<Account> c)
{
foreach (Account e in c) {
Console.WriteLine(e);
}
}
}
Imports System
Imports System.Collections.Generic
Class Account
Dim id As Integer
Dim name As String
Public Sub New(ByVal id As Integer, ByVal name As String)
MyClass.id = id
MyClass.name = name
End Sub
' 比較演算子 < のオーバーロード
Public Shared Operator < (ByVal x As Account, ByVal y As Account) As Boolean
' 被比較対象・比較対象がnullの場合はArgumentNullExceptionをスローする
If x Is Nothing Then Throw New ArgumentNullException("x")
If y Is Nothing Then Throw New ArgumentNullException("y")
' フィールドidの値の大小関係に基づいて結果を返す
Return x.id < y.id
End Operator
' 比較演算子 <= のオーバーロード
Public Shared Operator <= (ByVal x As Account, ByVal y As Account) As Boolean
' 被比較対象がNothingの場合はArgumentNullExceptionをスローする
If x Is Nothing Then Throw New ArgumentNullException("x")
If y Is Nothing Then Throw New ArgumentNullException("y")
' フィールドidの値の大小関係に基づいて結果を返す
Return x.id <= y.id
End Operator
' 比較演算子 > のオーバーロード
Public Shared Operator > (ByVal x As Account, ByVal y As Account) As Boolean
' 左辺と右辺を入れ替え比較演算子 < を使って結果を返す
'Return y < x
' 比較演算子 <= の結果を逆転して返すやり方でも可
Return Not (x <= y)
End Operator
' 比較演算子 >= のオーバーロード
Public Shared Operator >= (ByVal x As Account, ByVal y As Account) As Boolean
' 左辺と右辺を入れ替え比較演算子 <= を使って結果を返す
'Return y <= x
' 比較演算子 < の結果を逆転して返すやり方でも可
Return Not (x < y)
End Operator
Public Overrides Function ToString() As String
Return String.Format("{0}:{1}", id, name)
End Function
End Class
' Accountクラスを並べ替えるComparer
Class AccountComparer
Inherits Comparer(Of Account)
Public Overrides Function Compare(ByVal x As Account, ByVal y As Account) As Integer
' オーバーロードした比較演算子を使って比較
If x < y Then
Return -1
Else If x > y Then
Return 1
Else ' If x = y Then
Return 0
End If
End Function
End Class
Class Sample
Shared Sub Main()
Dim arr() As Account = New Account() { _
New Account(1, "Eve"), _
New Account(4, "Dave"), _
New Account(2, "Alice"), _
New Account(0, "Charlie"), _
New Account(3, "Bob") _
}
Console.WriteLine("[before sort]")
Print(arr)
Console.WriteLine("[after sort]")
' AccountComparerを使ってソート
Array.Sort(arr, New AccountComparer())
Print(arr)
End Sub
Shared Sub Print(ByVal c As IEnumerable(Of Account))
For Each e As Account In c
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