質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

Q&A

解決済

1回答

2103閲覧

メモリが大きくなりアプリがエラーになる(disposeのやり方が知りたい)

hanbee.com

総合スコア52

VB.NET

Microsoft Visual Basic .NETのことで、Microsoft Visual Basic(VB6)の後継。 .NET環境向けのプログラムを開発することができます。 現在のVB.NETでは、.NET Frameworkを利用して開発を行うことが可能です。

0グッド

0クリップ

投稿2020/11/06 10:10

いつもお世話になっております。
以下のコードのようにピクチャーボックスを並べて画像を表示しているのですが、
時間がたつとアプリが「メモリが大きすぎる」というエラーを出してしまいます。
おそらくdisposeで開放していないからなのかなと思っています。

Dim fs As FileStream Dim img As Image For i As Integer = 1 To 4 '生成 Dim bihin As New PictureBox          '~~~DBからresourceを読み出します~~~~' fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) bihin.Image = img Me.Controls.Add(bihin) Next

しかし、ネットで調べてコードを作り、デバックしてみてもコンパイルエラーが起きてしまいます。
(使用されたパラメータが有効ではありませんとのことです。)
以下にコードを載せます。このコードでは何がだめなのでしょうか。

どうかご教授宜しくお願い致します。

Imports System.IO Try fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) bihin.Image = img Me.Controls.Add(bihin) Finally fs.dispose() img.Dispose() End Try

Windows Form Application
Framework:.Net4.6.1

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

YAmaGNZ

2020/11/06 10:38 編集

「時間がたつと」とありますが、提示されたコードは定期的に動くコードなのでしょうか? それとも1回だけなのでしょうか? もし定期的に動くのであれば PictureBoxを毎回新たに生成しなくてはならないのでしょうか? 生成した後のPictureBoxはどうしているのでしょうか?
hanbee.com

2020/11/06 10:56

ご質問ありがとうございます。YAmaGNZ様、いつもありがとうございます。 説明が抜けておりました。書いていませんでしたが、ご指摘の通り、このコードは1つのプロシージャ(スタートアップフォームのロードイベント)として使っており、 ピクチャーボックスに表示する情報を更新していくので、毎回DBから読み出すこのイベントを使っています。そして、これもまた書いていませんが、毎回このイベントでは前回生成したピクチャーボックスを消去しています。
hanbee.com

2020/11/06 10:58

時間が経つと、というのはこのイベントを何度も読み出すと、次第にメモリが大きくなってエラーを出すようになるということです。 やり方が分からず使っていませんが 生成したピクチャーボックスをdisposeできていないのが原因と考えています。
退会済みユーザー

退会済みユーザー

2020/11/06 15:26

コンパイルエラーなのか、デバッグ時の例外なのかどちらですか? エラー内容はそのままを正確に記載してください
hanbee.com

2020/11/06 21:47

エラーというか、フリーズです。 Releaseの実行ファイルを起動してもある程度動かしてたらメモリが大きくなり、動かなくなります。そのあとに「メモリが大きいです」というようなエラーメッセージが出ます。全文は申し訳ないですが分かりません。
YAmaGNZ

2020/11/06 23:55 編集

PictureBoxを破棄しているとのことですが、その部分はどうなっていますか? また、PictureBoxを作って、破棄するのであれば1回作って使いまわせばいいのではないですか?
hanbee.com

2020/11/06 23:56

ご回答ありがとうございます。 無知で申し訳ないのですが、それはどういうことなのでしょうか?
hanbee.com

2020/11/06 23:58

説明が下手で申し訳ないですが pictureboxは現在のコードでは破棄するコードを入れてません。
YAmaGNZ

2020/11/07 00:03

>そして、これもまた書いていませんが、毎回このイベントでは前回生成 >したピクチャーボックスを消去しています。 と書いているのに破棄のコードが無いということは消去していないのではないですか? 使いまわしとは1回どこかでPictureBoxを生成して配列にでも入れておいて 提示されたループでNewするのではなく、その配列に格納されているPictureBoxにアクセスすればいいだけです。
hanbee.com

2020/11/07 02:07

語弊をまねく書き方で申し訳ありません。 前回生成しているピクチャーボックスを消去していると書いたのは以下のように配列の前にForm上のコントロールを全部削除していますという意味です。あえて書く必要もありませんでした。 ↓↓↓↓ For Each c As Control In Me 'コントロールを削除します Next 'この後にピクチャーボックスを新しく生成しています。 For i = 1 to 4 '生成 Dim bihin As New PictureBox          '~~~DBからresourceを読み出します~~~~' fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) bihin.Image = img Me.Controls.Add(bihin) Next そして今したいのは新しいピクチャーボックスを生成している配列の中にアンマネージドリソースの解放句らしいdisposeを加えたいということです。 現状のこのコードでは、このピクチャーボックスを新しく生成しているイベントを何回か繰り返すとPCが重くなり、しばらくすると「メモリが大きすぎる」というメッセージのエラーが出てしまいます。 これでは使い物にならないので、アンマネージドリソースを開放するコードを加えたいのです。
hanbee.com

2020/11/07 02:14

使いまわしというのは私のコードで書くと、以下のようにするということですか? ↓↓↓ '配列の外に出す Dim bihin As New PictureBox For i = 1 to 4  '~~~DBからresourceを読み出します~~~~' fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) bihin.Image = img Me.Controls.Add(bihin)     bihin.dispose() Next
YAmaGNZ

2020/11/07 02:25

使いまわしとは どこかで1回PictureBoxを生成しておいて この処理では For i = 1 to 4 '~~~DBからresourceを読み出します~~~~' fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) Dim oldImage = bihin(i).Image bihin(i).Image = img oldImage.Dispose() Next といった感じで画像をセットするだけでいいのではないですかということです。
hanbee.com

2020/11/07 12:27

返信が遅くなり、申し訳ありません。 YAmaGNZさんの提示してくださったコードをいまから試してみようと思います。 bihin(i)のような配列の指定変数みたいなものを使うのは初めてです。
hanbee.com

2020/11/07 12:44

一つ一つ理解できずにすみませんが、 どこかで一回Pictureboxを生成しておくというのは 以下のようにするのでしょうか? New 句はもういらなくなるのですか? ↓↓↓ Dim bihin(5) as PictureBox for i = 1 to 4 ’それぞれのbihin(i)にimageを入れる Next
hanbee.com

2020/11/07 12:52

Dim bihin(5) as New PictureBoxとはできませんでした。 配列をNewで宣言することはできませんとなります。
hanbee.com

2020/11/07 12:57

>使いまわしとは1回どこかでPictureBoxを生成して配列にでも入れておいて このやり方がわかりません。。。
hanbee.com

2020/11/07 13:14

また、oldimageはなんのために宣言するのでしょうか? Dim oldImage = bihin(i).Image とありますが、この時点ではbihin(i).ImageはNothingのはずです。 bihin(i).Image = img img.Dispose() ではだめなのでしょうか。
YAmaGNZ

2020/11/07 14:01

コントロールの配列に関して調べてください。 また、Disposeするのは何なのか考えてください。 私が書いたのはあくまで例であり動作を保証するものではありません。 ようは前にセットしてあったイメージをDisposeして新しいものをセットしましょうということです。 PictureBoxのImageにセットする前に保存しておくのは、PictureBox.Imageにセットされている状態でDisposeするとDispose後にPictureBox側からアクセスされる可能性があるので、Imageプロパティに新しいImageをセットし古いImageをPictureBoxから完全に切り離すという目的です。
hanbee.com

2020/11/08 00:07

ありがとうございます。oldimageを宣言するのはもともとbihin(i).imageに格納されているリソースを移してこれをdisposeするためだけのものってことだと理解しました。(1回目はNothingなはずなので2回目以降有効)これであっていますでしょうか? また、そうすると一つ疑問が浮かんだのは Dim oldimage =bihin(i).image oldimage.dispose() にしても、bihin(i).imageの中にdisposeは残っているのではないか?ということです。 でもそれは大丈夫なのですね。 いま作ったコードを書きます。以下のページも参考にして作りました https://dobon.net/vb/dotnet/control/buttonarray.html ↓↓↓ 'ボタンコントロール配列のフィールドを作成 Private bihin() As System.Windows.Forms.PictureBox 'ボタンコントロール配列の作成 Me.bihin = New System.Windows.Forms.PictureBox(3) {} For i = 1 to 4 Me.bihin(i) = New System.Windows.Forms.PictureBox '~~~DBからresourceを読み出します~~~~' fs = File.OpenRead(resource) img = Image.FromStream(fs, False, False) If Not bihin(i).Image Is Nothing Then '古いイメージを削除(イベント発生2回目以降有効) Dim oldImage = bihin(i, j).Image oldImage.Dispose() End If bihin(i).Image = img Next ↑↑↑ 一応これで動作はしますが、だめなのは分かります。 配列の中でピクチャーボックスを生成してしまっているから、 If Not bihin(i).Image Is Nothing Then の分岐には行かないということですよね。 YAmaGNZさんの言われる使い回しにできるようにまず考えてみます。 あと、もしかしてYAmaGNZさんの意図では >PictureBox.Imageにセットされている状態でDisposeするとDispose後にPictureBox側からアクセスさ>れる可能性があるので ということですから、 oldimage.disposeはbihin.image=imgの後にやったほうがいいのでしょうかね。。。
hanbee.com

2020/11/08 00:54

思うのですが、使いまわしをすることで動作はいくらか軽くなるのでしょうか? 配列で生成している今のコードではdisposeは必要がないのではと今思いました。 なぜメモリがいっぱいになってしまうのでしょうか。 YAmaGNZさんの言われる使い回しをすれば、、、軽くなるのでしょうか。 現状のコードではフォームのデザインに何も配置していません。全て生成と削除をコードで記入しています。 同じフォームで何度も生成と削除を繰り返す。。 それが多分だめなのでしょうね。。。使い回しの方が軽くなりそうですね。
hanbee.com

2020/11/08 01:02

あと削除といっても以下のようにVisibleプロパティをFalseにしているだけです。 メモリが大きくなっていくのはこれだからかもしれません。 Dim objControl As Control On Error Resume Next For Each objControl In Me.Controls objControl.Visible = False Next
YAmaGNZ

2020/11/08 01:20

PictureBoxを使いまわすということは、どれだけの時間実行してもPictureBoxは最初のものだけということになります。 細かい話になりますが、「PictureBoxを作る」「PictureBoxを削除する」という処理を行う必要がなくなるのでその分軽くなります。 また、 Dim oldImage = bihin(i).Image oldImage.Dispose() bihin(i).Image = img ではなく Dim oldImage = bihin(i).Image bihin(i).Image = img oldImage.Dispose() この順番です。 何故前者ではダメなのかというと Dim oldImage = bihin(i).Image oldImage.Dispose()   この時点でImageは破棄されるがPictureBoxはまだImageのインスタンスを保持していて、PictureBoxがその破棄されたImageを使う可能性がある なので Dim oldImage = bihin(i).Image bihin(i).Image = img このようにPictureBoxのImageを新しいものに変えてしまえばPictureBoxは古いImageのインスタンスは保持しなくなるのでアクセスできなくなります。 このように確実にアクセスできないようにしてから破棄してくださいということです。 VisibleプロパティをFalseにすることを削除とは言いません。 非表示と言います。 Control.Visible プロパティ https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.control.visible?view=netframework-4.8 なので、見えなくなっているだけで存在します。 そしてPictureBoxは新たに生成され続けるのでPictureBoxの数はどんどん増えていきます。 PictureBoxを使いまわすということはこういったことも防げることになります。
hanbee.com

2020/11/08 01:45

なるほどです。納得しました。 親切丁寧な説明ありがとうございます。 ちょっと時間はかかりますが使いまわしのコードに今から直そうと思います。
YAmaGNZ

2020/11/08 04:05

「使いまわす」といった言葉がややこしいのかもしれませんね。 最初からPictureBoxを必要個数分配置しておいて、それに対してImageをセットするという言い方なら分かりますか?
hanbee.com

2020/11/08 04:14

ありがとうございます。 「使い回す」という言葉ですが、理解できていると思っています。 今ピクチャーボックスを必要個数分formのloadイベントで生成して使い回すようにコードを編集している最中です。 このloadイベントを何回も繰り返すようにプログラムを作ってしまっていたので、loadイベントははじめの一度きりしか使わず、後はその生成したピクチャーボックスを更新するイベントを繰り返すようにする方針です。 「使い回す」はとても分かりやすい表現だと感じてます!
hanbee.com

2020/11/08 04:50

生成するイベントは表題のコードをそのまま使えばいいと思っていますが、 更新するイベントは表題のコードで配列として生成したピクチャーボックスにアクセスしないといけないので、ちょっと難しいですね。 フォームのデザインに配置しているわけではないので、Loadイベントの中以外ではbihin(i)は 宣言されていない扱いになってしまいます。 これについてももうちょっと考えてみようと思います。
guest

回答1

0

ベストアンサー

変数のスコープなど基本を学習する必要があるかと思います。
また、コメントにて「コントロールの配列に関して調べてください。」と書きましたが
「VB.NET コントロール 配列」と検索すれば下記のようにコントロールを配列に格納して利用するようなコードが紹介されているページがいろいろ出てくるはずです。

VBNET

1Private Bihin(4) As PictureBox 2 3Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load 4 For i = 0 To 4 5 Bihin(i) = New PictureBox() 6 Next 7End Sub 8 9Private Sub proc() 10 11 For i = 0 To 4 12 '新しいイメージを取得する処理 13 Dim newImage As Image = Nothing 14 15 '古いイメージの破棄&新しいイメージをセット 16 Dim oldImage = Bihin(i).Image 17 Bihin(i).Image = newImage 18 19 oldImage?.Dispose() 20 'この書き方はNull条件演算子といって下記のコードと同等です。 21 'If oldImage IsNot Nothing Then 22 ' oldImage.Dispose() 23 'End If 24 25 Next 26 27End Sub

また、表示されているコントロールを破棄するのは

VBNET

1Dim removecontrol = Me.Controls("PictureBox1") 2Me.Controls.Remove(removecontrol) 3removecontrol.Dispose()

という感じでControlsコレクションより削除して、そのコントロールをDisposeする処理となります。
もし、他に変数などにインスタンスを保持している場合はそれらもクリアして破棄対象のインスタンスを保持しているものはクリアする必要があります。

投稿2020/11/08 06:10

YAmaGNZ

総合スコア10258

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hanbee.com

2020/11/08 06:59

ご回答ありがとうございます。 変数のスコープもそうですし、出来れば何でもいいという調子でどんどん進んできたせいで、基本が全然できておりません。落ち着いたらまた基本に立ち返って学習したと思います。 提示してくださったコードのように試してみました。 古いイメージの破棄&新しいイメージをセットを自分なりに作ってみたものの、 動作はしますが、目的の「古いイメージの破棄&新しいイメージ」がプログラムに反映されていないようです。 Loadイベントでbihin(i)をpanel.controls.Add(bihin(i))として配置していますが(これは関係ないことかもしれないのですが、実はMe.controls.addではなく配列で生成したPanelの上に配置しています。)、 Bihin(i).Image = newImageとしてbihin(i)のimageプロパティを変更するだけでは反映されないのかもしれません。 それこそ、panel.controls.Remove(bihin(i))と、panel.controls.Add(bihin(i))を加えないといけないのかなと考えています。ここでもLoadイベントで生成したPanelを他のイベントで指定して使わないといけないのかなと悩んでいます。話をややこしくしてすみません。
YAmaGNZ

2020/11/08 08:04

>Bihin(i).Image = newImageとしてbihin(i)のimageプロパティを変更するだけでは反映されないのかもしれません。 いつどこで何を設定して何が反映されないのですか? また、コントロールの破棄については勘違いされていたので提示しただけです。 コントロールを使いまわすのであれば不要です。
hanbee.com

2020/11/08 08:45

はい、ややこしいことを言ってすみません ある処理が終わって、再度bihin(i)を更新するイベントを起こしています YAmaGNZさんのコードで言えばPrivate Sub proc()です。 ↓↓↓ Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load For i = 0 To 4 Bihin(i) = New PictureBox() Next End Sub Private Sub Kousin(sender As Object, e As EventArgs)   For i = 1 to 4 '~~~新たにresouceを読み出す~~~' fs = File.OpenRead(resource) newimg = Image.FromStream(fs, False, False) Dim oldImage = bihin(i).Image bihin(i).Image = newimg oldImage?.Dispose() Next End Sub ↑↑↑ というようにコードを書きました。 しかし、bihin(i)の表示は最初にLoadしたときのままになっています。
YAmaGNZ

2020/11/08 09:05

Form1_Loadで作成したPictureBoxは本当に表示されているのですか? Kousinは呼び出されているのですか? 呼び出されているので有れば正しいイメージは読み込めているのですか?
hanbee.com

2020/11/08 10:21

LoadでPictureBoxは正しく表示されます。 Kousinも適当なMessagebox.show("あいうえお")を入れても出ていたので、呼び出されています。 一旦アプリを消して再度Loadしたら更新された状態で表示されるので最悪いいといえばいいのですが、 Kousinは機能できていないことになります。 自分のKousinのコードが間違っているかもしれないのですが。。
YAmaGNZ

2020/11/08 10:44

Kousinをどうやって呼び出しているのか分かりませんし 「一旦アプリを消して再度Loadしたら」とは何を指しているのかも分かりません。 提示されたコード部分ではなく他の場所に問題があるように思えます。
hanbee.com

2020/11/08 10:53

分かりました。もうちょっと考えてみます。ありがとうございます。
hanbee.com

2020/11/08 12:39

https://dobon.net/vb/dotnet/control/buttonarray.html ここでのソースコードと同じように動的にコントロールを生成して配列も作成しているつもりです。 そしてKousinでbihin(i)のimageプロパティを変更してもやっぱり表示が変わりません。 Kousinでbihin(i).image=newimgとしてもエラーにならないので、ちゃんと配列に入ってるんだと思っているんですが。。。
hanbee.com

2020/11/08 12:48

https://social.msdn.microsoft.com/Forums/vstudio/ja-JP/ca2b6a53-fdf9-40d2-b0e6-88e09f62c25e/21205303401239529983251041237512383124671253112488125251254012 多分この質問の投稿者の人も自分と同じことに悩んでいるのかと思いますが、 回答者の方が言われている >自分で動的に作成しているのであれば、作成した際にそれを配列などにしまっておいて というのが自分の場合はKousinでbihin(i)にエラーは出ていませんし、もうできているんだと思うんですが、表示が変わらないのが本当に謎ですね。。。汗
hanbee.com

2020/11/08 12:50

YAmaGNZ様には長きにわたって私の質問に丁寧なご回答を頂きまして、本当に助かりました。ありがとうございます。
hanbee.com

2020/11/29 05:32

いつもお世話になっております。YAmaGNZさんの提案いただいた使いまわしのお陰でメモリがいっぱいになるという表題の問題点はクリアできていますので、これからKousinプロシージャを見直していこうと思います。それができてからこの質問をクローズしたいと思います。
hanbee.com

2020/11/29 05:55

すみません。やっぱり違う趣旨の質問となるのでいったんこの質問はクローズして新しく上げさせていただきます。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問