|
||||
|
FamilyCarやSportsCarクラスにおいて、Carクラスの前進するメソッドは、
ただくつがえされるために存在するダミーに過ぎない。
このメソッドを誰かが間違って、正しいメソッドのつもりで実行してしまったら大変だ。
C#では、abstractキーワードによって、メソッドを抽象化することができる。
抽象メソッドは、メソッドヘッダだけを含むだけで実装は含まず、
代わりに子孫クラスが実装を提供することを要求する。
これにより、
| p.222 |
抽象クラス型 抽象クラスを実体化することはできないが、その型の変数を宣言することは可能。 これは、多相性を使う上で重要。 | p.224 |
具象クラス 実体化することのできる「非抽象クラス」を、具象クラスと呼ぶ。 | p.225 |
プロパティやインデクサのget/setアクセサもabstract宣言が可能。 public abstract int MyProperty { get; ←これらも set; ←本体を持たない } | p.225 |
レーシングカー型は、クルマ型であるので、次のコードは有効になる。 Car myCar; myCar = new RacingCar(); しかし、逆はない。クルマ型は必ずしもレーシングカー型でない。次のコードは無効。 RacingCar myRacingCar; myRacingCar = new Car(); つまり、Car型のmyCarは、FamilyCarオブジェクトや、SportsCarオブジェクトも参照できるし、 その子孫の型のオブジェクトでも参照できる。 次のように書くと、 Car myCar = new RacingCar(); 祖先型(ここではmyCar型)の変数を使って呼び出せるのは、 その祖先型で定義されたメソッドあるいはプロパティ、インデクサだけ。 つまり、子孫クラスのオブジェクトは、祖先型で定義することが可能だが、 使えるメソッド、プロパティ、インデクサの内容は、祖先型によって制限される。 | p.226 |
仮想メソッドの動的束縛 Car myCar; myCar = new RacingCar(); //@ myCar.前進する() //A←仮想メソッド。その由来はこの後... 上のコードにおいて、CarクラスにもRacingCarクラスにも、前進する()メソッドが含まれていて、 Carクラスで前進するメソッドがvirtual宣言されているとき、 ランタイムは、どちらのクラスのメソッドを呼び出すのだろうか? 答えはRacingCarクラスにある、前進する()メソッド。 @コードが書かれた時点で、決定する。 myCarにSportsCarオブジェクトが入っていれば、SportsCarクラスの前進するメソッドを実行し、 myCarにRacingCarオブジェクトが入っていれば、RacingCarクラスの前進するメソッドを実行する。 どのオブジェクトを参照するかで、Aのコードが表す実装は動的に決定されるので動的束縛、 その決定はコンパイル時よりも後に遅延され、プログラム実行時になるので、遅延束縛と呼ぶ。 | p.227 |
多相性の意味は、 「1個の変数を使って様々なクラスのオブジェクトを参照し、動的束縛により、 参照されるオブジェクトのクラスによって、実装されたメソッドが自動的に呼び出されること」 | p.229 |
オブジェクト指向言語には クラス、オブジェクト、カプセル化、継承、多相性の概念をサポートしなければならない。 | p.229 |
多相性を利用した 製図プログラム 製図プログラムを作るとする。 三角形、円形、長方形クラスがあり、これらは全て図形クラスの派生クラスとする。 これら3つのクラスを配列に入れたいが、配列の要素は全て同じ型でなければならない、一体どうするか。 それは、親クラスである、図形型クラスで定義してやればOK♪ 次に、それぞれのクラスがどういう動きをするか実装してやる。 これにはオブジェクト自身に自分を描くコードを実装してやれば良い。 ということは、3つのクラスに共通のメソッドが必要になるので、 この実装は親クラスの図形クラスで実装してやれば良い。 但し、直接図形クラスを使って、描く動作は行わないので、このメソッドはabstract宣言する。 そして、子クラス3つそれぞれでoverrideしてやれば、 動的束縛によって、個々の配列要素に格納されている図形オブジェクトの種類が判定され、 それぞれ適切な描画メソッドが自動的に呼び出される。 /////リスト6-2/////////////////////////////////////////////////////////// using System; public abstract class 図形 { public abstract void DrawYourself(); } public class 三角形 : 図形 { public override void DrawYourself() { Console.WriteLine(" * "); Console.WriteLine(" * * "); Console.WriteLine(" * * "); Console.WriteLine(" * * "); Console.WriteLine(" * * "); Console.WriteLine("* *"); Console.WriteLine(" ~~~~~~~~~ "); } } public class 円形 : 図形 { public override void DrawYourself() { Console.WriteLine(" *** "); Console.WriteLine(" * * "); Console.WriteLine(" * * "); Console.WriteLine(" * * "); Console.WriteLine(" * * "); Console.WriteLine(" *** "); } } public class 長方形 : 図形 { public abstract void DrawYourself() { Console.WriteLine(" ****** "); Console.WriteLine(" * * "); Console.WriteLine(" ****** "); } } ////////////////////////////////////////////////////////////////////// リスト6-2とリスト6-3を繋げば動く。 /////リスト6-3//////////////////////////////////////////////////////// class ShapesTester { public static void Main() { 図形 my図形; my図形 = new 円形(); //動的束縛 my図形.DrawYourself(); my図形 = new 三角形(); my図形.DrawYourself(); my図形 = new 長方形(); my図形.DrawYourself(); } } ////////////////////////////////////////////////////////////////////// リスト6-3を削除し、リスト6-2と繋げる。 /////リスト6-4//////////////////////////////////////////////////////// public class DrawingEngine { public 図形 [ ] 図面を作る() { int 図形の総数 = 0; int 図形のカウンタ = 0; string choice; 図形 [ ] 図面; //親クラスで配列要素の定義 Console.Write("図面にいくつの図面を入れますか?"); 図形の総数 = Convert.ToInt32(Console.ReadLine()); 図面 = new 図形[図形の総数]; //親クラスで配列要素の定義 do { Console.Write("図形を選んでください: C)円形 R)長方形 T)三角形:"); choice = Console.ReadLine().ToUpper(); switch(choice) { case "C": 図面[図面カウンタ] = new 円形(); break; case "R": 図面[図面カウンタ] = new 長方形(); break; case "T": 図面[図面カウンタ] = new 三角形(); break; default: Console.WriteLine("選択が無効です"); 図面カウンタ--; break; //defaultにもbreak書くんだね! } 図形カウンタ++; }while(図形カウンタ < 図形の総数); return 図面 } public void 図面を描く(図形 [ ] 図面) { for (int i = 0; i < 図面.Length; i++) { 図面[i].DrawYourself(); } } } ////////////////////////////////////////////////////////////////////// リスト6-5は、リスト6-2とリスト6-4のデモ用 /////リスト6-5//////////////////////////////////////////////////////// class TestDrawingEngine { public static void Main() { 図形 [ ] myDrawing; DrawingEngine myCAD = new DrawingEngine(); string choice; do //ユーザーの入力次第で、繰り返しを行うコードは、switch文を使うんだね! { Console.WriteLine("製図の準備をします"); myDrawing = myCAD.図面を作る(); do { Console.WriteLine("図形を描きます\n"); myCAD.図面を描く(myDrawing); Console.Write("\nもう一度見たいですか? Y)es N)o"); choice = Console.ReadLine().ToUpper(); }while(choice != "N"); Console.Write("別の図面を作りますか? Y)es N)o"); choice = Console.ReadLine().ToUpper(); }while(choice != "N"); } } ////////////////////////////////////////////////////////////////////// 多相性と継承を用いることによって、ある基底クラスから派生したクラスのオブジェクトが何なのか 知らなくても、共通する名前の抽象メソッドを呼び出せば、 そのオブジェクトの型に一致したメソッドが自動的に呼び出される。 これは、ソフトウェアの書き方に変革をもたらしたらしい。 | p.229 |
型情報の喪失 図形 my図形 = new 長方形(); と書くと、ソースコードの中でmy図形を見る限り、どの型のオブジェクトを参照しているか見分けがつかない。 つまり、長方形オブジェクトはアイデンティティ(独自性)を失う。 my図形に長方形オブジェクトが入っているとしても、 (長方形オブジェクトの中にHeightプロパティがあるとして)次のコードはエラーになる。 my図形.Height = 20.4 //無効 なぜならコンパイル時には、どのオブジェクトを参照するのか、決められないから。 コンパイル時には、図形クラスで定義した関数しか呼び出すことを認めない。 必要とされるのが、図形クラスで定義されたメンバーであれば、問題ないが、 もし長方形クラス特有の関数メンバーを呼び出す必要があるとしたら、どうすれば良いか? クリアするために、次のように書いてみると、 double totalHeight; for (int i=0;i < 図面.Length;i++) { totalHeight += 図面[i].Height; //無効 } しかし、失敗に終わる。。図面配列の要素の型は図面型であり、Heightプロパティ呼び出しをサポートしないから。 (図面クラスの中にはHeightプロパティが無いから) もし長方形型に戻すことができたなら、上のようにHeightプロパティを呼び出せそうだ。 これには、is演算子(キャストとの組み合わせが必要)と、as演算子が使える。 | p.236 |
継承を使うことで、1つのクラスに属する様々なクラスを作ることができる。 作られた様々な派生クラスをひとまとめにして扱いたい、例えば配列に収めたいとき、 個々の派生クラス単位では、もちろん型が異なるので、配列宣言のとき型宣言がそのままじゃできない。 そこで、派生クラスの基底クラスを配列の型宣言に用いる。 但し、配列で使う処理が基底クラス内で定義されているメソッド等を扱うだけなら問題ないが、 派生クラスで定義された処理を使いたいとき問題がある。 例えば 図形 my図形; で宣言された変数に、どんなオブジェクトが入るかコンパイル時では判断が付かなく、 派生クラスの中のメソッド等を使おうとすると、コンパイルエラーを起こしてしまう。 これをクリアするには、どのオブジェクト参照しているか判断するコードを書き、 判断の後に、個々の派生クラス内のメソッド等を呼び出すコードを書いてやらないといけない。 | -- |
is演算子を使うと、変数が特定の型のオブジェクトを参照しているかどうか判定できる。 判定の結果は1個のbool値であり、下のコードの場合、 (my図形 is 長方形) my図形が長方形オブジェクトを参照していたらtrueを、違うならfalseを返す。 そして、my図形が長方形オブジェクトを参照したら、次はmy図形を長方形型の変数に変換したい (長方形クラスで定義されたメソッド等を扱いたいため) そのためには、キャスト演算子を用いる。 | p.237 |
Car myCar = new FamilyCar(); Car型で宣言された変数に、FamiliyCarオブジェクトを代入している。 このような種類の代入には、「アップキャスト」が必要とされる。 逆に祖先型の変数を子孫型にキャストする場合は、「ダウンキャスト」と呼ぶ。 但し、ダウンキャストには問題が多く、例えば下のように単純に書くことができない。 FamilyCar myFamilyCar = myCar; //myCarの中身が、FamilyCarオブジェクト以外かもしれないから無効。 この問題は、myCarをFamilyCarオブジェクトとして明示的にキャストすることでクリアできる。 FamilyCar myFamilyCar = (FamilyCar) myCar; この方法を、is演算子でオブジェクトの判定をして、trueが返されたときの後の処理に用いれば、 派生クラスで定義されたメソッド等を使用することができる。 アップキャストの場合、明示的なキャスト演算子はいらないが、 ダウンキャストの場合、明示的なキャスト演算子が必要となる。 ////////////////////////////////////////////////////////////////////// using System; abstract public class 図形 { public abstract void DrawYourself(); } public class 長方形 : 図形 { private double height; public 長方形(double initialHeight) { height = initialHeight; } public override void DrawYourself() { Console.WriteLine("長方形を書きます"); } public double Height { get { return height; } set { height = value; } } } class 円形 : 図形 { public override DrawYouself() { Console.WriteLine("円形を描きます"); } } class Tester { private static 図形[ ] 図面; public static void Main() { 長方形 my長方形; double totalHeight = 0; //高さの合計 図面 = new 図形[3]; 図面[0] = new 長方形(10.6); 図面[1] = new 円形(); 図面[2] = new 長方形(30.8); for (int i=0;i < 図面.Height;i++) { if (図面[i] is 長方形) //is演算子の具体的使われ方 { my長方形 = (長方形)図面[i]; //オブジェクトのキャスト(ダウンキャスト) totalHeight += my長方形.Height; } } Console.WriteLine("四角形の高さの合計:{0}",totalHeight); } } ////////////////////////////////////////////////////////////////////// | p.237 |
is演算子とキャスト演算子の使われどき 型情報の喪失を伴うアップキャストを行って良いのは、失われた型情報があとで必要にならない場合に限る。 また、プログラムの大部分では、is演算子とキャスト演算子を使う必要はないはず。 もしis演算子とキャスト演算子が多く使われていたら、設計に欠陥があるはず。 | p.240 |
先ほどの上コードでは、
| p.240 |
System.Objectは、あらゆるクラスの祖先クラス。
基底クラスを作成する時、C#はSystem.Objectが基底クラスとして自動的に指定される。
なので、全てのクラスはSystem.Objectが持つクラスメンバーを継承する。
objectは、System.Objectの別名(エイリアス)
| p.241 |
objectクラスのメンバをイジる ////////////////////////////////////////////////////////////////////// using System; namespace Animals { class Dog { private string name; public Dog() { name = "不明"; } public Dog(string initialName) { name = initialName; } public string Name { get { return name; } } public override string ToString() { return "犬の名前: " + name; } public override bool Equals(object obj) { Dog tempDog = (Dog) obj; //ダウンキャスト if(tempDog.Name == name) return true; else return false; } } } class ObjectTester { public static void Main() { Animals.Dog myDog = new Animals.Dog("ポチ"); Animals.Dog yourDog = new Animals.Dog("ポチ"); Cosole.WriteLine(myDog); //myDogのToStringを自動的に呼び出す //ここでは、どのクラスもToStringメソッドを継承しているため、 //このような書き方ができる。 if (myDog.Equals(yourDog) Console.WriteLine("myDogとyourDogは名前が同じです"); else Console.WriteLine("myDogとyourDogは名前が違います"); } } 結果::: 犬の名前: ポチ myDogとyourDogは名前が同じです ////////////////////////////////////////////////////////////////////// ※上コードでは、Equalsメソッドをオーバーライドしたが、本来ならば Equalsメソッドをオーバーライドするクラスは、同時にGetHashCodeメソッドもオーバーライドして、 そのオブジェクトをハッシュ表コレクションの中で識別するための ユニークな数値を返すようにしなければいけない。 これを省略すると、警告が発せられる。 | p.243 |
関数(メソッド、プロパティ、インデクサ)が基底クラスでvirtual宣言されていなくても、 派生クラスでそれと同じシグネチャとreturn型を持つ関数を書くことができる。 このとき勿論、動的束縛は動作しない。 この派生クラスで定義した新しい関数は、基底クラスの関数をオーバーライドせず、隠すと言われる。 下コードに、基底クラスのメソッドを、オーバーライドするのと、隠すのとで、どう違うか示す。 ////////////////////////////////////////////////////////////////////// using System; class Car { public virtual void 前進する() { Console.WriteLine("自動車を1キロメートル前進させます"); } public void 後退する() { Console.WriteLine("自動車を50メートル後退させます"); } } class FamilyCar : Car { public override void 前進する() { Console.WriteLine("ファミリーカーを5キロメートル前進させます"); } public new void 後退する() //基底クラスのメソッドを隠す時newが必須 { Console.WriteLine("ファミリーカーを200メートル後退させます"); } } class Tester { public static void Main() { Car myCar; myCar = new FamilyCar(); myCar.前進する(); //FamilyCar型オブジェクトからメソッドが呼び出される。 myCar.後退する(); //基底クラスのメソッドがvirtual宣言されておらず、 //動的束縛が行われず、FamilyCar型オブジェクトから呼び出されない。 } } 結果::: ファミリーカーを5キロメートル前進させます。 自動車を50メートル後退させます ////////////////////////////////////////////////////////////////////// ようは、動的束縛を行わせるか、そうさせないかの違い。 また、関数を隠す時は、キーワードnewを入れなければいけない。 (新しいオブジェクトを実体化するのに使うnewとは意味が異なる) newを入れない限り、virtual宣言されない関数はデフォルトで、非仮想メソッドになる。 | p.245 |
どうしてvirtual宣言しない関数のデフォルトは非仮想メソッドなのか どうして派生クラスでoverrideを書かないと、基底クラスの同じシグネチャ関数をオーバーライドしないのか 基底となるクラスに変更があった場合、 例えば、.NET frameworkのクラスライブラリがアップグレード(変更)された時、 派生クラスでは困ったことになり得る恐れがある。 下コードが基底クラス変更前とすると、 ////////////////////////////////////////////////////////////////////// using System; class Rocket { //Rocketが基底クラスとして使っているクラスライブラリとする private int age = 0; public int Age { get { return age; } set { age = value; } } } class SpaceShuttle : Rocket { //ある会社では、Rocketクラスを利用してこのクラスを書いているとする private int distanceTravelled = 0; public int DistanceTravelled { get { return distanceTravelled; } } public void MoveForward(int 前進距離) { distanceTravelled += 前進距離; } } class Tester { public static void Main() { SpaceShuttle columbia = new SpaceShuttle(); columbia.MoveForward(30); Console.WriteLine("移動距離: {0}",columbia.DistanceTravelled); } } ////////////////////////////////////////////////////////////////////// このコードに使っている基底クラスである、Rocketクラスがバージョンアップして、 下のコードが加わってしまうと、どうなってしまうか? public virtual void MoveForward(int daysAdded) { age += daysAdded; } Rocketクラスを使っているユーザは、Rocketクラスがバージョンアップしたことを知らされていない。 そして、もし「基底クラスの仮想メソッドは、同じシグネチャを持つ派生クラスのメソッドによって、 自動的にオーバーライドされる」という仕組みだったらば、 SpaceShuttleクラスのMoveForwardメソッドは、RocketクラスのMoveForwardメソッドをオーバーライドする。 これは間違いであって、バグになりかねない。 (SpaceShuttle型オブジェクトを参照するRocket型変数から、MoveForwardメソッドが呼び出されると、 動的束縛が働き、SpaceShuttleのMoveForwardメソッドが呼び出されてしまうから) なので、override宣言されていない場合、勝手にオーバーライドしないような仕組みになっている。 C#の場合、派生クラスでoverrideもnewも書かれていない、基底クラスにある関数と同じシグネチャを持つ関数が あったならば、コンパイラが警告を出して教えてくれる。 overrideを書かないと、デフォルトでnew関数(メソッドの場合隠すメソッド)になる。 | p.247 |
仮想関数は、非仮想関数と比べ実行が遅くなる。 ⇒仮想メソッドの場合、どの実装を実行するのかをランタイムが動的に決定するのに、 少しCPUの処理能力を費やすから。 これが非仮想関数がデフォルトに設定されている理由の1つ。 | p.250 |
C#での多重継承は禁止 ある種類の自動車は、ジープであると同時にファミリーカーであるかもしれない。 複数のクラスから継承(多重継承)を許す場合、どちらの基底クラスにもToStringがあったとき、 派生クラスはどちらを継承すれば良いのだろうか。 という問題に対し、C#では多重継承を禁止し、代わりにインターフェイスという言語構造を提供する。 | p.251 |
抽象クラスから派生するクラスは、 「図形クラスの派生クラスである私は、図形クラスに抽象メソッドが含まれている場合、 メソッドをすべて実装(override)するか、あるいは私自身が抽象クラスになることを誓います」 ということを保証する。 この保証によって動的束縛を通じた呼び出しが可能になる。 | p.252 |
インターフェイスは、クラスが実装しなければならないプロパティやメソッドの名前や型だけを定義したものだ。
1つのグループにしたいクラス郡が、共通の祖先クラスを持っていないときに便利。例p.253
<能力>
| p.252 |
インターフェイスの定義(書き方)
| p.254 |
インターフェイスを実装する
| p.256 |
インターフェイスを使った総称的プログラミング ソートについて考える。 「総称的に」というのは「もしオブジェクトxがオブジェクトyよりも大きければ」という表現。 ソートする対象となるオブジェクトの型に依存せず、あらゆる型データをソートするルーチンを インターフェイスを使えば書ける。 つまり、その実装はどの型のオブジェクトのソートに再利用できる。 下コードはインターフェイスを使わない場合 ////インターフェイスを使わない従来のバージョン//////////////////////// //配列の要素を昇順にソートする public static void BubbleSortAscending(int [ ] bubbles) { bool 交換した = ture; for(int i=0; 交換した; i++) { 交換した= false; for(int j=0; j < (bubbles.Length - (i + 1); j++) { if(bubbles[j] > bubbles[j + 1]) { Swap(j, j+1, bubbles); 交換した = true; } } } } //配列の2つの要素を交換する public static void Swap(int first , int second , int [ ] 配列) { int temp; temp = 配列[first]; 配列[first] = 配列[second]; 配列[second] = temp; } ////////////////////////////////////////////////////////////////////// 上コードでは、要素がint型である配列に限られる。 つまり、他の型の要素を持つ配列をソートしたい場合、また別の専用バブルソートメソッドを作らないといけない。 これでは効率が悪い。 次のコードでは、総称的バブルソートメソッドの仮パラメータをIComparable型の配列として宣言することで、 IComparableインターフェイスを実装するクラスならどれでも、このソートメソッドでソートできる。 /////インターフェイスを実装した再利用可能なコード///リスト6-13//////// using System; public interface IComparable { int CompareTo(IComparable comp); } public class 時間 : IComparable { private uint 総秒数; public 時間() { 総秒数 = 0; } public 時間(uint 初期秒数) { 総秒数 = 初期秒数; } public uint Seconds { get { return 総秒数; } set { 総秒数 = value; } } ★ public virtual int CompareTo(IComparable comp) //virtualの役割は後に紹介 { 時間 compareTime = (時間) comp; if(総秒数 > compareTime.Seconds) return 1; else if(compareTime.Seconds == 総秒数) return 0; else return -1; } } class Sorter { //配列内の比較可能な要素を昇順にソートする public static void BubbleSortAscending(IComparable [ ] bubbles) { bool 交換した = true; for(int i=0; 交換した; i++) { 交換した = false; for(int j=0; j < (bubbles.Length - (i + 1)); j++) { if(bubbles[j].CompareTo(bubbles[j + 1] > 0) { Swap(j,j+1,bubbles); 交換した = true; } } } } //配列の2つの要素を交換する public static void Swap(int first, int second, IComparable [ ] 配列)//配列は参照渡し { IComparable temp; temp = 配列[first]; 配列[first] = 配列[second]; 配列[second] = temp; } } class Tester { public static void Main() { 時間 [ ] raceTimes = 時間[4]; raceTime[0] = new 時間(153); raceTime[1] = new 時間(165); raceTime[2] = new 時間(108); raceTime[3] = new 時間(142); Sorter.BubbleSortAscending(raceTimes); Console.WriteLine("ソートされたタイムのリスト"); foreach (時間 time in raceTimes) { Console.WriteLine(time.Seconds); } } } 結果::: ソートされたタイムのリスト 108 142 153 165 ////////////////////////////////////////////////////////////////////// | p.259 |
インターフェイスの階層構造を構築する 既存のインターフェイスAは、インターフェイスBでインターフェイスAを実装することで拡張できる。 すると、インターフェイスBは、インターフェイスAのメンバーと、B自身で定義したメンバーを含む。 このインターフェイスBを実装するクラスは、インターフェースAのメンバーと、Bのメンバーを実装することになる。 例えば、 interface IComparableAdvanced : IComparable { bool GreaterThan(IComparableAdvanced comp); bool LessThan(IComparableAdvanced comp); } IComparableAdvancedを実装するクラスは、IComparableのメンバーCompareToも実装することになる。 | p.263 |
インターフェイス変換 時間クラスのオブジェクトがインターフェイスIを実装している時、 明示的なキャストを使わずに、インターフェイスIの変数に代入できる。 (これは基底クラスと派生クラスの関係と同じ) IComparable icTime = new 時間(392); 逆にダウンキャストの場合、 時間 myTime = (時間) icTime; ここで、icTimeが時間オブジェクトを参照していなければ、例外が発生する。 これもクラスのときと同様に、isおよびas演算子を使って、 変換を行う前にicTimeが時間型であることを確認できる。 <is演算子を使う例> 時間 myTime; if(icTime is 時間) myTime = (時間) icTime; else Console.WriteLine("icTimeをmyTimeに代入できませんでした"); <as演算子を使う例> 時間 myTime; myTime = icTime as 時間; if(myTime == null) Console.WriteLine("icTimeをmyTimeに代入できませんでした"); この文脈でも、as演算子のほうが、is演算子より効率が良い。 ときにはキャストを実行することなく、 インターフェイス型の変数に入っているオブジェクトの型だけをチェックしたい場合もある。 そのときにはas演算子よりもis演算子が適している。(これもクラスの時と同様) | p.263 |
インターフェイスのvirtual実装をオーバーライドする クラスがインターフェイスから実装する関数は、オプションでvirtual宣言できる。 リスト6-13の★行で実際に、virtual宣言が行われている。 時間の派生クラスでは、このvirtualメソッドを ・通常の方法でオーバーライドするか(下コードが例) ・newで新しい実装(隠す)できる ////////////////////////////////////////////////////////////////////// public class 時間Advanced : 時間 { public override int CompareTo(IComparable comp) { 時間 compareTime = (時間) comp; if(base.Seconds > compareTime.Seconds) { if(base.Seconds > (compareTime.Seconds + 50)) //基底クラスのプロパティにアクセス return 2; else return 1; } else if(base.Seconds < compareTime.Seconds) { if(base.Seconds < (comapreTime.Seconds - 50)) return -2; else return -1; } else { return 0; } } } ////////////////////////////////////////////////////////////////////// | p.264 |
インターフェイスを明示して関数を実装する 時間クラスでCompareToメソッドを実装した時、 IComparableインターフェイスによって指定されたCompareToメソッドを実装していることは、 コンパイラで暗黙的了解されている。 どのインターフェイスを実装しているか明らかにする必要はなく、 インターフェイスの抽象関数と同じシグネチャ、return型、アクセス修飾子を持つ関数ヘッダを、 そのインターフェイスに実装するクラスに入れるだけで良い。 しかし、2つのインターフェースを実装していて、両方に同じシグネチャを持つ抽象関数が含まれていたら、 どちらを実装したいのか、コンパイラが混乱してしまう。 下コードがその例。 interface IDrawable { void DrawYourself(); } interface IPrintable { void DrawYourself(); } public class SpaceShip : IDrawable, IPrintable { public void DrawYourself() //どちらのインターフェースのメソッド実装か判定できない { } } この問題を解決するには、 片方または両方の実装に「明示的実装」を使えば良い。 それには次のように、関数名の前にインターフェイス名を書けば良い。 public class SpaceShip : IDrawable, IPrintable { void IDrawable.DrawYourself() //明示的実装は、暗黙のうちにpublic宣言 { //このメソッドは、IDrawableのメソッドを実装する } public void DrawYourself() { //このメソッドは、IPrintableのメソッドを実装する } }
| p.265 |
明示的実装の他用途 明示的実装は、インターフェイス間のメソッドシグネチャ衝突を解決する以外にも役立つことがある。 例えば、特定のメソッドをオブジェクトを通じて呼び出したくなく、 動的束縛でのみ呼び出せるようにしたいとき。 それには次ぎのように定義すれば良い。 public class 時間 //←実装インターフェイスリストは?? { int IComparable.CompareTo(IComparable comp) { } } すると、次のコード片の3行目は無効になるが、 時間 myTime = new 時間(); 時間 yourTime = new yourTime(); ...myTime.CompareTo(yourTime)...; 次のコードは有効 IComparable icTime = myTime; ...icTime.CompareTo(yourTime)...; | p.267 |
明示的実装の「再実装」 インターフェイスの明示的実装をvirtual宣言できないので、 派生クラスは、その関数をオーバーライドできず、「再実装」しなければならない。 | p.267 |
p. | |
p. | |
p. |