ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

Dispose Finalizeパターン

「Solid Code 高品質なコードを生み出す実践的開発手法」という本を読んでいたら
Dispose Finalizeパターンについての解説がありました。

現在参加しているプロジェクトでも、不要になったリソースをDispose()してはいますが
パターンとして同じやり方でDisposeをしているわけではないので
改めてDispose Finalizeパターンについて検証してみました。

ちなみに.Net全般に当てはまることなので、カテゴリは「書籍」ではなく「ASP.NET」にしています。

本に書いてあったDispose Finalizeパターンのサンプルソースを元に、
ファイルに文字列を書いた後、ファイルと閉じてDisposeするソースを書いてみました。
Dispose Finalizeパターンとの違いは、
Dispose()メソッドがソースから明示的に呼び出されたかどうかを表すフラグを省略していることです。


1: using System;
2: using System.Collections.Generic;
3: using System.Linq;
4: using System.Text;
5: using System.IO;
6:
7: namespace Dispose
8: {
9: class Program
10: {
11: static void Main(string[] args)
12: {
13: FileWriter firstWriter = null;
14: firstWriter = new FileWriter();
15: firstWriter.WriteLine("first");
16: firstWriter.Close();
17: firstWriter.Dispose();
18:
19: FileWriter secondWriter = null;
20: secondWriter = new FileWriter();
21: secondWriter.WriteLine("second");
22: secondWriter.Close();
23: secondWriter.Dispose();
24: }
25: }
26:
27: class FileWriter:IDisposable
28: {
29: private bool disposed = false;
30: private StreamWriter writer = null;
31:
32: ~FileWriter()
33: {
34: this.Dispose();
35: }
36:
37: public void WriteLine(string s)
38: {
39: if (writer == null)
40: {
41: writer = new System.IO.StreamWriter(@"D:\test.txt",
true,
System.Text.Encoding.GetEncoding(932));
42: }
43:
44: writer.WriteLine(s);
45: }
46:
47: public void Close()
48: {
49: if (writer != null)
50: {
51: writer.Close();
52: }
53: }
54:
55: public void Dispose()
56: {
57: if (disposed)
58: {
59: throw new ObjectDisposedException("FileWriter");
60: }
61:
62: disposed = true;
63:
64: if (writer != null)
65: {
66: writer.Close();
67: writer.Dispose();
68: }
69:
70: GC.SuppressFinalize(this);
71: }
72: }
73: }

ファイルに文字列を出力するFileWriterクラスはIDisposableインターフェースを継承しており
StreamWriterを解放するためのDispose()メソッドを実装しています。

このパターンがリソースの解放を確実にやってくれることを確認するため
わざと17行目、23行目のDispose()呼び出しをコメントアウトしてデバッグ実行してみました。
・・・Dispose()の呼び忘れなんて、ありがちなミスだからね(笑)。

結果は、55行目からのDispose()メソッドはソースコードから呼び出されるのではなく
ガベージコレクタがファイナライザを呼び出すことにより、ファイナライザ内から呼び出されました。

動きを細かく書くと
1.Main()が終わった時点(24行目)でファイナライザ(32行目)が呼び出される。
これはガベージコレクタが、これ以上FileWriterクラスを参照しないことを認識して
オブジェクトを解放しようとするため。
2.ファイナライザ内でDispose()メソッドを呼び出す(34行目)。
3.Dispose()メソッド内で、無事にStreamWriterを解放する。
となります。