連載

C#入門

第15 インターフェイスの活用

(株)ピーデー
川俣 晶
2001/11/10


ソート順を操る

 .NET Frameworkには、一次元リスト構造でデータを管理する一種の配列であるArrayListクラス(System.Collections.ArrayList)がある。これは、配列に似ているが、配列では実現できない機能をいくつか持っていて便利な代物である。例えば、動的にサイズを増加させるというのは、配列ではできないが、ArrayListならできる。さて、ArrayListには、Sortという便利なメソッドがある。その名のとおり、内容をソートしてくれるというもので、なかなか使い出がありそうだ。さて、そこで問題になるのは、ソートの並び順をどうやって指定するかである。数値の大小や、文字列をロケール順に並べ替えるのならば、何も考えることはない。だが、込み入った自作クラスのインスタンスを詰め込んだArrayListをソートする場合、どんな基準で並べ替えるべきなのだろうか。

 これに対する答えは、インターフェイスIComparableを実装する、というものだ。ArrayListに格納するインスタンスが、すべて、このインターフェイスを実装していれば、これを基準にしてSortメソッドは並べ替えを行うことができる。つまり、IComparableを実装することによって、プログラマはソートの並び順を思いどおりにコントロールできるわけである。以下にその例を示す。

   1: using System;
   2: using System.Collections;
   3:
   4: namespace ConsoleApplication100
   5: {
   6:   class ClassSample : IComparable
   7:   {
   8:     public string number;
   9:     public int CompareTo( object obj )
  10:     {
  11:       string s = ((ClassSample)obj).number;
  12:       if( number == "one" && s == "two" ) return -1;
  13:       if( number == "one" && s == "three" ) return -1;
  14:       if( number == "two" && s == "one" ) return 1;
  15:       if( number == "two" && s == "three" ) return -1;
  16:       if( number == "three" && s == "one" ) return 1;
  17:       if( number == "three" && s == "two" ) return 1;
  18:       return 0;
  19:     }
  20:     public ClassSample( string s )
  21:     {
  22:       number = s;
  23:     }
  24:   }
  25:   class Class1
  26:   {
  27:     static void Main(string[] args)
  28:     {
  29:       ArrayList al = new ArrayList();
  30:       al.Add( new ClassSample("two") );
  31:       al.Add( new ClassSample("three") );
  32:       al.Add( new ClassSample("one") );
  33:       al.Sort();
  34:       foreach( ClassSample cs in al )
  35:       {
  36:         Console.WriteLine( cs.number );
  37:       }
  38:     }
  39:   }
  40: }
独自のソート順を使用したサンプル・プログラム8

クラスにIComparableインターフェイスを実装し、CompareToメソッドを記述する。

 これを実行すると以下のようになる。

サンプル・プログラム8の実行結果
文字列が英単語の意味順にソートされているのが分かる。

 IComparableは、CompareToというメソッドを1つだけ持つ。このメソッドは、自分自身と、引数として渡されたオブジェクトobjを比較して、小さければ負数、同じなら0、大きければ正数を返す。ここでは文字列one、two、threeを数値の1、2、3に見立てた結果を返している。あとは難しいことはないだろう。30〜32行目でデタラメな順番で値をArrayListに登録しているのに、33行目でSortメソッドを実行すると、正しい順番に内容は整列される。もちろん、SortメソッドからCompareToメソッドが何度も呼び出されて、その情報を基準にして並べ替えが行われているのである。

外部から条件を指定してソート

 上の例は、ArrayListに格納されるインスタンス自身が、どう並べ替えられるべきかを知っている場合のサンプルである。しかし、ソート順がいつも決まっている場合ばかりとは限らない。例えば住所録なら、名前順でソートしたいときや、住所順でソートしたいときがあるだろう。そういう場合のためには、ソート順をクラスの外部で指定するという方法もある。この方法なら、さまざまなソート方法を準備しておき、それを切り替えながら利用できる。以下はそれを実現した例である。

   1: using System;
   2: using System.Collections;
   3:
   4: namespace ConsoleApplication101
   5: {
   6:   class ClassComparer : IComparer
   7:   {
   8:     public int Compare( object x, object y )
   9:     {
  10:       double dx = (double)x;
  11:       double dy = (double)y;
  12:       return (int)dx - (int)dy;
  13:     }
  14:   }
  15:   class Class1
  16:   {
  17:     static void Main(string[] args)
  18:     {
  19:       ArrayList al = new ArrayList();
  20:       al.Add( 2.2 );
  21:       al.Add( 3.7 );
  22:       al.Add( 1.5 );
  23:       al.Add( 2.7 );
  24:       al.Add( 3.2 );
  25:       al.Add( 1.1 );
  26:       al.Sort( new ClassComparer() );
  27:       foreach( double d in al )
  28:       {
  29:         Console.WriteLine( d );
  30:       }
  31:     }
  32:   }
  33: }
ソート順をクラスの外部で記述しているサンプル・プログラム9
2つの値を比較するCompareメソッドだけを記述したクラスを作成すればよい。

 これを実行すると以下のようになる。

サンプル・プログラム9の実行結果
このプログラムでは各数値の整数部だけでソートしている。

 このサンプルでは、ちょっと変わった条件付けを行ってソートを行っている。つまり実数をソートするのだが、順番は整数扱いで決めるというものだ。小数部は昇順に揃っていないが、整数だけは揃っているという変な状態を作り出している。さてこれを実現するには、Sortメソッドの引数にIComparerを渡す。26行目にその実例が記述されている。ここではClassComparerクラスのインスタンスが渡されているが、これにはIComparerが実装されているので、自動的にそれが取り出されて渡される。

 さてIComparerは実装しなければ使えないので、IComparerを実装するクラスとして、6〜14行目でClassComparerクラスを定義している。IComparerは1つのメソッドCompareを含んでいるので、これを実装する。8行目の引数のx、yは比較すべき2つのオブジェクトである。これらの値は実数になるはずなので、10〜11行目のように、一度doubleにキャストしてやる。そして12行目で整数にキャストして比較する。この比較により、小さければ負数、同じなら0、大きければ正数を返すという条件が満たされる。

 以上により、26行目のSortメソッド実行時には、何度もCompareメソッドが呼び出され、その結果により並べ替えが行われるというわけである。

国際化を意識した自動文字列化

 .NET Frameworkでは、どんなクラスもToStringというメソッドを持っている。どんなオブジェクトも文字列に変換される必要があると、これが呼び出される。例えば、Console.WriteLineメソッドなどで出力するときに、自作クラスを指定しても、ToStringメソッドさえきちんと実装されていれば、適切な値が表示されるわけである。ここまでの機能は、単なる継承によって実現される。だが、インターフェイスを使うと、もうちょっと凝ったことができる。

 デフォルトのカルチャーが日本の日本語(ja-JP)である場合に、数値の1〜3を、漢数字の一、二、三にする機能を記述してみよう。以下はそれを実現する例である。

   1: using System;
   2: using System.Globalization;
   3:
   4: namespace ConsoleApplication102
   5: {
   6:   class ClassSample : IFormattable
   7:   {
   8:     public int x;
   9:     public string ToString( string format, IFormatProvider formatProvider )
  10:     {
  11:       if( formatProvider is CultureInfo ) {
  12:         CultureInfo ci = (CultureInfo)formatProvider;
  13:         if( ci.Name == "ja-JP" )
  14:         {
  15:           switch( x )
  16:           {
  17:             case 1:
  18:               return "一";
  19:             case 2:
  20:               return "二";
  21:             case 3:
  22:               return "三";
  23:           }
  24:         }
  25:       }
  26:       return x.ToString();
  27:     }
  28:   }
  29:   class Class1
  30:   {
  31:     static void Main(string[] args)
  32:     {
  33:       ClassSample cs = new ClassSample();
  34:       cs.x = 3;
  35:       Console.WriteLine( cs );
  36:     }
  37:   }
  38: }
独自のToStringメソッドを実装したサンプル・プログラム10
.NET Frameworkの動作環境が日本語の場合には数値を漢数字で文字列化する。

 これを実行すると以下のようになる。

サンプル・プログラム10の実行結果
数値の「3」を持つオブジェクトをWriteLineメソッドにより表示すると「三」となる。

 では解説しよう。IFormattableとは、インスタンスを文字列で書式化する機能を提供するインターフェイスである。カルチャーの情報などを参照して、動作環境によって動作を変えることができる。例えば、日付などの処理に利用されるものである。アメリカなら“January”だが、日本では「一月」を出力する、というような場面で意味を持つ。

 IFormattableが持つメソッドはToStringだけである。このToStringは、すべてのクラスが持っているToStringとは異なり、9行目にあるとおり、2つの引数を持つ。最初の引数としては、通貨などの条件を指定する文字列を取るが、ここでは無視している。2番目の引数は、Console.WriteLineメソッドなどから呼び出すときはカルチャー情報が渡されてくる。そこで、11行目で確認してから、12行目でキャストしてCultureInfoクラスのインスタンスとして扱う。このインスタンスのNameプロパティの値が“ja-JP”なら、日本語環境で動いていることを示す。これらの条件を満たすときには、15〜22行目のように、数値の1〜3を、漢数字として返している。どの条件にも当てはまらない場合は、26行目のように、引数のないToStringで文字列に変換しているが、これは、数値の123が文字列の“123”になるような単純な変換である。

 さて、これらのコードによって、35行目のように直接インスタンスをConsole.WriteLineに渡すと、Console.WriteLineメソッドの書式整形処理の一環として、9行目からのToStringメソッドが呼び出され、出力内容をカルチャー情報によって変化させられるのである。

まとめ

 今回の説明で分かるとおり、インターフェイスは単なるプログラム言語の機能というだけでなく、システム側に用意されたさまざまな便利な機能にアクセスするための入り口という意味合いも持つ。いろいろなインターフェイスを自作クラスに実装することにより、システムが用意したさまざまな機能を利用したり、自由自在にコントロールしたりできる。そういう意味で、インターフェイスを自分で宣言するよりも、まずは既存のインターフェイスの活用から始めるとよいだろう。

 さて、次回は列挙型(Enum)について取り上げたいと考えている。

 それでは次回もLet's See Sharp!End of Article


 INDEX
  第15回 インターフェイスの活用
    1.インターフェイスの効能
    2.インターフェイスの継承
    3.同名のメソッドを持つインターフェイス
  4.ソート順を操る

「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間