みなさまこんにちは。
C#のファイナライザはご存知でしょうか。
ファイナライザはどういうときに使えば良いのでしょうか。
実はC#のファイナライザには、注意しないとハマってしまう危険性が潜んでいるのです…
C#のファイナライザとは
C#のファイナライザは、オブジェクトがガベージコレクタにより解放されたときに実行される処理です。
教科書的な説明では、オブジェクトの終了処理に使用するものとされています。
しかし、ガベージコレクタの呼び出されるタイミングは厳密に制御できないため、
「リソースを解放し忘れていないか確認し、解放し忘れていれば解放する」のようなフェイルセーフ的な使用方法にするべきとも説明されます。
本当にそうでしょうか?
次のコードをご覧ください。
クラスBが、クラスAを所持する形です。
この場合、ファイナライザはどの順番で呼び出されるのでしょうか?試してみましょう。
class A
{
~A()
{
Console.WriteLine( "A" );
}
};
class B
{
A a = new A();
~B()
{
Console.WriteLine( "B" );
}
};
class Program
{
static void Main( string[] args )
{
B b = new B();
}
}
実行結果
A
B
A→Bの順番になりました。
この結果を見る限り、Bのファイナライザの段階では、すでにAのファイナライザ処理は完了しているということになります。
C#においては、ガベージコレクタによって解放される順番は制御することができず、
それは、所持関係にあるオブジェクトであっても例外ではありません。
今回はたまたまA→Bの順になりましたが、常にそうなる保証はないのです。
この場合、Bの解放処理の中で、Aの状態を参照してなにか処理をするといったことはできませんし、
Aの持つメソッドを呼ぶようなことがあれば、Aは解放済み状態のため例外やクラッシュを引き起こすことも考えられます。
さて困りました
こうなると、Bのファイナライザの段階でなにかフェイルセーフ的な処理を行おうにも、
Bがメンバに持っているオブジェクトの存在すら担保できないことになります。
この状態でできるフェイルセーフって何があるんでしょう…いちおう、アンマネージなリソースに限れば念のため解放するくらいはできそうですが…
結論:ファイナライザには期待するな
結論としては、C#においては解放処理が必要なクラスにはDisposeを使おう、ということになります。
身もふたもない話ですが、C#のファイナライザにできることはほとんどないと覚えておいたほうが良いでしょう。
追記
.NET 5 (.NET Core を含む) 以降のバージョンにおいては、アプリケーションの終了時にファイナライザ呼び出しがされなくなりました。→参考
そうなればなお、ファイナライザの意義とは…