概要
(※本項で説明するファイナライザーは、かつてはデストラクター(destructor)と呼ばれていました。 うちのサイト内でもかつてはその表記だったため、今でも痕跡が残っている箇所があるかもしれません。 参考ブログ: ファイナライザー)
ファイナライザーとは、オブジェクトがガベージ コレクションに回収されるときに呼び出される特別なメソッドです。
「リソースの破棄」で説明しているように、
基本的には、確保したリソースの後片付けはDispose
メソッドをusing
ステートメントを使って行います。
しかし、using
ステートメントは呼び忘れる可能性があって、100%保証のある後片付けにはなりません。
一方で、ガベージ コレクションによって回収されるタイミングであれば、
呼び忘れの心配はありません。
そのため、確実に解放しなければならないリソースは、Dispose
メソッドだけでなく、ファイナライザーでも後片付けを行います。
(もちろん、ガベージ コレクション自体を阻害するようなバグ(メモリ リーク)は起こり得て、
その場合はファイナライザーも呼ばれないため、かなりまずいです。)
ポイント
~
+ クラス名で、ファイナライザーと呼ばれる特殊なメソッドが定義できます- ファイナライザーはオブジェクトがガベージ コレクションで回収される際に呼ばれます
- リソースの破棄を確実にするためには、
Dispose
メソッドに加えてファイナライザーも定義します
ファイナライザー
コンストラクターとは逆に、インスタンスが破棄されるときに呼び出されるのがファイナライザーです。
ファイナライザーは以下のように、クラス名の前に ~
を付けた名前のメソッドを書くことで定義できます。
class SampleClass
{
// ↓これがファイナライザー
~SampleClass()
{
// インスタンスの破棄用のコードを書く
}
}
ファイナライザーはコンストラクターと違って、引数を持つことができません。
また、アクセシビリティも指定できず、static
にもできません。
注意: ファイナライザーの呼び出しタイミング
.NET Framework では、インスタンスの寿命は .NET Framework 自体が管理していて、 いつインスタンスの破棄が行われるのかは分かりません。 (C++ 言語に慣れている人は注意が必要。)
using System;
class Test
{
public Test()
{
Console.Write("Test クラスのコンストラクターが呼ばれました\n");
}
~Test()
{
Console.Write("Test クラスのファイナライザーが呼ばれました\n");
}
}
class DestructorSample
{
static void Main()
{
Console.Write("1\n");
Test t = new Test(); // ここで Test のコンストラクターが呼ばれる
Console.Write("2\n");
t = null; // ↑で作成したインスタンスはもう利用されなくなる
// でも、ファイナライザーはまだ呼ばれない
Console.Write("3\n");
}
}
1 Test クラスのコンストラクターが呼ばれました 2 3 Test クラスのファイナライザーが呼ばれました
この例では、ファイナライザーはプログラムの終了時に呼び出されます(「ガベージ コレクション」するときに呼ばれます)。 ガベージ コレクションのタイミングは、通常は制御できないので、ファイナライザーはいつ呼び出されるかわかりません。
このような性質を持っているため、通常、ファイナライザーはあまり利用されません。 ほぼ、非管理リソースの破棄漏れ防止用です(参考: 「IDisposable インターフェイスの実装」)。
破棄のタイミングを明示的に制御する必要がある場合 (例えば、何らかの外部リソース(ファイルやプリンタなど)の破棄(ファイルのバッファのフラッシュやプリンタの解放)を行う必要がある場合)、 後述する 「using ステートメント」というものを使った Dispose を行います。
注意: Finalize
かつてのデストラクターという呼び名と、~ 記号を使う構文は C++ の構文を参考にしたものです。 しかし、C++ のデストラクター(変数のスコープを抜けたとき、もしくは、delete 演算子を呼んだタイミングで呼ばれる)とは呼び出されるタイミングが全然違うので注意してください。
C++ では、特定のスコープを抜けた時に確実に呼びたい処理のためにデストラクターを使うことがありますが、 こういう用途には、C# の場合、 「using ステートメント」 を使います。
C# のファイナライザー(旧称、デストラクター)は、動作的にはむしろ、Java の finalize
メソッド(ガベージ コレクションに回収された時点で呼ばれる)と同じです。
実際、C# では「デストラクター」と呼んでいた頃も、.NET Framework の中間言語(= C#のコンパイル結果)的にはファイナライザーと呼んでいました。
呼び名の問題だけではなくて、中間言語にコンパイルした結果としては、(旧)デストラクターは Finalize
という名前のメソッドになっています
ちなみに、通常、リソースの破棄処理は、 「using ステートメント」とファイナライザーでの2段構えで行います。
using
ステートメントは高効率ですが確実性がなく、
ファイナライザーは確実ですがパフォーマンスが悪いです。
2段構えにすることで、忘れずusing
していればパフォーマンスがよく、忘れた場合でもファイナライザーでの確実な破棄ができます。
詳しくは「IDisposable インターフェイスの実装」で説明しています。