実際にプログラムを作成していると、newキーワードによるメソッドの置き換えは生ぬるいと思うようになってくる。せっかく置き換えても、スーパークラスのデータ型で宣言された変数を通すと利用されない、というような制限があっては使いにくいことこのうえない。置き換えた以上は、どんなデータ型を通じて利用しようと、置き換えたメソッドが呼び出されてほしいと思うのは当然の成り行きだろう。
そのためには、newキーワードで置き換えるのではなく、virtualキーワードとoverrideキーワードを用いて、別の方法で置き換える機能が用意されている。
実際に記述した例がList 4-3である。
1: using System;
2:
3: namespace Sample003
4: {
5: public class Person
6: {
7: public virtual string getName()
8: {
9: return "私には名前がありません。";
10: }
11: }
12:
13: public class Taro : Person
14: {
15: public override string getName()
16: {
17: return "私の名前は太郎です。";
18: }
19: }
20:
21: class Class1
22: {
23: [STAThread]
24: static void Main(string[] args)
25: {
26: Person person = new Person();
27: Console.WriteLine( person.getName() );
28: Taro taro = new Taro();
29: Console.WriteLine( taro.getName() );
30: Person someone = new Taro();
31: Console.WriteLine( someone.getName() );
32: }
33: }
34: }
このサンプル・ソースのポイントは、7行目と15行目のメソッド宣言にある。7行目のメソッド宣言には、virtualというキーワードが増えている。そして、15行目にはnewではなく、overrideというキーワードが書き込まれている。違いとしてはそれだけである。ところがこれを実行すると違う結果になる。Fig.4-4を見てほしい。
実行結果の最後の行がList 4-2ではPersonクラスのgetNameの結果であったものが、この例ではTaroクラスのgetNameの結果となっている。つまり、virtualとoverrideというキーワードを用いると、オブジェクトにアクセスする変数の型とは関係なく、置き換えたメソッドが呼び出されるようになったわけである。
ここでポイントになるのは、置き換える側だけでなく、置き換えられる側にも常にvirtualというキーワードを付けなければならない点だ。もしかしたら置き換えられるかもしれないメソッドには、あらかじめvirtualキーワードを付けておく必要がある。それをやっておかないと、overrideキーワードを付けることができない。virtualキーワードなしでoverrideキーワードだけ付けてもコンパイル・エラーになってしまう。
さて、ここで気になることは、メソッドを置き換える方法に、どうして2種類の方法が存在するのか、ということだ。その理由は処理効率にある。virtualキーワードによる置き換えは、強力で使いやすいが、いったいどんなメソッドが呼び出されるか、実行してみないと分からないということも多い。それに対して、newキーワードによる置き換えは、変数のデータ型によって呼び出されるメソッドが分かるため、コンパイラにも容易に呼び出し先が推測可能である。これにより、さまざまな最適化機能を適用することができ、処理速度を上げることが可能になるのである。そのため、特に必要とされないかぎり、virtualキーワードは使わないのが普通である。しかし、機能的な要請によりvirtualキーワードが不可欠というケースも存在するので、virtualキーワードは不要というわけでもないのである。
ここまでのサンプル・ソースは、すべてPersonクラスを継承してTaroクラスを得ているが、もちろん、Personクラスは抽象的な人間を代表させるクラスで、Taroクラスは具体的な太郎さんについての情報を持つクラスとしたいわけである。さらに、実際に使うとなれば、Personクラスを継承するTaroクラスだけでなく、HanakoクラスやJiroクラスも作りたいと考えることになる。そうすると、PersonクラスがgetName、つまり「名前をください」というメソッドを持つ意味はない。TaroクラスやHanakoクラスにだけgetNameメソッドがあればよい。しかし継承の活用という観点からいうと、PersonクラスにgetNameというメソッドがあるおかげで、Taroクラス、Hanakoクラス、Jiroクラスというクラスの種類に関係なく、getNameメソッドを呼び出すプログラムが書けるわけだ。
これを要約すれば、PersonクラスのgetNameメソッドはほしいけれど、中身はなくてもかまわないということである。実際に、抽象的な何かを実現するクラスを作る場合には、このような中身のないメソッドはよく必要になる。
実際に記述してみると、List 4-4のようになる。
1: using System;
2:
3: namespace Sample004
4: {
5: public abstract class Person
6: {
7: public abstract string getName();
8: }
9:
10: public class Taro : Person
11: {
12: public override string getName()
13: {
14: return "私の名前は太郎です。";
15: }
16: }
17:
18: class Class1
19: {
20: [STAThread]
21: static void Main(string[] args)
22: {
23: // 次の行はコンパイル・エラーになる
24: // 抽象クラスまたはインターフェース 'Sample004.Person' のインスタンスを作成できません。
25: // Person person = new Person();
26: Taro taro = new Taro();
27: Console.WriteLine( taro.getName() );
28: Person someone = new Taro();
29: Console.WriteLine( someone.getName() );
30: }
31: }
32: }
このソース・コードのポイントは、5行目と7行目に追加されたabstractキーワードである。12行目のoverrideキーワードはList 4-3と同じである。abstractとはつまり「抽象」ということである。7行目では、具体的な中身のないメソッドであるということを示すために書き込まれる。メソッドの処理内容を具体的に書かなくてもエラーにはならない。逆に処理内容を書き込むとエラーになってしまうのである。
5行目のabstractキーワードは、7行目のメソッド宣言とは違う意味を持つ。class宣言といっしょに用いられるabstractキーワードは、これが抽象クラスであるということを示す。抽象クラスとは、中身のない定義が含まれるクラスのことをいう。そのため、抽象クラスは継承には使えるが、インスタンスを作成することはできない。実際に、25行目のコメントの中身をソース・コードに書き込むと、「抽象クラスまたはインターフェイス 'ConsoleApplication5.Person' のインスタンスを作成できません。」というエラーになり、コンパイルできない。
実行結果はFig.4-5のようになるが、基本的には、virtualとoverrideキーワードを使った場合と同じである。ただ、Personクラスのインスタンスは作成できないため、出力される行数が1行少なくなっている。
Copyright© Digital Advantage Corp. All Rights Reserved.