C#には、staticなメソッドやメンバ変数が存在するが、コンストラクタにもstaticなものが存在する。staticなコンストラクタは、そのクラスのインスタンスが生成されたり、staticメソッドなどが呼び出されたりする前に自動的に呼び出されるコンストラクタである。ただし、いつ呼び出されるかは厳密に規定されていないので、プログラムの実行開始時に呼び出されるという保証はない。しかし、staticなメソッドやstaticなメンバ変数にアクセスされる前には必ず呼ばれるので、これらの初期化や準備のために使用することができる。
実際にstaticなコンストラクタを記述したサンプル・ソースをList 11-7に示す。
1: using System;
2:
3: namespace Sample007
4: {
5: class Class2
6: {
7: static Class2()
8: {
9: Console.WriteLine("static Class2() called");
10: }
11: static public void special()
12: {
13: Console.WriteLine("static public special() called");
14: }
15: }
16: class Class1
17: {
18: [STAThread]
19: static void Main(string[] args)
20: {
21: Class2.special();
22: }
23: }
24: }
これを実行した結果がFig.11-7である。
21行目で11〜14行目のメソッドを呼び出しているが、それに先だって7〜10行目のstaticコンストラクタが実行されていることが、Fig.11-7から分かるだろう。
なお、staticなコンストラクタには、publicやprivateのようなアクセス制御キーワードは付けない。これは、暗黙のうちに実行する以外の選択肢がないので、それを明示的にコントロールしても意味がないためだ。
デストラクタとは、コンストラクタとは逆の役割を持った機能である。つまり、インスタンスが消滅する際に呼び出されるメソッドである。
C++のようなインスタンスの寿命をプログラマーが厳密に管理するプログラミング言語では、インスタンスの後処理を行うのに便利な機能なのだが、C#はインスタンスの寿命をシステムが自動的に管理するため、デストラクタがいつ呼び出されるか予測不可能である。例えば、C++なら、デストラクタの内部に、開いたファイルを閉じる処理を記述することも可能だが、C#で同じように記述すると、いつファイルを閉じるか予測不可能ということになり、トラブルのもとになりかねない。その意味で、C#のデストラクタは出番があまりないと思われるが、一応紹介しておく。
List 11-8はデストラクタを使ったサンプル・ソースである。
1: using System;
2:
3: namespace Sample008
4: {
5: class Class2
6: {
7: ~Class2()
8: {
9: Console.WriteLine("Class2's destructor called");
10: }
11: }
12: class Class1
13: {
14: static private void test()
15: {
16: Console.WriteLine("static private void test() called");
17: Class2 t1 = new Class2();
18: Console.WriteLine("static private void test() done");
19: }
20: [STAThread]
21: static void Main(string[] args)
22: {
23: Console.WriteLine("static void Main(string[] args) called");
24: test();
25: Console.WriteLine("static void Main(string[] args) done");
26: }
27: }
28: }
これを実行した結果がFig.11-8である。
この例では、17行目で生成されるClass2クラスのインスタンスは15〜19行目のブロックの内部で宣言された変数t1を経由してしか参照できないので、メソッドtestの実行が終わればインスタンスは用済みに思える。これがC++のようなプログラミング言語なら、ここでデストラクタを実行させることができるのだが、C#では動作が異なっている。Fig.11-8を見て分かるとおり、デストラクタが実行されるのは、メソッドtestの実行が終わり、さらにメソッドMainの実行が終わった後ということになっている。もちろん、必ずこのタイミングでデストラクタが実行される保証はない。デストラクタは、いつ実行されるか分からないものなのだ。
Column - 確実な終了処理 -
コンストラクタとデストラクタの話題からは外れるが、確実な終了処理について少々補足を加える。ある範囲の実行を終えた時点で確実に何かの終了処理を実行させたい場合は、例外処理を使うのが普通である。例外処理については、この先で詳しい説明をするので、ここでは概略だけ述べる。以下が、例外処理を用いてList 11-8のサンプル・ソースを書き直してみたものである。
1: using System;
2:
3: namespace Sample009
4: {
5: class Class2
6: {
7: public void close()
8: {
9: Console.WriteLine("public void close() called");
10: }
11: }
12: class Class1
13: {
14: static private void test()
15: {
16: Console.WriteLine("static private void test() called");
17: Class2 t1 = new Class2();
18: try
19: {
20: // ここに何かの処理があると想像されたい
21: }
22: finally
23: {
24: t1.close();
25: }
26: Console.WriteLine("static private void test() done");
27: }
28: [STAThread]
29: static void Main(string[] args)
30: {
31: Console.WriteLine("static void Main(string[] args) called");
32: test();
33: Console.WriteLine("static void Main(string[] args) done");
34: }
35: }
36: }
このソースのポイントは、終了処理を行うメソッドであるcloseの呼び出し(24行目)を、finallyブロック(22〜25行目)の内部に置いた点である。finallyブロックは、その前のtryブロックが終わった時点で、どんな理由で終わったにせよ、必ず実行されることになっている。returnでメソッドから直接抜けようがほかの例外が発生しようが、どんな理由でも、tryブロックの実行が終了するときにfinallyブロックは実行される。そのため、20行目の処理がどんな結果に終わろうとも(プロセスそのものが異常終了でもしないかぎり)必ず24行目は実行される。
これを実行すると以下のようになる。
Column - IDisposableとusingによる終了処理 -
ちょっと難しいので、C#初心者が理解できなくても問題はない。IDisposableというインターフェイスと、usingステートメントを使うと、確実な終了処理を記述できる。以下がそのサンプル・ソースである。
1: using System;
2:
3: namespace Sample010
4: {
5: class Class2: IDisposable
6: {
7: ~Class2()
8: {
9: Console.WriteLine("Class2's destructor called");
10: }
11: void IDisposable.Dispose()
12: {
13: Console.WriteLine("void IDisposable.Dispose() called");
14: }
15: }
16: class Class1
17: {
18: static private void test()
19: {
20: Console.WriteLine("static private void test() called");
21: using(Class2 t1 = new Class2())
22: {
23: // ここに何かの処理があると想像されたい
24: }
25: Console.WriteLine("static private void test() done");
26: }
27: [STAThread]
28: static void Main(string[] args)
29: {
30: Console.WriteLine("static void Main(string[] args) called");
31: test();
32: Console.WriteLine("static void Main(string[] args) done");
33: }
34: }
35: }
まず、確実な終了処理をさせたいクラスで、IDisposableインターフェイスを実装する(5行目)。これにはDisposeメソッドしかないので、実装するのに手間はかからない(11〜14行目)。そして、usingステートメントを使ってインスタンスを生成する(21行目)。すると、usingステートメントの範囲(21〜24行目)から抜け出す時点で、usingステートメントの機能がIDisposableインターフェイスのDisposeメソッドを呼び出してくれる。これにより、Disposeメソッドに終了処理を記述しておけば、この時点で確実に実行することができる。
これを実行すると以下のようになる。
これを見て分かるとおり、あいかわらずデストラクタはずっと後々になって実行されている。つまり、Disposeメソッドが呼び出されたからといって、インスタンスが消滅したわけではないのである。Disposeメソッドは後処理を行うチャンスをくれるもの、と考えるべきだろう。
『新プログラミング環境 C#がわかる+使える』
本記事は、(株)技術評論社が発行する書籍『新プログラミング環境 C#がわかる+使える』から許可を得て一部分を転載したものです。
【本連載と書籍の関係について 】
この書籍は、本フォーラムで連載した「C#入門」を大幅に加筆修正し、発行されたものです。連載時はベータ版のVS.NETをベースとしていましたが、書籍ではVS.NET製品版を使ってプログラムの検証などが実施されています。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。
→技術評論社の解説ページ
ご注文はこちらから
Copyright© Digital Advantage Corp. All Rights Reserved.