連載

C#入門

第11 コンストラクタとデストラクタ

(株)ピーデー
川俣 晶
2001/09/13


デストラクタとは何か?

 デストラクタとは、コンストラクタの逆の役割を持った機能である。つまり、インスタンスが消滅する際に呼び出されるメソッドである。

 C++のようなインスタンスの寿命をプログラマが厳密に管理するプログラム言語では、インスタンスの後処理を行うのに便利な機能なのだが、C#はインスタンスの寿命をシステムが自動的に管理するため、デストラクタがいつ呼び出されるか予測不可能である。例えばC++なら、デストラクタに開いたファイルを閉じる処理を記述することも可能だが、C#で同じように記述すると、いつファイルを閉じるか予測不可能と言うことになり、トラブルの元になりかねない。その意味で、C#のデストラクタは出番があまりないと思われるが、一応紹介しておく。

 以下はデストラクタを使ったサンプルソースである。

 1: using System;
 2:
 3: namespace ConsoleApplication33
 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:     static void Main(string[] args)
21:     {
22:       Console.WriteLine("static void Main(string[] args) called");
23:       test();
24:       Console.WriteLine("static void Main(string[] args) done");
25:     }
26:   }
27: }
デストラクタを記述したサンプル・プログラム8

 これを実行した結果は以下のようになる。

サンプル・プログラム8の実行結果
C#ではインスタンスの寿命をシステムが自動的に管理するため、デストラクタがいつ呼ばれるのか予測できない。このサンプル・プログラムの場合にはMainメソッドの実行後に呼び出されているのが分かる。

 この例では、17行目で生成されるClass2クラスのインスタンスは15〜19行目のブロックの内部で宣言された変数t1を経由してしか参照できないので、メソッドtestの実行が終わればインスタンスは用済みに思える。これがC++のようなプログラム言語なら、ここでデストラクタを実行させることができるのだが、C#では動作が異なっている。実行画面を見て分かるとおり、デストラクタが実行されるのは、メソッドtestの実行が終わり、さらにメソッドMainの実行が終わった後ということになっている。もちろん、必ずこのタイミングでデストラクタが実行される保証はない。デストラクタは、いつ実行されるか分からないものなのだ。

確実な終了処理

 コンストラクタとデストラクタの話題からは外れるが、確実な終了処理について少々補足を加える。ある範囲の実行を終えた時点で確実に何かの終了処理を実行させたい場合は、例外処理を使うのが普通である。例外処理については、この先で詳しい説明をするので、ここでは概略だけ述べる。以下が、例外処理を用いて上のサンプルソースを書き直してみたものである。

 1: using System;
 2:
 3: namespace ConsoleApplication34
 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:     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: }
例外処理を用いて終了時の処理を行ったサンプル・プログラム9

 このプログラムのポイントは、終了処理を行うメソッドであるcloseの呼び出し(24行目)を、finallyブロック(22〜25行目)の内部に置いた点である。finallyブロックは、その手前のtryブロックの終了が終わった時点で、どんな理由で終わったにせよ、必ず実行されることになっている。returnでメソッドから直接抜けようが他の例外が発生しようが、どんな理由でも、tryブロックの実行が終了するときにfinallyブロックは実行される。そのため、20行目の処理がどんな結果に終わろうとも(プロセスそのものが異常終了でもしない限り)必ず24行目は実行される。

 これを実行した結果は以下のようになる。

サンプル・プログラム9の実行結果
例外処理においてfinallyブロックは必ず実行されるため、必要な終了時の処理を確実に行うことができる。

IDisposableとusingによる終了処理

 これも余談である。ちょっと難しいので、C#初心者が理解できなくても問題はない。IDisposableというインターフェースと、usingというステートメントを使うと、確実な終了処理を記述できる。以下がそのサンプルソースである。

 1: using System;
 2:
 3: namespace ConsoleApplication35
 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:     static void Main(string[] args)
28:     {
29:       Console.WriteLine("static void Main(string[] args) called");
30:       test();
31:       Console.WriteLine("static void Main(string[] args) done");
32:     }
33:   }
34: }
IDisposableとusingを使用したサンプル・プログラム10

 まず、確実な終了処理をさせたいクラスで、IDisposableインターフェイスを実装する(5行目)。これには、Disposeメソッドしかないので、実装するのに面倒はない(11〜14行目)。そして、usingステートメントを使って、インスタンスを生成する(21行目)。すると、usingステートメントの範囲(21〜24行目)から抜け出す時点で、usingステートメントの機能が、IDisposableインターフェイスのDisposeメソッドを呼び出してくれる。これにより、Disposeメソッドに終了処理を記述しておけば、この時点で確実に実行することができる。

 これを実行した結果は以下のようになる。

サンプル・プログラム10の実行結果
クラスにIDisposableインターフェイスを実装し、usingステートメントを用いることによって、終了処理を記述したDisposeメソッドを確実に実行させることができる。実行結果から“IDisposable.Dispose()”が“test()”の終了より前に呼び出されているのが分かる。

 これを見て分かるとおり、相変わらずデストラクタはずっと後になって実行されている。つまり、Disposeメソッドが呼び出されたからと言って、インスタンスが消滅したわけではないのである。Disposeメソッドは後処理を行うチャンスをくれるもの、と考えるべきだろう。

まとめ

 コンストラクタは、インスタンスの内容を正しい状態からスタートさせるために重要な機能である。どんなクラスでも、コンストラクタがきちんとしていないと使いにくいものである。一方デストラクタの役割は、C++など他のプログラム言語に比べて、重要度は下がっていると言える。むしろ、デストラクタが担ってきた役割を、もっと別のもので交代させていくべきと言えるかもしれない。

 さて、次回は非常にC#らしい機能と言えるプロパティやインデクサについて解説したいと思っている。

 それでは次回もLet's See Sharp!End of Article


 INDEX
  第11回 コンストラクタとデストラクタ
    1.コンストラクタとは何か?
    2.baseクラスのコンストラクタの呼び出し
  3.デストラクタとは何か?

「C#入門」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間