ここでは、.NET Frameworkに用意されているArray.SortやEnumerable.OrderByなどのソート処理を使わずに、独自にジェネリックなソートアルゴリズムを実装する方法について見ていきます。 以下で紹介するコードはそのまま実用することもできますが、ソート処理の内部動作とジェネリックインターフェイス・ジェネリックデリゲートの関わり、ジェネリックメソッドの実装について理解することを主な目的としています。
導入
ジェネリックなソートアルゴリズムを実装するにあたり、基本形としてint/Integer型の配列をソートして昇順に並べ替えるコードを考えます。 ここでは、実装するソートアルゴリズムとしてクイックソートを使用することにします。
このコードを書き換えて、double型の配列をソートできるようにすると次のようになります。
このように、扱うデータの型が変わっているだけで、それ以外のコード(ソートのアルゴリズム部分)は全く同ものを流用することができます。 そこで、このソート処理をどのような型に対しても適用できるよう、このQSortメソッドをジェネリックメソッド化することを考えます。
ジェネリックメソッド化
先のコードでソート対象のデータ型として使用していたdoubleの個所を、ジェネリック型T
に置き換えることでジェネリックメソッド化することを目指します。
しかし単純に型をジェネリック型パラメータTに置き換えるだけでは上記のようにコンパイルエラーとなります。
intやdoubleでは比較演算子<
が使用できるので、これによって値の大小関係を比較することができます。 一方ジェネリック型パラメータのTは、intやdoubleとなる場合があるだけでなく、比較演算子を持たない型となる場合もあります。 つまり、型Tは必ずしも比較演算子による大小関係の比較は行えないことから、上記のようなコンパイルエラーが発生します。
そのため、ジェネリック型同士を比較するには比較演算子ではなく他の方法によって比較する必要があります。 このような目的にはIComparer<T>インターフェイスを使うことができます。 IComparer<T>インターフェイスのCompareメソッドは引数に指定した二つの値の大小関係に応じた戻り値を返すので、これを比較演算子の代わりに使うことができます。 Array.Sortメソッドなども、IComparer<T>インターフェイスを指定することにより比較演算子を持たない型のソートができるようになっています。
IComparer<T>による大小比較のパラメータ化
比較演算子の代わりとして使用するIComparer<T>を指定できるようQSortメソッドの引数を増やし、大小関係の比較をIComparer<T>.Compareメソッドで行うように書き換えると次のようになります。
これにより、どのような型でもソートできるジェネリックなメソッドを作成することができました。 またソート順序をIComparer<T>によって定義・指定できるようになるため、デフォルトとは異なる順序でソートしたい場合、例えば文字列をソートする際に大文字小文字の違いを無視する目的でStringComparer.OrdinalIgnoreCaseを指定する、といった使い方ができるようになります。
利便性の向上
基本型のIComparer<T>の省略
ここまでの実装では、比較演算子を持つintやdoubleなどの基本型の場合でも必ずIComparer<T>を指定する必要があります。 基本型の比較を行うIComparer<T>はComparer<T>.Defaultプロパティを参照するだけで取得できますが、それでもこれを毎回指定するのは不便です。
そこで、次のようにIComparer<T>を指定しないオーバーロードを増やすことにより、引数の数をジェネリック化する前と同じ数にすることができます。
Comparison<T>の導入
ソート順序の定義にIComparer<T>を使用する場合、IComparer<T>を実装したクラスを用意する必要があります。 一方、Comparison<T>デリゲートを使えば、単にソート順序を定義したメソッドやラムダ式を用意するだけでよくなるため、わざわざクラスを用意する必要がなくなり、実装を簡略化できます。
従って、IComparer<T>.Comparerメソッドと同じようにComparison<T>を使うオーバーロードも増やせば、より使いやすくすることができます。
発展
ここではソートアルゴリズムにクイックソートを使用しましたが、それ以外のソートアルゴリズムの場合もここでの手順と同様にして実装することができます。
ここまでで紹介したコードではComparer<T>.DefaultやStringComparerなどのすでに用意されているIComparer<T>を使用しましたが、IComparer<T>を適切に実装することにより、基本型だけでなく独自に作成したクラスや構造体など任意の型のソート順や、複数キーによるソートも定義できるようになります。
IComparer<T>の実装方法などについては大小関係の定義と比較や複合型のソート・複数キーでのソートを参照してください。
ここではソート対象を配列(T[]
)としましたが、これをIList<T>にすれば、配列だけでなくList<T>を含む幅広いコレクション型のソートに対応できるようになります。