C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

PictureBox1.Image.Disposeに関して

[トピック内 6 記事 (1 - 6 表示)]  << 0 >>

■92354 / inTopicNo.1)  PictureBox1.Image.Disposeに関して
  
□投稿者/ EN (1回)-(2019/09/12(Thu) 10:43:26)

分類:[.NET 全般] 

PictureBox1.Image.Dispose


SizeModeの変更
PictureBox1.SizeMode = PictureBoxSizeMode.CenterImage

を連続して行うと

型 'System.ArgumentException' のハンドルされていない例外が System.Drawing.dll で発生しました

追加情報: 使用されたパラメーターが有効ではありません。

というエラーが出るのですが、なぜなのでしょうか?

disposeの後にSleepを入れるか、

PictureBox1.Image = Nothing
を入れるとエラーは出なくなります。


あと、
PictureBox1.Image.Dispose

PictureBox1.Image = Nothing
は同じなのでしょうか?

どちらを使っても構いませんか?



引用返信 編集キー/
■92355 / inTopicNo.2)  Re[1]: PictureBox1.Image.Disposeに関して
□投稿者/ 魔界の仮面弁士 (2379回)-(2019/09/12(Thu) 11:31:15)
2019/09/12(Thu) 11:39:56 編集(投稿者)

No92354 (EN さん) に返信
> PictureBox1.Image.Dispose
使用中のリソースを Dispose してはいけません。


> 型 'System.ArgumentException' のハンドルされていない例外が
> というエラーが出るのですが、なぜなのでしょうか?
CenterImage で表示するためには、処理手続き的に、
「Image プロパティが持つ画像のサイズ」を調べる処理が必要となります。

具体的には Private ReadOnly Property ImageRectangle As Rectangle への
アクセスが行われているわけですが、細かい点を省略すれば、
 Dim w As Integer = PictureBox1.Image.Width
のような感じの処理が行われているということです。

しかし、Image オブジェクトが既に破棄されているため、
処分済みオブジェクトのプロパティへのアクセスが失敗し、ArgumentException が発生します。


> PictureBox1.Image.Dispose
> PictureBox1.Image = Nothing
> は同じなのでしょうか?

Nothing 代入と Dispose() 呼び出しは、意味がまったく異なります。


たとえば下記のコードを見てください。
Load 時に example.jpg を読み取り、それを表示するコードです。

この場合、画像ファイルがロックされるため、
フォームを閉じるまでの間、example.jpg をリネームしたり
削除したりすることができないはずです。


Private bmp As Image

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
  bmp = Image.FromFile("C:\TEMP\example.jpg") 'ここは実在するファイルを指定して下さい
  PictureBox1.Image = bmp
End Sub

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
  PictureBox1.Image.Dispose()
End Sub

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
  PictureBox1.Image = Nothing
End Sub

Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
  bmp.Dispose()
End Sub

Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
  bmp = Nothing
End Sub


この場合、Button1 と Button3 は同じ意味を持ちます。
どちらのコードでも、example.jpg ファイルをロックしていた
Image オブジェクトのインスタンスが Dispose されます。
Image オブジェクトが破棄されれば、example.jpg も解放され、
ファイルのリネームが可能な状態となります。
(Image が破棄されているので、PictureBox1 には表示できなくなる)


一方、Button4 については、変数 bmp が参照していた情報が、
「example.jpg な画像への参照」ではなく「Nothing」になっただけなので、
PictureBox1.Image に対しては影響がありません。
PictureBox1 には画像が表示されたままですし、
example.jpg はリネームできないままです。


Button2 も同様です。
PictureBox1 の表示はクリアされますが、変数 bmp が
Image インスタンスexample.jpg への参照を保持したままなので、
example.jpg はリネームできないままです。


また、Button2 と Button4 の両方を押した場合、
example.jpg はどこからも参照されていない状態となりますが、
この場合も Dispose() されてはいないため、
ファイルは暫くロックされたままです。
ガベージコレクトが発動して自動的に Dispose されたり、
アプリを終了したりすれば、ファイルのロックは解除されますが。
引用返信 編集キー/
■92356 / inTopicNo.3)  Re[1]: PictureBox1.Image.Disposeに関して
□投稿者/ 大谷刑部 (14回)-(2019/09/12(Thu) 11:35:23)
No92354 (EN さん) に返信
> PictureBox1.Image.Dispose
> と
> PictureBox1.Image = Nothing
> は同じなのでしょうか?
>
> どちらを使っても構いませんか?
>
>

違うオブジェクトでのは話ではありますが、理屈は同じと思うので、
http://mxproj.blogspot.com/2018/02/set-nothing.html

是非論はともかく、メモリを確実に開放するならDisposeは必須ということかと。
上記のリンクの人も書いてますが、.netに限らず、VB6とかVBAでも(特にExcel操作時に)よくおこる系の現象かと。
どちらかというよりは、私なら両方やりますね。

引用返信 編集キー/
■92357 / inTopicNo.4)  Re[2]: PictureBox1.Image.Disposeに関して
□投稿者/ EN (2回)-(2019/09/12(Thu) 11:43:41)

ありがとうございます。

disposeとnothingの違いについては理解できました。




> 使用中のリソースを Dispose してはいけません。

これはどういうことですか?
Pictureboxの画像を消したい場合にはdisposeするのではないのでしょうか?




> しかし、Image オブジェクトが既に破棄されているため、
> 処分済みオブジェクトのプロパティへのアクセスが失敗し、ArgumentException が発生します。

質問文でも述べたように
dispose直後にsleep(400)を入れたり
PictureBox1.Image = Nothing
すると、エラーは発生しないのですが、
画像がnothingだと
Dim w As Integer = PictureBox1.Image.Width
のような処理を行おうとしないためエラーにならないということですか?

sleep(400)を入れるとエラーにならないのは
中にtry catchのようなものが入っているためでしょうか?


引用返信 編集キー/
■92358 / inTopicNo.5)  Re[3]: PictureBox1.Image.Disposeに関して
□投稿者/ 魔界の仮面弁士 (2380回)-(2019/09/12(Thu) 12:24:00)
2019/09/12(Thu) 20:07:34 編集(投稿者)

No92357 (EN さん) に返信
>>使用中のリソースを Dispose してはいけません。
> これはどういうことですか?
> Pictureboxの画像を消したい場合にはdisposeするのではないのでしょうか?

使用していた Image を解放したいなら Dispose() 呼び出しですが、
PictureBox の表示を消したいなら、Nothing 代入です。
先に述べたとおり、この 2 つは明確に別の意味を持ちます。


.Dispose() の呼び出しは、
「この画像は(もう使っていないので)処分しちゃおう!」
的な意味合い。
使用中なのに勝手に捨てられたら、利用者は怒ります(例外が起こる)ので、
コントロール等から利用している段階で Dispose するのは NG です。


一方、PictureBox1.Image = Nothing という操作は、
「今まで表示していた画像の事は忘れて、まっさらな表示にしよう!」
という意味合いであり、リソースの処分という意図は含みません。
PircuteBox1 がその存在を忘れてしまったとしても、元のインスタンスは
メモリ上に残り続けたままです。(ガベージコレクトされるまでは)


> PictureBox1.Image = Nothing

Dispose を行ってから、Nothing を代入するというのは、
「処分した後で、それを使用していたオブジェクトからの参照を解除する」
という行為を意味するので、本来は順番が逆だったりします。
「どこからも使用されていない状態になってから処分する」のが望ましいです。


その画像が PictureBox1 でしか使われていないことを保証できる時は、
「Nothing 代入して参照を解除してから、Dispose で処分する」ために
下記のように記述することが出来ます。やや冗長的ですケド。

  Dim oldImage As Image = PictureBox1.Image '既存の参照を取得してから
  PictureBox1.Image = Nothing 'Nothing を代入して画像を消す
  If oldImage IsNot Nothing Then
    'その前に参照されていた Image は、
    'もうどこからも利用されないので処分する
    oldImage.Dispose()
    oldImage = Nothing
  End If

まぁ、実際には順番の違いには目を瞑って、
  PictureBox1.Image?.Dispose()
  PictureBox1.Image = Nothing
と連続で書いてしまっても、さほど問題にはならないでしょうけれど。
(Dispose から Nothing 代入までの間が空いてしまうとマズイですが)


とにかく、Dispose() するのは、『使用中』ではない事が
大前提ということです。その Image が他の場所からも使われる
可能性があるうちは、それを Dispose してはいけません。

たとえば、先の No92355 において、Load 時の処理が
  PictureBox1.Image = jpg
  PictureBox2.Image = jpg
となっているようなケースを思い浮かべてみてください。
2 箇所で使用されている状況で、片方では用済みになったからと
処分してしまったら、まだ利用中の他方で問題になりますね。


どこから参照されているのか分からないオブジェクトについては、
明示的な Dispose() 呼び出しは諦めて、あとはガベージコレクトに
任せてしまうという、後ろ向きな選択肢もあります。


> dispose直後にsleep(400)を入れたり
そもそも UI スレッドで Sleep(400) を呼ぶ行為自体が NG なんですが…それはさておき。

.NET Framework 4 以降と 3.5 以下とで多少の違いはあるのですが、
PictureBox への再描画依頼がいつ発生するのかが問題となります。
要は、発生までのタイミングがずれただけではないでしょうか。

仮に Sleep を挟んだとしても、その後で、
 ・他のアプリ(電卓とかメモ帳とか)を PictureBox の上に重ねてみる
 ・フォームをドラッグして、PictureBox が 画面外にはみ出す位置にする
 ・フォームを最小化してみる
などの行為を行うと、問題が発生しそうです。

これらの後で、再度 PictureBox が表示される状態になると、
‘再描画依頼’が OS から通知され、PictureBox に対して
描画処理が行われます。
この時点で、PictureBox の Image が Nothing ではなく、
かつ Dispose 済みであったとすれば、やはりエラーとなりうるかと。

ただし一部の例外は Visual Studio によって隠されてしまうため、
今回の実験においては、普段お使いの
[デバッグ]-[デバッグの開始] からではなく、あえて
[デバッグ]-[デバッグなしで開始]で実行するようにすると、
この状況が掴みやすいかもしれません。
引用返信 編集キー/
■92365 / inTopicNo.6)  Re[3]: PictureBox1.Image.Disposeに関して
□投稿者/ shu (1195回)-(2019/09/13(Fri) 13:53:39)
2019/09/13(Fri) 13:57:33 編集(投稿者)

No92357 (EN さん) に返信
>
>
>>使用中のリソースを Dispose してはいけません。
>
> これはどういうことですか?
> Pictureboxの画像を消したい場合にはdisposeするのではないのでしょうか?
>
>
>>しかし、Image オブジェクトが既に破棄されているため、
>>処分済みオブジェクトのプロパティへのアクセスが失敗し、ArgumentException が発生します。
> sleep(400)を入れるとエラーにならないのは
> 中にtry catchのようなものが入っているためでしょうか?
>
この場合、disposeを行った場合、破棄しても大丈夫という目印をつけるだけで、実際には
まだオブジェクトが残っている状態となります。
しばらくすると目印を見つけてオブジェクトが完全に破棄された状態になります。
例外が発生するのはこの間でオブジェクトを参照した事によるものです。
なのでsleepして待つと例外が発生しなくなります。

引用返信 編集キー/

このトピックをツリーで一括表示


トピック内ページ移動 / << 0 >>

このトピックに書きこむ