[ VB.NET / C# ] Try ~ Finally と Using による確実な終了処理

Pocket

ここでは、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
    }

 

スポンサーリンク


Pocket

Leave a Comment

Your email address will not be published. Required fields are marked *