第14章 インターフェイスの活用:連載 改訂版 C#入門(2/4 ページ)
継承と並び、OOPの鍵となるインターフェイス。foreach処理の実装やソート時の並べ替え処理の実装など、この機能はコードとシステムの接点ともなる。
14-3 インターフェイスの継承
インターフェイスはクラスのように継承できる。List 14-4はインターフェイスを継承した例である。
1: using System;
2:
3: namespace Sample004
4: {
5: interface IBase
6: {
7: void task1();
8: }
9: interface IDerived : IBase
10: {
11: void task2();
12: }
13: class Class1 : IDerived
14: {
15: public void task1()
16: {
17: Console.WriteLine("task1() called");
18: }
19: public void task2()
20: {
21: Console.WriteLine("task2() called");
22: }
23: [STAThread]
24: static void Main(string[] args)
25: {
26: Class1 c1 = new Class1();
27: c1.task1();
28: c1.task2();
29: }
30: }
31: }
これを実行するとFig.14-4のようになる。
ここでは、まず5〜8行目でIBaseというインターフェイスを定義している。次に、9〜12行目で、IDerivedというインターフェイスを、IBaseを継承して定義している。つまり、IDerivedには、IBaseで定義されたメソッドとIDerived自身で定義されたメソッドが含まれるのである。そのため、IDerivedを実装するClass1では、15〜18行目と19〜22行目で、IBaseで定義されたメソッドと、IDerivedで定義されたメソッドの両方を実装している。
この機能は、大規模なインターフェイスを定義する場合に便利だといえる。基本的なインターフェイスを定義してから、それらを集めて大きなインターフェイスを定義できるからである。
14-4 プロパティ、インデクサ、イベントとインターフェイス
メソッドだけでなく、プロパティ、インデクサ、イベントもインターフェイスに記述できる。List 14-5は実際に記述してみた例である。
1: using System;
2:
3: namespace Sample005
4: {
5: public delegate void SampleEvent(object sender, EventArgs e);
6: public interface ISample
7: {
8: int SampleProperty { get; set; }
9: string this[int index] { get; set; }
10: event SampleEvent sampleEvent;
11: }
12: class Class1 : ISample
13: {
14: private int sample = 0;
15: public int SampleProperty
16: {
17: get { return sample; }
18: set { sample = value; }
19: }
20: private string [] ar = new string[3];
21: public string this[int index]
22: {
23: get { return ar[index]; }
24: set { ar[index] = value; }
25: }
26: public event SampleEvent sampleEvent;
27: public void handler(object sender, EventArgs e)
28: {
29: ISample isample = (ISample)sender;
30: Console.WriteLine( isample.SampleProperty );
31: Console.WriteLine( isample[0] );
32: Console.WriteLine( isample[1] );
33: Console.WriteLine( isample[2] );
34: }
35: public void invokeEvent()
36: {
37: sampleEvent( this, EventArgs.Empty );
38: }
39: [STAThread]
40: static void Main(string[] args)
41: {
42: Class1 c1 = new Class1();
43: c1.SampleProperty = 123;
44: c1[0] = "ABC";
45: c1[1] = "DEF";
46: c1[2] = "GHI";
47: c1.sampleEvent += new SampleEvent(c1.handler);
48: c1.invokeEvent();
49: }
50: }
51: }
これを実行するとFig.14-5のようになる。
このソース・コードは込み入って見えるが、書いてあることは、イベントを除き、すでに説明した知識で理解できるものばかりである。パズルを解くと思って解読してみるとよいだろう。
なお、念のために、インターフェイスの定義部分のみ説明する。6〜11行目がこれに当たる。6行目でinterfaceの前にpublicが付いているのは、5行目のdelegateの宣言に付くpublicと水準を合わせるためのものだ。水準を合わせないとエラーになる。8行目はプロパティの宣言である。普通は、getやsetの後ろに中括弧を書いて処理内容を書くのだが、ここはインターフェイス内なので、実装は書かない。ここで、getだけ、あるいはsetだけ書けば、読み出し専用、あるいは書き込み専用のプロパティを強制できる。9行目はインデクサだ。getとsetに関しては、プロパティと同じである。10行目はイベント(後の章で詳しく説明する)を宣言している。インターフェイス内のイベントは、当然、単なる宣言にすぎないので、実装しなければ使えない。
14-5 同名のメソッドを持つインターフェイス
あるクラスで2つのインターフェイスを実装しようとしたが、偶然にも同じ名前のメソッドが定義されていたらどうなるだろうか? List 14-6は、そのような例を実際に記述してみた例である。
1: using System;
2:
3: namespace Sample006
4: {
5: interface ISample1
6: {
7: void task();
8: }
9: interface ISample2
10: {
11: void task();
12: }
13: class Class2 : ISample1
14: {
15: public void task()
16: {
17: Console.WriteLine("task() in class2 called");
18: }
19: }
20: class Class3 : ISample1, ISample2
21: {
22: void ISample1.task()
23: {
24: Console.WriteLine("ISample1.task() in class3 called");
25: }
26: void ISample2.task()
27: {
28: Console.WriteLine("ISample2.task() in class3 called");
29: }
30: }
31: class Class1
32: {
33: [STAThread]
34: static void Main(string[] args)
35: {
36: Class2 c2 = new Class2();
37: c2.task();
38: Class3 c3 = new Class3();
39: // c3.task(); // 'Sample006.Class3' に 'task' の定義がありません。
40: ISample1 i1 = (ISample1)c3;
41: i1.task();
42: ISample2 i2 = (ISample2)c3;
43: i2.task();
44: }
45: }
46: }
これを実行するとFig.14-6のようになる。
5〜8行目のインターフェイスISample1と、9〜12行目のインターフェイスISample2は、どちらも同じtaskというメソッドを持っている。この2つのインターフェイスを20行目のように1つのクラスで実装しようとすると、戻り値も引数も共通なので、そのままでは区別することができない。そこで、どのインターフェイス由来のメソッドを実装しようとしているのか、インターフェイス名を明示して記述する。22〜25行目はtaskではなく、ISample1.taskと明示的に書くことで、インターフェイスISample1のtaskを実装していることを明示している。同じように、26〜29行目でも同じようにインターフェイスISample2のtaskを実装している。
さて、呼び出す方も一工夫が必要である。同じ名前のメソッドが相手なので、39行目のような単純な呼び出し方法ではうまくいかないのである。そこで、40〜43行目を見ていただきたい。ここにあるように、まずインターフェイスにキャストしてから呼び出せば、曖昧さはなくなる。例えば、40行目では、クラスClass3のインスタンスをインターフェイスISample1にキャストしている。キャスト結果に対し、41行目ではtaskメソッドを呼び出しているが、このコードは間違いなく、インターフェイスISample1のtaskメソッドを呼び出す。同様に、42〜43行目では、間違いなく、インターフェイスISample2のtaskメソッドを呼び出す。
Copyright© Digital Advantage Corp. All Rights Reserved.