クラスに継承関係があるとき、コンストラクタはややこしい問題を引き起こす。継承元のクラスに引数のあるコンストラクタがあり、継承先のクラスのコンストラクタに引数がないことも珍しくはない。そのような状況では、継承元のコンストラクタの引数に与える値を渡す手段がなくなってしまうのである。
この問題を解決するために、「: this」と似ている「: base」という構文がC#にはある。さっそく、これを使ったサンプル・ソースList 11-4を見てみよう。
1: using System;
2:
3: namespace Sample004
4: {
5: class ClassBase
6: {
7: public int x1,y1;
8: public ClassBase( int x, int y )
9: {
10: x1 = x;
11: y1 = y;
12: }
13: }
14: class ClassDerived : ClassBase
15: {
16: public int x2,y2;
17: public ClassDerived( int x, int y ) : base( x+1, y+1 )
18: {
19: x2 = x;
20: y2 = y;
21: }
22: }
23: class Class1
24: {
25: [STAThread]
26: static void Main(string[] args)
27: {
28: ClassDerived cd = new ClassDerived( 10, 20 );
29: Console.WriteLine(cd.x1);
30: Console.WriteLine(cd.y1);
31: Console.WriteLine(cd.x2);
32: Console.WriteLine(cd.y2);
33: }
34: }
35: }
ここでポイントになるのは、17行目のコンストラクタの宣言である。見て分かるとおり、普通のコンストラクタの宣言の後ろに「: base( x+1, y+1 )」という記述が付加されている。これは、継承元クラス(baseクラス)であるClassBaseのコンストラクタを呼び出すことを意味する記述である。そして引数には、「x+1」と「y+1」という2つの式が書かれているが、もちろん引数として渡されるのはこの式の計算結果であり、整数が2個渡されることになる。これに該当するClassBaseのコンストラクタは8〜12行目のものであり、これが実行される。
この流れを数値の変化として見るなら、28行目で「new」の引数として指定された数値は「10」と「20」である。もちろん、17〜21行目のコンストラクタには、これらは10と20として伝達される。しかし、「: base( x+1, y+1 )」という記述により、8〜12行目のコンストラクタに伝達される引数は11と21になる。その結果がFig.11-4で確認できることと思う。
単純にクラス内のメンバ変数に初期値を入れるだけなら、コンストラクタを使うのではなく、直接初期値を指定する方法もある。例えば、「public int x;」ではなく、「public int x = 1;」と初期値を指定すれば、インスタンスが生成されるときにその値が自動的に代入される。List 11-5がそれを見るためのサンプル・ソースである。
1: using System;
2:
3: namespace Sample005
4: {
5: class Test1
6: {
7: public int x = 1;
8: public Test1()
9: {
10: Console.WriteLine( x );
11: }
12: }
13: class Test2
14: {
15: public int x;
16: public Test2()
17: {
18: Console.WriteLine( x );
19: x = 2;
20: Console.WriteLine( x );
21: }
22: }
23: class Test3
24: {
25: public int x = 1;
26: public Test3()
27: {
28: Console.WriteLine( x );
29: x = 2;
30: Console.WriteLine( x );
31: }
32: }
33: class Class1
34: {
35: [STAThread]
36: static void Main(string[] args)
37: {
38: Test1 t1 = new Test1();
39: Test2 t2 = new Test2();
40: Test3 t3 = new Test3();
41: }
42: }
43: }
5〜12行目のクラスは単純なメンバ変数の初期化の例、13〜22行目はコンストラクタ内で代入した場合の例である。これを見比べると、前者のほうがシンプルで分かりやすい。しかし、両方同時に使ったら何が起こるのだろうか? その疑問に答えるために、もう1つのクラスを用意した。23〜32行目は、メンバ変数の初期化とコンストラクタ内での代入の双方を行った場合の例である。
これを実行した結果はFig.11-5のようになる。
Fig.11-5の最初の数値は10行目で出力しているものである。これにより、コンストラクタ実行時にはすでにメンバ変数の値は初期化済みであることが分かる。次の2つの数値は、18行目と20行目によるものだ。18行目の時点ではデフォルト値であるゼロが入ったままで、19行目の代入によって、やっと変数の値が意図した値に変わったことが分かる。最後の2行は、28行目と30行目によるものだ。これにより、コンストラクタ実行時点ではすでに25行目で指定した値である1に初期化済みであることが分かる。コンストラクタ内の代入文(29行目)は、この値への上書きとして機能していることが分かる。
つまり、メンバ変数の初期化はコンストラクタより先に実行されるので、コンストラクタで代入した値のほうが後々まで残るということである。
ここまでのサンプルでは、すべてコンストラクタにpublicキーワードを付けてきた。もし、これにprivateキーワードを付けると何が起こるのだろうか? 結論としては、コンストラクタは外部からいっさい呼び出し不可能になる。いい換えれば、newを使ったインスタンスの生成がいっさい不可能になることと等しい。
これにより、インスタンスが生成できないクラスができてしまうわけだが、こんなクラスにも立派な用途がある。例えば、staticなメソッドだけを提供するクラスを作ったとしよう。このクラスのインスタンスを生成することは無意味である。だが、勘違いしたプログラマーが、インスタンスを生成しようと試みるかもしれない。そのとき、privateなコンストラクタを付けておけば、インスタンス生成を行うソース・コードはエラーになり、その時点でプログラマーに間違ったことに気づくチャンスを与えることができる。
List 11-6は実際に、privateなコンストラクタを使った例である。
1: using System;
2:
3: namespace Sample006
4: {
5: class Class2
6: {
7: private Class2() {}
8: public static void special( string message )
9: {
10: Console.WriteLine( message );
11: }
12: }
13: class Class1
14: {
15: [STAThread]
16: static void Main(string[] args)
17: {
18: Class2.special("Hello!");
19: // Class2 t = new Class2(); // 'Sample006.Class2.Class2()' はアクセスできない保護レベルになっています。
20: }
21: }
22: }
ここで注意すべきことは、18行目のstaticなメソッド呼び出しは問題なく実行可能であるが、19行目のnewは実行できないことである。19行目先頭のコメント記号(//)を取り去ると、コンパイル時に「'Sample006.Class2.Class2()' はアクセスできない保護レベルになっています。」というエラーが発生する。
これを実行した結果がFig.11-6である。
Copyright© Digital Advantage Corp. All Rights Reserved.