WebSurfer's Home

トップ > Blog 1   |   Login
Filter by APML

Dispose パターン

by WebSurfer 31. May 2019 18:30

Visual Studio Community 2015(以下、VS2015 と書きます)には Dispose パターンに準拠したコードの骨組みを自動生成する機能が追加されています。

Dispose パターンの実装

上の画像がそれで、Disposable とタイプすると電球マークが表示されるので、その横の▼印をクリックすると Disposeパターンを選択できるようになります。

ここで、[Dispose パターンを使ってインターフェイスを実装します]にマウスをポイントすると以下の画像(Dispose パターンの骨組みのコード)が表示され、クリックするとそのコードがクラスに挿入されます。

([... 明示的に ...]の方を選ぶと、Dispose メソッドが IDisposable.Dispose と明示的に実装されます。その場合、クラスインスタンスから Dispose には直接アクセスできませんので注意してください。明示的の使い道は調べてなくて不明です)

コードの実装

そういう機能が追加されていることを知ってました? 実は自分は最近まで知らなかったです。(汗) VS2010 にはその機能はありませんでしたので、VS2012 ~ VS2015 のどれかから追加されたようです。

Dispose パターンの実装については、Microsoft のドキュメント「Dispose メソッドの実装」などに書かれていますが、読んでもよく分かりませんでした。(笑)

(ちなみに、VS2015 で自動生成されるのは上の Microsoft のドキュメントの「Dispose パターンには 2 種類あります」以下に書いてある後者の Object.Finalize メソッドをオーバーライドする方です)

VS2015 で自動生成されたコードと、ネットの情報「確保したリソースを忘れずに解放するには?」や「C# のファイナライザ、Dispose() メソッド、IDisposable インターフェースについて」などを読んで、やっと Dispose パターンを理解できたような気がします。

Dispose パターンの実装が必要なのは、アンマネージドリソースを使用するクラスのみです。アンマネージドリソースの種類で一般的なのは、ファイル、ウィンドウ、ネットワーク接続、データベース接続などのオペレーティングシステムリソースをラップしたオブジェクトです。

.NET Framework のクラスライブラリの中にも、例えば FileStream のようにファイルを開くために Windows API を呼び出してファイルハンドルを保持するというように、アンマネージドリソースを利用するものがあります。

そういうクラスは、IDisposable インターフェイスを継承して Dispose パターンを使った実装がされていて、Dispose メソッドでアンマネージドリソースを開放できるようになっているはずです。

ソースを見たわけではないので、.NET Frameowrk のクラスライブラリの全部が全部そうなのかは分かりませんが、Dispose メソッドを実装しているクラスは内部でアンマネージドリソースを使っていると考えて、そのオブジェクトが使用されなくなった時点で Dispose メソッドを呼び出すことを基本とすべきと思います。

自分で作るカスタムクラスの場合、マネージドリソースしか保持しない場合は Dispose パターンの実装は不要です。必要なのはアンマネージドリソースを保持する場合のみですが、それには以下のケースがあると思います。

  1. Dispose パターンを実装した .NET のクラスのインスタンスを保持している。
  2. クラス内でアンマネージドリソースを取得し、それを保持している。  

VS2015 のウィザードが生成する Dispose パターンの骨組みのコードを利用して、上記 1 および上記 1 + 2 のケースでコードを実装してみます。

上記 1 のケース

Dispose パターンを実装した .NET のクラスの例として DataSet を使いました。

public class DisposableSample : IDisposable
{
    private DataSet myDataSet;

    public DisposableSample()
    {
        myDataSet = new DataSet();
        // ・・・中略・・・
    }

    private bool disposedValue = false; // 重複呼出の検出用

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // .NET のオブジェクトを解放
                myDataSet.Dispose();
            }

            disposedValue = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
    }
    
    // ・・・中略・・・
}

上記 1 + 2 のケース

アンマネージド(= GC では解放できない)リソースの例として SqlConnection で接続プールから取得する接続を使いました。接続プールはデフォルトで有効で、SqlConnection を Open すると接続プールから接続を取得してきます。

public class DisposableSample : IDisposable
{
    private DataSet myDataSet;
    private SqlConnection connection;

    public DisposableSample()
    {
        myDataSet = new DataSet();

        // アンマネージドリソースを取得
        string connString = ConfigurationManager.
                            ConnectionStrings["MyDB"].
                            ConnectionString;
        connection = new SqlConnection(connString);
        connection.Open();

        // ・・・中略・・・
    }

    private bool disposedValue = false; // 重複呼出の検出用

    protected virtual void Dispose(bool disposing)
    {
        if (!disposedValue)
        {
            if (disposing)
            {
                // .NET のオブジェクトを解放
                myDataSet.Dispose();
            }

            // アンマネージドリソースを解放
            // SqlConnection の Dispose と Close は同じ
            connection.Dispose();

            disposedValue = true;
        }
    }

    // Dispose し忘れても、ガベージコレクタが働いたときに
    // ファイナライザが呼ばれるので、そこでアンマネージド
    // リソースを解放できるよう以下のコードを実装する。
    // C++ のデストラクタの構文だが、C# ではこれによりフ
    // ァイナライザをオーバーライドすることになるらしい
    ~DisposableSample() 
    {
        Dispose(false);
    }

    public void Dispose()
    {
        Dispose(true);

        // 上の Dispose(bool disposing) メソッドでアンマネー
        // ジドリソースを解放した場合はファイナライザが実行
        // されないようにする
        GC.SuppressFinalize(this);
    }

    // ・・・中略・・・
}

上のコードのアンマネージドリソースの取得で、実際にこのような形で接続を保持するケースはないのかもしれませんが、他に具体例が思いつかなかったのでこうしてみました。

プログラマが Dispose() するのを忘れた場合でもガベージコレクタは働きます。ガベージコレクタではアンマネージドリソースは開放できませんが、その際ファイナライザが呼び出されます。なので、最悪でもファイナライザでアンマネージドリソースを開放できるようにしています。

Dispose() メソッドが呼ばれ、それから Dispose(bool disposing) メソッドが呼ばれてアンマネージドリソースを解放した場合は GC.SuppressFinalize(this) を呼び出します。 ファイナライザの実行はパフォーマンスに大きな影響を与えるそうですが、GC.SuppressFinalize メソッドを呼び出しておくと、ファイナライザの呼び出しは行われなくなります。

Tags: , , ,

.NET Framework

XML ファイルを DataGridView に表示

by WebSurfer 26. April 2019 12:14

XML ファイルからデータを取得して Windows Forms アプリの DataGridView に表示し、それをユーザーが編集して結果を XML ファイルに書き戻すサンプルを書きます。

XML ファイルを DataGridView に表示

ASP.NET Web Forms アプリの場合は先の記事「XML ファイルの更新操作」に書きましたのでそちらを見てください。

アプリの基本的な構成は、DataGridView ⇔ BindingSource ⇔ DataSet ⇔ XML ファイルとしています。

DataSet.ReadXml メソッドで XML ファイルからデータを DataSet に読み込み、BindingSource 経由で DataGridView に表示。ユーザーが DataGridView に表示されたデータを編集後(編集結果は DataSet に反映されます)、ボタンクリックで DataSet.WriteXml メソッドにより編集結果を XML ファイルに書き戻すという操作を行います。

Windows Forms アプリの場合、ASP.NET Web Froms アプリとは違って、DataSet や DataGridView などすべてのインスタンスをユーザーの PC メモリ上に保持できますので、上記の操作が可能になります。

コードは、表示・編集・更新を行うためのごく基本的な部分だけですが、以下の通りです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form6 : Form
    {
        private DataGridView dataGridView1;
        private BindingSource bindingSource1;
        private DataSet dataset1;
        private string dir;
        private string file;

        public Form6()
        {
            InitializeComponent();            
        }

        private void Form6_Load(object sender, EventArgs e)
        {
            this.dataGridView1 = new DataGridView();
            this.dataGridView1.Dock = DockStyle.Fill;
            this.bindingSource1 = new BindingSource();
            this.dataGridView1.DataSource = this.bindingSource1;
            this.Controls.Add(this.dataGridView1);

            this.dataset1 = new DataSet();
            this.dir = @"XML ファイルのあるフォルダ";
            this.file = "XML ファイル";
            this.dataset1.ReadXml(dir + file);

            this.bindingSource1.DataSource = dataset1.Tables[0];
        }

        private void button1_Click(object sender, EventArgs e)
        {
            this.Validate();
            this.bindingSource1.EndEdit();
            this.dataset1.WriteXml(dir + file, 
                                   XmlWriteMode.WriteSchema);
            this.dataset1.AcceptChanges();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            this.bindingSource1.RemoveCurrent();
        }
    }
}

XML ファイルはスキーマ付きにしています。その結果、DataTable の各列の DataType はスキーマに応じて型が設定され、例えば bool 型の場合はチェックボックスが表示されます。

DataSet.WriteXml で DELETE 操作を行うためには DataTable の当該行の DataRow.RowState プロパティを DataRowSate.Deleted に設定する必要があります。それを BindingSource.RemoveCurrent メソッドで行っています。

Tags: , , , ,

.NET Framework

VB.NET の ByVal / ByRef と値型 / 参照型

by WebSurfer 20. January 2019 12:37

Visual Basic .NET (VB.NET) のメソッドの引数の値渡し (ByVal) / 参照渡し (ByRef) と、変数の値型 / 参照型をそれらの引数に渡す場合の違いについて、このように考えると理解しやすいと思っていることを書きます。

ちなみに、C# では ByVal に相当するキーワードがなくデフォルトで値渡しになるだけで、考え方は同じです。(ByRef に相当する ref というキーワードはあります)

まず、メソッドの引数に付与する ByVal, ByRef の違いですが、メソッドの呼び出し元から引数に値を渡す時、以下のようしていると考えれば良いと思います。

  • ByVal: 別の入れ物に中身をコピーして渡す
  • ByRef: 入れ物を中身ごと渡す

上記で「入れ物」というのは、例えば、Dim X As Integer で宣言された変数 X で、「中身」というのは X = 10 のようにして変数 X に代入された値 10 のことです。

次に、変数の値型・参照型ですが、クラスや配列は参照型、Integer のような構造体は値型になります。詳しくは、3-2 値型と参照型を見てください。

メソッドに ByVal, ByRef どちらで渡そうと、変数の参照型が値型に変わったり、値型が参照型に変わることはありません。

という訳で、以下の 4 つのケースに分けて考えると良いと思います。

(1) 値型を ByVal で渡す

呼び出し元の入れ物の中身を、メソッド側で用意した別の入れ物に、コピーして渡す。

呼び出し元とメソッドで入れ物が異なるので、メソッド側で入れ物の中身をどのように操作しようと呼び出し元の変数には何の影響もない。

(2) 値型を ByRef で渡す

呼び出し元の入れ物とメソッドに渡された入れ物は同じなので、メソッド側で中身を変更すれば呼び出し側でも中身が変更される。

(3) 参照型を ByVal で渡す

参照型なので渡すのはオブジェクト(インスタンス)を指すアドレス情報。ByVal なのでメソッド側で用意した別の入れ物にアドレス情報をコピーして渡している。

つまり、呼び出し元の変数とメソッド側の変数の中身は同じアドレス情報即ち単一のオブジェクトを指している。

なので、メソッド側で変数が指すオブジェクトを変更すると、呼び出し元の変数が指すオブジェクトでも同じ結果となる。

(4) 参照型を ByRef で渡す

基本は上記 (3) と同じ。

ただし、呼び出し側の入れ物とメソッドに渡された入れ物は同じなので、メソッド側で新しくオブジェクト(インスタンス)を生成してそれのアドレス情報を入れ物に代入すると、呼び出し元の入れ物の中身が指すオブジェクトも、メソッド側で生成した新しいオブジェクトになる。

Tags: , , ,

.NET Framework

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  June 2019  >>
MoTuWeThFrSaSu
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

View posts in large calendar