DOBON.NET DOBON.NETプログラミング掲示板過去ログ

ShowDialogとDisposeについて

環境/言語:[VB.net]
分類:[.NET]

VS2008でVb.netを使用しています。

ShowDialogで開いた画面について質問があります。
ShowDialogで開いたにもかかわらず、終了時にDisposeをしないFormはどのよう
な問題が起こるのでしょうか?

下記のようなプログラムを作成した場合、何度もFormAからFormBを呼び出すと、
FormBのインスタンスを複数作成し、メモリを圧迫してしまうのでしょうか?

@FormAにあるボタンのClickイベント 
  ・FormBのインスタンスを作成
  ・FormBをShowDialogで開くよう記載
  ・ShowDialogの後に、FormBのDisposeについて記載しない

AFormBのCloseイベント
  ・Disposeを記載しない


FormAのClickイベント

 'フォーム(FormB)のインスタンスを作成
 Dim fB As New FormB

 'フォーム(FormB)を表示
 f1.ShowDialog()

End Sub

FormBのClosedイベント

 'なにもしない

End Sub

宜しくお願い致します。
> ShowDialogで開いた画面について質問があります。
> ShowDialogで開いたにもかかわらず、終了時にDisposeをしないFormはどのよう
> な問題が起こるのでしょうか?

メソッドを抜けてもインスタンスは生存し続けるようですね。
ただし.NET の場合アプリケーションを終了すれば自動的に解放されるので
いわゆるメモリリークのような問題は起こりません。
やはり Dispose は必須でしょうね。

・・・とここまで書いたところで、下記の記事を見つけました。
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=46554&forum=7&start=8

Dispose すれば解放されると私も勘違いしていましたが
どうやら思った以上に複雑な仕組みみたいですね。(^ω^;
■No25507に返信(ひらぽんさんの記事)
> ただし.NET の場合アプリケーションを終了すれば自動的に解放されるので
> いわゆるメモリリークのような問題は起こりません。
裏を返せば連続稼動するようなアプリだとまずいということになりませんか?
グリッド系のコントロールを多用したフォームだったら?
すぐに圧迫しそうですが。。。

VB.NET は Forms コレクションみたいなのも無くなったので、一度、参照
変数のアドレスが上書きされて消えてしまうと操作できなくなるんじゃ
ないかなぁ?。。。これってりっぱなメモリリークだと思います。

[前スレ]
http://dobon.net/cgi-bin/vbbbs/cbbs.cgi?mode=al2&namber=25462
# サンプルコードで fB と前回質問時のコード f1 が混在してコンパイルエラーです。

>AFormBのCloseイベント
>  ・Disposeを記載しない
前回指摘を受けたように、呼び出し元で管理します。(FormA が破棄します)

こういった内容は、質問するより、自分でサンプルプログラムを作り、タスク
マネージャなどで確認された方がいいと思います。
> いわゆるメモリリークのような問題は起こりません。
> やはり Dispose は必須でしょうね。

"が" が抜けてたので誤解を与える印象になってました。

> いわゆるメモリリークのような問題は起こりませんが、
> やはり Dispose は必須でしょうね。

ですね。すみません。


> 一度、参照変数のアドレスが上書きされて消えてしまうと
> 操作できなくなるんじゃないかなぁ?。。。

確かにいらなくなったらすぐ明示的に解放するのがセオリーですね。
ただ参照変数を上書きしたらどうなるかは不明です。
ちょっと調べてみますね。


> これってりっぱなメモリリークだと思います。

メモリの無駄遣いと「メモリリーク」は別概念だと思います。
http://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%AF
# 本題(ShowDialog と Dispose)とは別のところに反応。

■No25508に返信(るしぇさんの記事)
> VB.NET は Forms コレクションみたいなのも無くなったので、

(VB6 でいうところの) Forms 相当の機能が無いというのは、
 http://support.microsoft.com/kb/308537/ja
のことでしょうか?

2005 以降では、
 My.Application.OpenForms
 Application.OpenForms
を、Forms コレクションとして利用できます。


Private Sub Button1_Click(ByVal sender As Object, ByVal e As EventArgs) Handles Button1.Click
    Dim F As New Form()
    F.Text = Now.ToLongTimeString()
    F.Show()
    MsgBox(Application.OpenForms.Count)
End Sub
■No25510に返信(ひらぽんさんの記事)
> メモリの無駄遣いと「メモリリーク」は別概念だと思います。
はい。そこは一応理解して書いたつもりですが、
> ただ参照変数を上書きしたらどうなるかは不明です。
アドレスだけの話なので、インスタンスは残り続けますよね。
そのインスタンスにアクセスする手段が無くなりそうという
ことです。
プログラムから制御できない(=以後プログラムから利用できない)
部分のメモリ消費を、プログラムを終了する事でしか解消できない
のはメモリリークでは?

Wiki で言えば、「その限界」あたりの話かな?
>2005 以降では、
> My.Application.OpenForms
> Application.OpenForms
>を、Forms コレクションとして利用できます。
いつも、ありがとうございます。
検索の段階で OpenForms もチェックしましたが、Count プロパティで1しか取得
できなかったので、閉じたら出てこないのかなぁ。。。と思いお茶を濁しておりました。
魔界の仮面弁士さんの環境では繰り返しダイアログ表示で増えていきますか?

サンプルコード[VB2008 Pro]
Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        Dim f2 As Form2
        f2 = New Form2
        f2.ShowDialog()
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        Debug.WriteLine(My.Application.OpenForms.Count)
        Debug.WriteLine(Application.OpenForms.Count)
    End Sub

End Class
Public Class Form2

    Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
        Debug.WriteLine(Now & Me.Name)
    End Sub

    Private Sub Form2_Disposed(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Disposed
        Debug.WriteLine(Now & "Form2_Disposed")
    End Sub

    Private Sub Form2_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        Debug.WriteLine(Now & "Form2_FormClosed")
    End Sub

    Private Sub Form2_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        With Me.Timer1
            .Interval = 1000
            .Start()
        End With
    End Sub

End Class
2009/10/06(Tue) 15:55:31 編集(投稿者)
2009/10/06(Tue) 15:53:56 編集(投稿者)

■No25515に返信(るしぇさんの記事)
> 検索の段階で OpenForms もチェックしましたが、Count プロパティで1しか取得
> できなかったので、閉じたら出てこないのかなぁ。。。と思いお茶を濁しておりました。

閉じたら出てこないのは、VB6 時代も同様かと思います。
(Forms.Count は、インスタンスの有無を表している訳ではありません)


VB6 の Form では
 0. フォームがまだ生成されていない
 1. フォームが生成されている
 2. フォームがロードされている
 3. フォームが表示されている
 4. フォームが表示されてない
 5. フォームがアンロードされているが破棄されていない
 6. フォームが破棄されている(状態6 と 状態0 は同じです)
といった状態を遷移していきます。

そして、Forms.Count については、
 Count が増加するのは、1 → 2 に状態が変わったとき
 Count が減少するのは、4 → 5 に状態が変わったとき
という動作になっていました。(すなわち、ロードされているか否か)

# ちなみに、DoEvents 関数の戻り値については、
#  増加は、2 → 3 に状態が変わったとき
#  減少は、3 → 4 に状態が変わったとき
# でした。(すなわち、画面上に表示されているか否か)
■No25516に返信(魔界の仮面弁士さんの記事)
> 閉じたら出てこないのは、VB6 時代も同様かと思います。
> (Forms.Count は、インスタンスの有無を表している訳ではありません)
…そういうことですか。。。orz
そもそもインスタンスの管理に関係ないんですね。了解です。
# VB6.0 時代も VB.NET 時代もインスタンスは自分で書いたコードで
# 管理していたもので、勘違いしていました。
# というか、自分で管理しないとだめって事ですか。
なるほど。今までの私の概念ですと

「アプリケーションが終了しても、アプリが使用していたメモリが解放されずに残ってしまう」

つまりプロセスが終了してもメモリが確保されたままの状態を「メモリリーク」として捉えていましたが、
確かに起動しっ放しのアプリ内でメモリを食いつぶしていきシステム全体に負荷をかけるのであれば
これも広義で「メモリリーク」と言えそうですね。


> アドレスだけの話なので、インスタンスは残り続けますよね。
> そのインスタンスにアクセスする手段が無くなりそうといことです。

・・・試すまでもないかと思いつつ、.NET では
この辺の話を真剣に考えたことがなかったので少し試してみました(汗

ちなみに私の認識では

Dim frm As New Form()
frm.Dispose → Form のインスタンスの実体を解放
frm = Nothing → Form の参照変数を解放(実体は解放されない)

です。


Public Class Form1

Private frm As Form

Private Sub Button1_Click _
(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button1.Click

Dim frm2 As New Form2
frm2.ShowDialog()
frm = frm2

End Sub

Private Sub Button2_Click _
(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles Button2.Click

frm.ShowDialog()

End Sub

End Class


Button1 クリックの後に Button2 クリックを実行・・・
確かに frm2 を閉じてButton1 クリックイベント抜けても、
frm2 のインスタンスは解放されずに残ってますね。

まぁいずれにせよ、Form を使う場合は Using を使うか、
きちんと Dispose を呼べということですね。
サンプルコードに記載ミスがあり、申し訳ありませんでした。

いろいろとご教授頂き、ありがとうございました。

なにぶん初心者なもので、皆さんの回答からわかった私なりの答えが
正しいのか、再度ご教授お願いできないでしょうか。

ShowDialogで開いた画面をDisposeしないで何度もNewし続けた場合は・・・

 1.参照変数のアドレスが上書きされ消えてしまう
 2.インスタンスは存在しつづける
 3.アプリケーションを終了すれば、ガベージコレクターの対象となる
  (メモリの無駄遣いになり、場合によってはメモリリークを起こす
   原因になる可能性がある。)

上記のような理由でシステムに負荷をかける原因となってしまうので、
確実にDisposeをする必要がある。

ということで間違いないでしょうか?


また、私が作成しているプログラムはメニューをexeで作成し、他画面をdllで
作成しています。(exeで各画面をNewして呼んでいます。)
その場合、「3.アプリケーションを終了すれば、ガベージコレクターの対象となる」
のアプリケーションの終了とは、newした各dllをnothingすることなのでしょうか?

稚拙な質問で申し訳ありませんが、宜しくお願い致します。
> アプリケーションを終了すれば、ガベージコレクターの対象となる
> ・・・・・・中略・・・・・・
> 「3.アプリケーションを終了すれば、ガベージコレクターの対象となる」
> のアプリケーションの終了とは、newした各dllをnothingすることなのでしょうか?

基本的に DLL が.NET アセンブリなら アプリケーション終了と同時に解放されます。

ちなみにマネージリソース(.NET のオブジェクト)は、ガベージコレクタの対象となりますが、
アンマネージリソース(ファイルやらデータベース接続やらetc・・・)は解放の対象になりません。

以下、参考URLです。
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1220462157


さらに詳しい資料ですが

ガベージ・コレクタを明示的に動作させるには?
http://www.atmarkit.co.jp/fdotnet/dotnettips/021gc/gc.html
確実な終了処理を行うには?
http://www.atmarkit.co.jp/fdotnet/dotnettips/027dispose/dispose.html


以下もっと深い話です。(^ω^;

ガベージコレクション入門: Microsoft .NET Framework の自動メモリ管理
http://msdn.microsoft.com/ja-jp/library/bb985010.aspx
http://msdn.microsoft.com/ja-jp/library/dd297765.aspx
2009/10/07(Wed) 17:29:12 編集(投稿者)


> 基本的に DLL が.NET アセンブリなら アプリケーション終了と同時に解放されます。

.NET アセンブリとはどのようなことでわかるのでしょうか?

> ちなみにマネージリソース(.NET のオブジェクト)は、ガベージコレクタの対象となりますが、
> アンマネージリソース(ファイルやらデータベース接続やらetc・・・)は解放の対象になりません。

マネージリソースについてはいろいろと調べているのですが、
調べても調べてもンマネージリソースが何をさすのかわかりません。
ひらぽんさんが挙げたものがアンマネージリソースだってことと、
自作のクラスがアンマネージリソースだということはわかったのですが。。

参考URLありがとうございました。
ガベージコレクション入門は、私にはレベルが高く、少ししか理解できませんでした。
少しずつでも理解していけるよう、 勉強しようと思います。
> .NET アセンブリとはどのようなことでわかるのでしょうか?

.NET アセンブリ とは .NET で作られた EXE もしくは DLL ファイルで、
「プロジェクト」→「参照の追加」で「.NET」もしくは「プロジェクト」に
表示されるものが.NET アセンブリになります。


> 自作のクラスがアンマネージリソースだということはわかったのですが。。

.NET で作られたクラスは基本的にマネージリソースですよ。

極めて特殊な例で unsafe キーワードが付加された
あたかも C/C++ のようにポインタを直に扱えるクラスがあります。
これはアンマネージになるようですが、本当に特殊なケースです。

それ以外のクラスは、基本的にマネージリソースと考えていいでしょう。

http://www.atmarkit.co.jp/fdotnet/csharp_abc/csharp_abc_021/csharp_abc01.html
> .NET アセンブリ とは .NET で作られた EXE もしくは DLL ファイルで、
> 「プロジェクト」→「参照の追加」で「.NET」もしくは「プロジェクト」に
> 表示されるものが.NET アセンブリになります。

なるほど。
ザンネンなことに、どちらにも表示されませんでした。
(いつも参照から追加しています。)
ということは、自分で解放してあげないとガベージコレクターの対象には
ならないということですね。

WithEventsでクラスを定義し、使用前にNewして使い、使用後Nohingをセット
しているのですが、これでは参照変数を解放しているだけで、インスタンスは
解放されていないということになるのでしょうか?

> .NET で作られたクラスは基本的にマネージリソースですよ。

そうだったんですか。
では、IDisposableを使用するのはどのようなときなのでしょうか?
(自作クラスはアンマネージリソースだと思っていたので、Disposeするよう
 IDisposableを使用する必要があると思っていたんです。)

> 極めて特殊な例で unsafe キーワードが付加された
> あたかも C/C++ のようにポインタを直に扱えるクラスがあります。
> これはアンマネージになるようですが、本当に特殊なケースです。

なるほど。
VB.netは「unsafe」キーワードが使用できないので、アンマネージは
作られないということですね。
> なるほど。
> ザンネンなことに、どちらにも表示されませんでした。

失礼しました。もっと簡単な判定方法がありました。
参照設定でライブラリを選択して、
「ファイルの種類」が「アセンブリ」ならアセンブリです。<(_ _;)>

この方法で DLL を選択してなんと表示されるでしょうか?

> (いつも参照から追加しています。)

でも VB.NET で作ったクラスですよね???まさか COM ?
> では、IDisposableを使用するのはどのようなときなのでしょうか?
> (自作クラスはアンマネージリソースだと思っていたので、Disposeするよう
>  IDisposableを使用する必要があると思っていたんです。)

わたくし的には

・ヘビー級のクラス(Form や UserContorl等)
・マネージヒープ外のメモリ(アプリケーション管理外のメモリ)を使う場合。
・データベース接続を扱うクラス。
・ファイルを扱うクラス。
・GDIハンドルを扱うクラス。
・怪しい COM を扱うクラス(泣・・・(TдT)

これらは Dispose を実装するようにしてます。

例えばデータベース接続を扱うクラスなら、
アプリが終了しても DB 接続が開きっ放しだとまずいので、
Disposeメソッド内では、万一接続が開いたままなら閉じるように実装してます。

Dispose に関しては、以下の掲示板の記事が判り易そうです。

http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=34497&forum=7


> VB.netは「unsafe」キーワードが使用できないので、アンマネージは
> 作られないということですね。

VB.NET なら考えなくてもいいでしょうね。
■No25539に返信(ながさんの記事)
> ザンネンなことに、どちらにも表示されませんでした。
そういうものです。

[参照の追加]に表示されるようにするためには、作成したアセンブリを
登録する作業が必要になります。
http://www.atmarkit.co.jp/fdotnet/dotnettips/454vsaddreflist/vsaddreflist.html

> ということは、自分で解放してあげないとガベージコレクターの対象には
> ならないということですね。
参照の追加に現れるかどうかと、ガベージコレクト対象かどうかは別問題です。

> WithEventsでクラスを定義し、
蛇足ですが、そのクラスが COM オブジェクトの場合、WithEvents を使うと
後始末の際に問題になる事があったりします。

>>.NET で作られたクラスは基本的にマネージリソースですよ。
> そうだったんですか。
> では、IDisposableを使用するのはどのようなときなのでしょうか?
IDisposable は、割り当てられた資源を解放するメソッドを定義するためのものです。

そして多くの場合、この資源とはアンマネージ リソースのことを指しています。
IDisposable そのものは、別にアンマネージ専用という決まりは無いのですが、
マネージ リソースの場合、ガベージコレクトに任せれば充分という事が多いため、
結果として、アンマネージ リソースのために利用されることが多いのです。

# MSDN Library の“IDisposable インターフェイス”の項を参照してみると、
# MSDN 文書の更新時期によって、その表記内容には微妙に差があり、
# 『割り当てられたアンマネージ リソースを解放するメソッドを定義します。』な物と
# 『割り当てられたリソースを解放するメソッドを定義します。』な物とがあります。


> (自作クラスはアンマネージリソースだと思っていたので、Disposeするよう
>  IDisposableを使用する必要があると思っていたんです。)
managed resources とは、.NET で管理される資源の事です。対する
unmanaged resources とは .NET 管理外の資源の事であり、たとえば
API で「ハンドル」として扱われる物などは、アンマネージと言えます。


たとえば「ウィンドウ」は、OS によって管理されるものであり、
.NET にとってはアンマネージな資源にあたります。
OS にとって、Form や Button や TextBox は、すべてウィンドウの一種であり、
これらは、Control クラスというマネージ オブジェクト内で管理されています。
(Control そのものは、.NET で管理される managed なクラスである事に注意)

# ウィンドウはウィンドウ ハンドル(HWND) という ID で管理されており、
# .NET からは、Handle プロパティでこの HWND を得ることができます。

作成したウィンドウは、使用後に破棄しなければいけないため、
それを保証するために、Control は IDisposable を実装しているのです。


なお、VB の InputBox 関数なども、ウィンドウを表示させてはいますが、
これは生成から破棄までが InputBox メソッド内で完結されているため、
利用側からは IDisposable として処理する必要がありません。


以下、InputBox 関数内部の実装イメージ。

Public Shared Function InputBox(パラメータ) As String
 Using box As New InternalInputBox(パラメータ)
  box.ShowDialog()
  Return box.InputedText
 End Using
End Function


> VB.netは「unsafe」キーワードが使用できないので、アンマネージは
> 作られないということですね。
アンマネージ クラスを作る事はできませんが、
アンマネージ リソースを扱う事は少なからずあるでしょう。

その場合、アンマネージな資源を確実に後始末するために、
IDisposable を実装することができます。
2009/10/08(Thu) 13:28:12 編集(投稿者)

ひらぽんさん

お世話になっております。

> 参照設定でライブラリを選択して、
> 「ファイルの種類」が「アセンブリ」ならアセンブリです。<(_ _;)>
>
> この方法で DLL を選択してなんと表示されるでしょうか?

アセンブリでした(^^)
では問題なさそうですね。

> でも VB.NET で作ったクラスですよね???まさか COM ?

VB.NETで作成したクラスです。
「COM相互運用機能の登録」にチェックはつけていないので、COMではありません。

> ・ヘビー級のクラス(Form や UserContorl等)
> ・マネージヒープ外のメモリ(アプリケーション管理外のメモリ)を使う場合。
> ・データベース接続を扱うクラス。
> ・ファイルを扱うクラス。
> ・GDIハンドルを扱うクラス。
> ・怪しい COM を扱うクラス(泣・・・(TдT)
>
> これらは Dispose を実装するようにしてます。

なるほど。

> Dispose に関しては、以下の掲示板の記事が判り易そうです。
>
> http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=34497&forum=7

ありがとうございます。
まだ全てを読みきれたわけではありませんが、Disposeについて少しは理解できたように思えます。
魔界の仮面弁士さん

お世話になっております。

> [参照の追加]に表示されるようにするためには、作成したアセンブリを
> 登録する作業が必要になります。
> http://www.atmarkit.co.jp/fdotnet/dotnettips/454vsaddreflist/vsaddreflist.html

それは知りませんでした。
実は、Oracle.DataAccessが「参照の追加」で「.NET」に表示されないんですよ。
他のPCだと表示されるPCもあるので、何が違うのか悩んでいたんですよね。
これで解決です!ありがとうございました。

>>WithEventsでクラスを定義し、
> 蛇足ですが、そのクラスが COM オブジェクトの場合、WithEvents を使うと
> 後始末の際に問題になる事があったりします。

今回はCOMオブジェクトではありません。できればCOMオブジェクトだった場合
どのような問題が起こるのか教えていただけないでしょうか?

> 作成したウィンドウは、使用後に破棄しなければいけないため、
> それを保証するために、Control は IDisposable を実装しているのです。

Controlクラスはマネージオブジェクトですが、FormやButtonはアンマネージなので
自分で破棄する必要があるためDisposeする必要があるということでしょうか?

> なお、VB の InputBox 関数なども、ウィンドウを表示させてはいますが、
> これは生成から破棄までが InputBox メソッド内で完結されているため、
> 利用側からは IDisposable として処理する必要がありません。

なるほど。

> アンマネージ クラスを作る事はできませんが、
> アンマネージ リソースを扱う事は少なからずあるでしょう。
>
> その場合、アンマネージな資源を確実に後始末するために、
> IDisposable を実装することができます。

VB.NETでもアンマネージなものを作成することができてしまうんですね。
わからないながらも、少しはわかってきました。

ありがとうございました。

DOBON.NET | プログラミング道 | プログラミング掲示板