C# インターフェース - IComparable, IComparable(T)
インターフェース | 対象 | 使えるようになる機能 |
---|---|---|
IComparable, IComparable<T> | コンテナーに格納するクラス | 格納したコンテナーのソート、検索など |
IEnumerable | コンテナークラス | foreach |
IEnumerable<T> | LINQ の各種メソッド | |
IDisposable | ファイルなどの後処理のタイミング管理が必要なクラス | using |
今回は IComparable, IComparable<T> インターフェースについての説明です。
用途
配列(Array)などのコンテナークラスでは数値を格納した場合に、 ソートや二分探索(ソート済みのデータから高速に検索)などができます。IComparable または IComparable<T> インターフェースを継承しておけば、自作のクラスでもこれらの機能が使えるようになります。
また、 SortedList のような比較を必要とするコンテナーでは継承していないと格納することができません。
コンテナーに格納するようなクラスでは実装しておいた方がいいでしょう。
IComparable
まず IComparable から説明します。実装するメソッド
IComparable インターフェースを継承した場合、CompareTo() メソッドを実装する必要があります。int CompareTo(Object obj);これは C 言語で言えば、文字列の strcmp() に相当する比較用メソッドです。
比較対象(obj)を受け取り、自身(this)と比べた結果を数値として返します。
比較 | 戻り値 |
---|---|
obj < this | -1 以下の整数 |
obj == this | 0 |
this < obj | 1 以上の整数 |

サンプル
サンプルとして点クラスを作成しました。 コンパイルする場合は以下のコマンドを実行します。> csc IComparableSample.cs Point_IComparable.cscsc.exe の使用したコンパイル方法については以前の記事を見て下さい。
点クラス Point は x, y のプロパティを持たせています。
CompareTo() メソッドはまず x で比較し、 同じ場合に y で比較するという仕様にしました。
Point_IComparable.cs(抜粋) :
class Point : IComparable { public int x { get; set; } public int y { get; set; } public Point(int x, int y) { this.x = x; this.y = y; } /// <summary> /// 比較メソッド /// </summary> public int CompareTo(Object obj) { if (obj == null) return 1; Point other = (Point)obj; if (other.x == x) { return y - other.y; } return x - other.x; } }作成した Point クラスを使った例は IComparableSample.cs に記述しています。
ここで、BinarySearch() の例を後にしているのは、ソートしたデータでなければ検索できないためです。
IComparableSample.cs(抜粋) :
class Program { static void Main() { // Point を格納したデータを準備 Point [] ary = { new Point(1, 2), new Point(3, 5), new Point(1, 9), new Point(4, 1), new Point(3, 1) }; DumpArray("Original", ary); // データのソート Array.Sort(ary); DumpArray("Sorted", ary); // 二分探索 Console.WriteLine("## Binary Search ##"); var target = new Point(3, 1); int pos = Array.BinarySearch(ary, target); Console.WriteLine("{0} @ {1}", target, pos); } }実行結果 :
## Original ## (1, 2) (3, 5) (1, 9) (4, 1) (3, 1) ## Sorted ## (1, 2) (1, 9) (3, 1) (3, 5) (4, 1) ## Binary Search ## (3, 1) @ 2
IComparable<T>
IComparable<T> は IComparable のジェネリック版です。 どちらを使っても同じように使うことができます。わかりやすいように先に IComparable の説明を行いましたが、 実際に使うのは ジェネリック版の IComparable<T> が良いでしょう。
CompareTo() 実装時にキャストしなくて済みますし、若干処理が速いようです。 IComparable<T> の方がいいなら、なぜ IComparable があるのかというと、 IComparable が先にできていたためです。 ジェネリックは C# 2.0 から追加されています。
実装するメソッド
IComparable<T> の場合はジェネリックな CompareTo() を実装します。int CompareTo(T other);

サンプル
点クラスを使用する側のコードは IComparable と同じ IComparableSample.cs を使用します。> csc IComparableSample.cs Point_IComparable_T.cs
Point_IComparable_T.cs(抜粋) :
class Point : IComparable<Point> { public int CompareTo(Point other) { if (other == null) return 1; if (other.x == x) { return y - other.y; } return x - other.x; } }実行結果も同じです。
ジェネリックな Point
ついでに Point をジェネリックにしたサンプルも紹介します。前のサンプルは Point の x, y の型は int 固定でしたが、 double などの任意の型を仕様できるようになります。
ただし、 CompareTo() で比較する必要があるので、 完全に任意ではなく、x, y の型は IComparable を継承しているなければなりません。
クラスのジェネリックと IComparable がジェネリックかどうかは関係ありません。 サンプルでは IComparable<T> で実装していますが、 IComparable でも同じように実装できます。
サンプルは今度は 1 つのファイルに記述しています。
> csc GenericPointSample.cs
GenericPointSample.cs(抜粋) :
class Point<Type> : IComparable<Point<Type> > where Type : IComparable { public Type x { get; set; } public Type y { get; set; } public Point(Type x, Type y) { this.x = x; this.y = y; } public int CompareTo(Point<Type> other) { if (other == null) return 1; if (x.CompareTo(other.x) == 0) { return y.CompareTo(other.y); } return x.CompareTo(other.x); } } // Point実行結果は他のサンプルと同じです。
C# インターフェース - IEnumerable(T)
このインターフェースは非常に便利なのですが、 実装するにはちょっとしたテクニックが必要となります。 知らないとなかなか思いつきにくいところなので、 覚えておいて損はないと思います。
用途
前回紹介したIEnumerable インターフェース と同様に IEnumerable<T> は自作のコンテナークラス などで継承します。IEnumerable を継承したクラスは foreach で使えるようになります。
IEnumerable<T> の場合は、 foreach で使えることに加えて、より大きなメリットがあります。
それは
System.Linq
を using
しておくことによって、
LINQ で定義された多くの拡張メソッドが使用可能になることです。
これは Ruby で言えば、 Enumerable を Mix-in したようなものです。自前のコンテナークラスを用意した場合には、 IEnumerable<T> を継承しておくのがお勧めです。
実装するメソッド
IEnumerable<T> を継承するには GetEnumerator()メソッドを実装します。IEnumerator<T> GetEnumerator();加えて、 IEnumerator の GetEnumerator() メソッドも実装する必要があります。
IEnumerator GetEnumerator()これは IEnumerator<T> が単に IEnumerator のジェネリック版というだけでなく、 IEnumerator を継承しているインターフェースだからです。

IEnumerator<T> を継承すると IEnumerator も継承することになります。
実際にはIEnumerator<T> の GetEnumerator() しかまず使われません。 しかし、両方の GetEnumerator() メソッドを実装しないとコンパイルに失敗してしまいます。
この IEnumerator 側の GetEnumerator() の実装ではよく使われる常套句的な記述があります。
実装方法
サンプルを使ってこの IEnumerator<T> の実装方法を説明していきたいと思います。 コンパイル:> csc IEnumerableSample_T.csJagged Array と呼ばれる 配列の配列 を自作のコンテナーのサンプルとします。

IEnumerableSample_T.cs (クラス定義部) :
class JaggedArray<TSource> : IEnumerable<TSource> { private List<TSource>[] _list; public JaggedArray(int rowmax) { _list = new List<TSource>[rowmax]; } public bool Add(int row, TSource val, params TSource[] restvals) { // : } public IEnumerator<TSource> GetEnumerator() { foreach (List<TSource> sublist in _list) { if (sublist != null) { // 子側の配列をイテレート foreach (TSource val in sublist) { yield return val; } } } } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } }この IEnumerable.GetEnumerator() の定義部分がポイントです。
IEnumerable のメソッドであることを
IEnumerable.
で明示的に指定しています。
これがないと戻り値だけが違うメソッドのオーバーロードとなり、コンパイルエラーとなります。定義内は IEnumerable<T> の GetEnumerator() を呼び出すだけの処理です。
次に作成した JaggedArray を実際に使ってみます。
// 結果表示用のメソッド static string Dump<TSource>(IEnumerable<TSource> source) { return "{" + string.Join(", ", source) + "}"; } static void Main() { JaggedArray<int> jagary = new JaggedArray<int>(5); jagary.Add(0, 1); jagary.Add(2, 1, 2, 3, 4); jagary.Add(3, 1, 2); jagary.Add(4, 5); foreach (int it in jagary) { Console.Write("{0} ", it); } Console.WriteLine("\n"); // LINQ Console.WriteLine("Count = {0}", jagary.Count()); Console.WriteLine("3 Contains ? = {0}", jagary.Contains(3)); Console.WriteLine("Max = {0}", jagary.Max()); Console.WriteLine("Sum = {0}", jagary.Sum()); Console.WriteLine("Average = {0}", jagary.Average()); Console.WriteLine("ToArray = {0}", Dump(jagary.ToArray())); Console.WriteLine("(Source) / 2.0 = {0}", Dump(jagary.Select(it=>it/2.0))); }実行結果 :
1 1 2 3 4 1 2 5 Count = 8 3 Contains ? = True Max = 5 Sum = 19 Average = 2.375 ToArray = {1, 1, 2, 3, 4, 1, 2, 5} (Source) / 2.0 = {0.5, 0.5, 1, 1.5, 2, 0.5, 1, 2.5}まず foreach で要素を順にアクセスする処理を行なっています。
その後に LINQ の演算子メソッドの幾つかを使用しています。
これは多くの演算子メソッドの中のほんの一部です。 これらを自分で実装することなく、使用することができるようになっています。