ここでは、Try ~ Finally 構文と Using 構文による確実な終了処理の記述方法について掲載しています。
スポンサーリンク
Try ~ Finally による終了処理
Try ~ Catch ~ Finally 構文を使用した終了処理を行うサンプルコードです。処理内容はファイル A.txt を1行ずつ読み込んで B.txt に書き込むといった単純なものです。確実にファイルを閉じるために、Finally 句でファイルのクローズ処理を行っているところがポイントです。
VB.NET
Dim sr As StreamReader = Nothing Try sr = New StreamReader("E:\A.txt", System.Text.Encoding.Default) Dim sw As StreamWriter = Nothing Try sw = New StreamWriter("E:\B.txt", False) While (sr.Peek() >= 0) ' ファイルから 1 行読み込んで、そのまま出力する sw.WriteLine(sr.ReadLine()) End While Catch ex As Exception ' 何かエラー処理を行う Finally ' 書き込みファイルを閉じる If sw IsNot Nothing Then sw.Close() End If End Try Catch ex As Exception ' 何かエラー処理を行う Finally ' 読み込みファイルを閉じる If sr IsNot Nothing Then sr.Close() End If End Try
C#
StreamReader sr = null; try { sr = new StreamReader(@"E:\A.txt", System.Text.Encoding.Default); StreamWriter sw = null; try { sw = new StreamWriter(@"E:\B.txt", false); // ファイルから読み込めるものがなくなるまで読み込む while (sr.Peek() >= 0) { // ファイルから 1 行読み込んで、そのまま出力する sw.WriteLine(sr.ReadLine()); } } catch (Exception ex) { // 何かエラー処理 } finally { if (sw != null) { sw.Close(); } } } catch (Exception ex) { // 何かエラー処理 } finally { if (sr != null) { sr.Close(); } }
ファイルやソケットなどを記述漏れによってオープンしたままにすると、その接続が残ることによって後続の処理に問題を引き起こす場合があります。クローズ処理は必ず行う必要があります。Try ~ Catch ~ Finally を使ったクローズ処理は上記のとおりですが、とてもコードが長くなり、読みづらいようにも思うことがあるかもしれません。
Using による終了処理の自動呼出し
次に Using を使って前述のサンプルコードを書き直したものになります。
VB.NET
Using sr As New StreamReader("E:\A.txt", System.Text.Encoding.Default) Using sw As New StreamWriter("E:\B.txt", False) ' ファイルから読み込めるものがなくなるまで読み込む While (sr.Peek() >= 0) ' ファイルから 1 行読み込んで、そのまま出力する sw.WriteLine(sr.ReadLine()) End While End Using End Using
C#
using (StreamReader sr = new StreamReader(@"E:\A.txt", System.Text.Encoding.Default)) { using (StreamWriter sw = new StreamWriter(@"E:\B.txt", false)) { // ファイルから読み込めるものがなくなるまで読み込む while (sr.Peek() >= 0) { // ファイルから 1 行読み込んで、そのまま出力する sw.WriteLine(sr.ReadLine()); } } }
Try ~ Finally を使った場合と比較するとすっきり記述できます。Close メソッドを呼び出していないことにお気づきでしょうか。using から抜けると IDisposable インターフェース の Dispose メソッドが自動的に呼び出されます。Dispose メソッドで Close メソッドが呼ばれているため自動で呼び出されることになります。つまり、StreamReader や StreamWriter クラスは IDisposable インターフェースの Dispose に Close を行うように実装されているということです。IDisposable インターフェースを実装していないクラスに対して Using ステートメントは コンパイルエラーとなり使用できません。
IDisposableインターフェースの実装サンプル
IDisposable インターフェースを実装しようとすると、Visual Studio によってサンプルコードが自動作成されます。自動作成されたコードにコメントを追加しましたので、よろしければ参照ください。
VB.NET
Public Class Class1 Implements IDisposable ' ファイナライザ Protected Overrides Sub Finalize() MyBase.Finalize() ' ファイナライザが呼び出された場合も終了処理を行う ' 呼び出し元で、Dispose()が呼び出されなかった場合(大体バグ)の保険 Dispose(True) End Sub Private disposedValue As Boolean = False ' 重複する呼び出しを検出するには ' IDisposable Protected Overridable Sub Dispose(ByVal disposing As Boolean) ' 終了処理を複数回実行することを防ぐ If Not Me.disposedValue Then If disposing Then ' TODO: 他の状態を解放します (マネージ オブジェクト)。 End If ' TODO: ユーザー独自の状態を解放します (アンマネージ オブジェクト)。 ' TODO: 大きなフィールドを null に設定します。 End If Me.disposedValue = True End Sub #Region " IDisposable Support " ' このコードは、破棄可能なパターンを正しく実装できるように ' Visual Basic によって追加されました。 Public Sub Dispose() Implements IDisposable.Dispose ' このコードを変更しないでください。クリーンアップ コードを上の ' Dispose(ByVal disposing As Boolean) に記述します。 Dispose(True) ' ファイナライザを呼ばれないようにする GC.SuppressFinalize(Me) End Sub #End Region End Class
C#
public class Class1:IDisposable { // デストラクタ ~Class1() { // 終了処理を呼び出す Dispose(true); } private Boolean disposedValue = false; protected virtual void Dispose(Boolean disposing) { // すでに終了処理が呼び出されている場合は何もしない if (this.disposedValue == false) { if (disposing) { // TODO::他の状態を開放します(マネージ オブジェクト)。 } // TODO: ユーザー独自の状態を解放します (アンマネージ オブジェクト)。 // TODO: 大きなフィールドを null に設定します。 } this.disposedValue = true; } #region IDisposable メンバ public void Dispose() { // 終了処理を呼び出す Dispose(true); // デストラクタが呼び出されないようにする GC.SuppressFinalize(this); } #endregion }