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 ? ">=" : "<");
}
}
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);
}
}
}
[before sort] 1:Eve 4:Dave 2:Alice 0:Charlie 3:Bob [after sort] 0:Charlie 1:Eve 2:Alice 3:Bob 4:Dave