自作クラスの配列やコレクションをSortメソッドで並び替えたり、BinarySearchメソッドで検索する必要がある時、どのように並び替えられるようにするかを定義しておく必要があります。その方法は、IComparerを指定するか、自作クラスにIComparableインターフェイスを実装するかのどちらかです。IComparerを指定してSortメソッドで並び替える方法は「配列やコレクション内の要素を並び替える」で、BinarySearchメソッドで検索する方法は「配列やコレクション内に指定された要素があるか調べ、その位置を知る」で説明しています。ここではIComparableインターフェイスを実装する方法を説明します。
IComparableインターフェイスには1つのメンバCompareToメソッドしかありません。CompareToメソッドでは、自分自身とパラメータで与えられたオブジェクトを比べ、どちらが大きいかを決定します。自分のほうが小さければ負の整数を、同じならば0を、大きければ正の整数を返します。
以下にIComparableを実装した簡単なクラスの例を示します。このクラスはNameとPriceの2つのプロパティを持ちますが、Priceプロパティの大小がProductオブジェクトの大小になるようにしています。
'コレクションで並び替えができるクラスの定義 'IComparableインターフェイスを実装する Public Class Product Implements System.IComparable Private _name As String 'String型のプロパティ Public ReadOnly Property Name() As String Get Return Me._name End Get End Property Private _price As Integer 'Int32型のプロパティ Public ReadOnly Property Price() As Integer Get Return Me._price End Get End Property 'コンストラクタ Public Sub New(ByVal productName As String, ByVal productPrice As Integer) Me._name = productName Me._price = productPrice End Sub '自分自身がobjより小さいときはマイナスの数、大きいときはプラスの数、 '同じときは0を返す Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo 'Nothingより大きい If obj Is Nothing Then Return 1 End If '違う型とは比較できない If Not Me.GetType() Is obj.GetType() Then Throw New ArgumentException("別の型とは比較できません。", "obj") End If 'このクラスが継承されることが無い(構造体など)ならば、次のようにできる 'If Not TypeOf other Is TestClass Then 'Priceを比較する Return Me.Price.CompareTo(DirectCast(obj, Product).Price) 'または、次のようにもできる 'Return Me.Price - DirectCast(obj, Product).Price End Function End Class
//コレクションで並び替えができるクラスの定義 //IComparableインターフェイスを実装する public class Product : System.IComparable { private string _name; //String型のプロパティ public string Name { get { return this._name; } } private int _price; //Int32型のプロパティ public int Price { get { return this._price; } } //コンストラクタ public Product(string productName, int productPrice) { this._name = productName; this._price = productPrice; } //自分自身がobjより小さいときはマイナスの数、大きいときはプラスの数、 //同じときは0を返す public int CompareTo(object obj) { //nullより大きい if (obj == null) { return 1; } //違う型とは比較できない if (this.GetType() != obj.GetType()) { throw new ArgumentException("別の型とは比較できません。", "obj"); } //このクラスが継承されることが無い(構造体など)ならば、次のようにできる //if (!(other is TestClass)) { } //Priceを比較する return this.Price.CompareTo(((Product)obj).Price); //または、次のようにもできる //return this.Price - ((Product)other).Price; } }
補足:このコードではnullが一番小さいとしていますが、ArgumentExceptionのような例外をスローしてもよいでしょう。
次に、このクラスのコレクションを並び替える例を示します。
'ArrayListの作成 Dim al As New System.Collections.ArrayList() al.Add(New Product("キャンディ", 50)) al.Add(New Product("キャラメル", 100)) al.Add(New Product("チョコレート", 10)) 'はじめの状態を表示 Console.WriteLine("並び替えなし") For Each c As Product In al Console.WriteLine("Name: {0} / Price: {1}", c.Name, c.Price) Next '並び替える al.Sort() '結果を表示 Console.WriteLine("並び替え後") For Each c As Product In al Console.WriteLine("Name: {0} / Price: {1}", c.Name, c.Price) Next '検索する Console.WriteLine("BinarySearchで探す") Console.WriteLine(al.BinarySearch(New Product("チョコレート", 50)))
//ArrayListの作成 System.Collections.ArrayList al = new System.Collections.ArrayList(); al.Add(new Product("キャンディ", 50)); al.Add(new Product("キャラメル", 100)); al.Add(new Product("チョコレート", 10)); //はじめの状態を表示 Console.WriteLine("並び替えなし"); foreach (Product c in al) { Console.WriteLine("Name: {0} / Price: {1}", c.Name, c.Price); } //並び替える al.Sort(); //結果を表示 Console.WriteLine("並び替え後"); foreach (Product c in al) { Console.WriteLine("Name: {0} / Price: {1}", c.Name, c.Price); } //検索する Console.WriteLine("BinarySearchで探す"); Console.WriteLine(al.BinarySearch(new Product("チョコレート", 50)));
出力結果は、以下のようになります。
並び替えなし Name: キャンディ / Price: 50 Name: キャラメル / Price: 100 Name: チョコレート / Price: 10 並び替え後 Name: チョコレート / Price: 10 Name: キャンディ / Price: 50 Name: キャラメル / Price: 100 BinarySearchで探す 1
このように、Sortメソッドを呼び出すと、Priceの大きさで並び替えられます。また、BinarySearchではNameを無視して、Priceが同じ要素が検索されます。
.NET Framework 2.0以降では、さらにIComparable<T>ジェネリックインターフェイスも実装した方が良いでしょう。
先のTestClassクラスにIComparable<T>ジェネリックインターフェイスも実装した例を示します。
'コレクションで並び替えができるクラスの定義 'IComparable<T>ジェネリックインターフェイスを実装する Public Class Product Implements System.IComparable Implements System.IComparable(Of Product) Private _name As String 'String型のプロパティ Public ReadOnly Property Name() As String Get Return Me._name End Get End Property Private _price As Integer 'Int32型のプロパティ Public ReadOnly Property Price() As Integer Get Return Me._price End Get End Property 'コンストラクタ Public Sub New(ByVal productName As String, ByVal productPrice As Integer) Me._name = productName Me._price = productPrice End Sub '自分自身がotherより小さいときはマイナスの数、大きいときはプラスの数、 '同じときは0を返す Public Function CompareTo(ByVal other As Product) As Integer _ Implements System.IComparable(Of Product).CompareTo 'Nothingより大きい If other Is Nothing Then Return 1 End If 'Priceを比較する Return Me.Price.CompareTo(other.Price) End Function Public Function CompareTo(ByVal obj As Object) As Integer _ Implements System.IComparable.CompareTo 'Nothingより大きい If obj Is Nothing Then Return 1 End If '違う型とは比較できない If Not Me.GetType() Is obj.GetType() Then Throw New ArgumentException("別の型とは比較できません。", "obj") End If Return Me.CompareTo(DirectCast(obj, Product)) End Function End Class
//コレクションで並び替えができるクラスの定義 //IComparable<T>ジェネリックインターフェイスを実装する public class Product : System.IComparable, System.IComparable<Product> { private string _name; //String型のプロパティ public string Name { get { return this._name; } } private int _price; //Int32型のプロパティ public int Price { get { return this._price; } } //コンストラクタ public Product(string productName, int productPrice) { this._name = productName; this._price = productPrice; } //自分自身がotherより小さいときはマイナスの数、大きいときはプラスの数、 //同じときは0を返す public int CompareTo(Product other) { //nullより大きい if (other == null) { return 1; } //Priceを比較する return this.Price.CompareTo(other.Price); } public int CompareTo(object obj) { //nullより大きい if (obj == null) { return 1; } //違う型とは比較できない if (this.GetType() != obj.GetType()) { throw new ArgumentException("別の型とは比較できません。", "obj"); } return this.CompareTo((Product)obj); } }
CompareToメソッドを定義(あるいは変更)し、Equalsメソッドをそのままにした場合、CompareToが0を返すのにEqualsがfalseを返すといったことが起こりえます。よってガイドラインでは、指定した型でIComparableインターフェイスを実装した場合は、その型でEqualsメソッドをオーバーライドするように求めています。
補足:.NET Framework 2.0 Beta1ではIComparable<T>インターフェイスにCompareToメソッドとEqualsメソッドの2つがありました。しかしCompareToメソッドはあくまで並び替えで使われるものであり、Equalsメソッドと分けるべきだなどの意見があったため、その後Equalsメソッドは分割され、IEquateable<T>インターフェイスのメソッドとなりました(詳しくは、「IComaprer & IComparable Refactoring Proposal」)。このような経緯を考慮すると、必ずしもIComparableインターフェイスを実装したならば、Equalsメソッドも実装しなければならないともいえないかもしれません。
さらに、ガイドラインでは、IComparableを実装した場合、等値(==)、非等値(!=)、小なり(<)、大なり(>)の各演算子のオーバーロードを実装することを検討するように求めています。
注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。