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

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

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

Re[1]: For Each文でNextステートメントを使わない


(過去ログ 129 を表示中)

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

■76567 / inTopicNo.1)  For Each文でNextステートメントを使わない
  
□投稿者/ しゃむこ (8回)-(2015/07/22(Wed) 21:19:35)

分類:[.NET 全般] 

.NET 3.5

パネルコントロール内の子クラスを解放します。
この際に、For Each文でPanel.Controlsを処理すると、1個とびに処理され、上手く全件が処理できません。

Private sub addObj()
 For i = 0 To 99
  Dim obj = New ClassA
  obj.Name = i
  Panel.Controls.Add(obj)
 Next i
End Sub

Private sub delObj()
 For Each obj As ClassA In Panel.Controls
  obj.Dispose()
  Console.Writeline(obj.Name) '←処理されるのは0,2,4,6,8...と一個飛びです。
 Next
 Panel.Controls.Clear()
End Sub

下記のように、そもそもFor Eachを使わず適当に書けば回避はできるので、解決はしているのですが。。
ただ、For Each文がどうにも気持ち悪い動きです(Disposeしているのが原因だとは思うのですが)。
Nextステートメントを使わない、など何か方法があったり、私が勘違いしている部分があればお知恵をお貸しいただけないでしょうか。

Private sub delObj()
Dim i As Integer = 0
 Do Until i = Panel.Controls.Count
  Dim obj As ClassA = Panel.Controls.Count(i)
  obj.Dispose()
  Console.Writeline(obj.Name) '←処理されるのは0,1,2,3,4...と正常です。
 Loop
 Panel.Controls.Clear()
End Sub

引用返信 編集キー/
■76568 / inTopicNo.2)  Re[1]: For Each文でNextステートメント
□投稿者/ Azulean (505回)-(2015/07/22(Wed) 22:26:14)
2015/07/22(Wed) 22:26:25 編集(投稿者)
No76567 (しゃむこ さん) に返信
> パネルコントロール内の子クラスを解放します。
> この際に、For Each文でPanel.Controlsを処理すると、1個とびに処理され、上手く全件が処理できません。

コントロールが Dispose されるとき、内部で勝手に親のコレクションから自分を削除する処理が実行されています。
今回の場合、コントロールを Dispose することで Panel.Controls から要素が削除されます。

削除することで 1 つ前に詰めた形になることと、For Each で次に進める形になることが重なることで、2 つ一気に進んだように見えているだけです。
列挙中にコレクションの要素を削除すると、コレクションによっては例外が発生するため、通常、このような実装をすることはありません。

このようなケースでは、末尾から消していくか、リストを複製することで回避します。

Dim controlArray = Panel1.Controls.OfType(Of Control).ToArray()
For Each c In controlArray
    c.Dispose()
Next

引用返信 編集キー/
■76572 / inTopicNo.3)  Re[1]: For Each文でNextステートメントを使わない
□投稿者/ Jitta (1回)-(2015/07/23(Thu) 11:52:57)
No76567 (しゃむこ さん) に返信

dispose後に使用するのは避けましょう。
nameプロパティーは削除されるリソースを使わないですが、全てがそうとは限りません。

panel.controls を dispose する目的はなんでしょう?
通常、panel を dispose すれば、その中で行われます。
引用返信 編集キー/
■76574 / inTopicNo.4)  Re[1]: For Each文でNextステートメントを使わない
□投稿者/ 魔界の仮面弁士 (423回)-(2015/07/23(Thu) 13:18:46)
2015/07/23(Thu) 14:43:20 編集(投稿者)

No76567 (しゃむこ さん) に返信
> 下記のように、そもそもFor Eachを使わず適当に書けば回避はできるので、解決はしているのですが。。
> Dim obj As ClassA = Panel.Controls.Count(i)
実際のコードは .Controls.Count(i) ではなく、.Controls(i) ではありませんか?


>  Panel.Controls.Clear()
Clear する前に Dispose することは大事ですが、Azulean さんも書かれていたように、
Dispose することで、そのコントロールは Controls から取り除かれてしまいます。



> Nextステートメントを使わない、など何か方法があったり
既に回答はついていますが、全削除する場合の実装例を幾つか紹介しておきます。


=======================================
案1) 常に 0 番目を Dispose していく
---------------------------------------
For n = 1 To Panel1.Controls.Count
  Panel1.Controls(0).Dispose()
Next
-----

コードがスッキリと簡潔に書けるということで、たまに見かけます。
(個人的にはあまりお奨めしていないのですが)


私が避ける理由:

・「Dispose される際に、Controls から自動的に取り除かれる」
 ということを知らない人には、毎回 0 番を処理することの意図が分かり難い。

・C# に慣れた人が、上記を「for (int n = 1; n <= Panel1.Controls.Count; n++)」という
 意味に誤解してしまう危険性がある。
 (VB の For ループにおける To 句は先行評価だが、C# の場合は逐次評価のため、別の結果になる)


=======================================
案2) 降順に Dispose していく
---------------------------------------
For n = Panel1.Controls.Count - 1 To 0 Step -1
  Panel1.Controls(n).Dispose()
Next
'Panel1.Controls.Clear()
-----

後ろから辿るのは、削除を伴うコレクション処理の定番です。
これは Azulean さんの A案でもあり、私はこの方法を使うことが多いです。


なお、最後の Clear は、記述してもしなくても構いません。(私は書かない派です)

ただ、「Dispose される際に、Controls から自動的に取り除かれる」という
前提を知らない人向けには、あえて Clear も書いておい方が
分かりやすいかも知れません。(No76567 の delObj では Clear していますね)


=======================================
案3) 処分する前に、別のコレクションに複写しておく
---------------------------------------
No76568 のコード (Azulean さんの B案)
-----

これも良く使われる方法ですね。私も時々使います。

Dispose すると Controls から取り除かれてしまうため、
事前に Controls 以外のコレクション(この場合は controlArray)を
用意しておき、それを使って削除しています。

なおこの方法は、コレクションの要素がクラス(参照型)の場合に
効果を発揮します。(値型のコレクションにはむきません)


=======================================
案4) Dispose 前に RemoveAt していく
---------------------------------------
For n = Panel1.Controls.Count - 1 To 0 Step -1
  Dim ctrl As Control = Panel1.Controls(n)
  Panel1.Controls.RemoveAt(n)
  ctrl.Dispose()
Next
-----

列挙方法としては、案2 と同じですが、Dispose の前に
明示的に RemoveAt しておくという点が異なります。

これにより、「Dispose すると、Controls からも自動的に取り除かれる」
という前提を知らない人に対しても、コードの意図が伝わりやすくなります。
(その分、コードが少し冗長となりますが…)



=======================================
案5) 子コントロールが 0 個になるまで、0 番目を処分していく
---------------------------------------
Do Until Panel1.Controls.Count = 0
 Using ctrl As Control = Panel1.Controls(0)
  Panel1.Controls.Remove(ctrl)
 End Using
Loop
-----

0 番目を処理するという点では 案1 と一緒ですし、
明示的に Controls から取り除いていくという点では 案4 と同じです。
(While 条件で「While Panel1.Controls.Count > 0」としても可)



> 下記のように、そもそもFor Eachを使わず適当に書けば回避はできるので、解決はしているのですが。。
もちろん、しゃむこさんが No76567 に記載した書き方でも問題ないと思います。
引用返信 編集キー/
■76575 / inTopicNo.5)  Re[2]: For Each文でNextステートメント
□投稿者/ しゃむこ (9回)-(2015/07/23(Thu) 13:51:03)
No76568 (Azulean さん) に返信
> 列挙中にコレクションの要素を削除すると、コレクションによっては例外が発生するため、通常、このような実装をすることはありません。
ありがとうございます。勉強になります。
汎用・万能な書き方、参考にさせていただきます。


No76572 (Jitta さん) に返信
> nameプロパティーは削除されるリソースを使わないですが、全てがそうとは限りません。
ありがとうございます、正しくはdispose前ですね。
console自体はデバッグのために追加した1行で、本来は特に処理していません。

> panel.controls を dispose する目的はなんでしょう?
> 通常、panel を dispose すれば、その中で行われます。
パネル内に最大100件のオブジェクトを配置・表示し、ページジング処理で100件ずつ切替えるようなイメージ、です。
したがって、オブジェクトは動的に配置していますが、パネルはデザイナで配置してるのでソース上でdisposeはしたくないな、という程度の意味合いです。

No76574 (魔界の仮面弁士 さん) に返信
> 実際のコードは .Controls.Count(i) ではなく、.Controls(i) ではありませんか?
すいません、正しくはその通りです。(簡略サンプルをつらつら書いたので幾つかミスがあります。。)

先に書いたとおりdisposeしたりControls.Clearしてる目的はページングです。
そもそももpanel.controlsコレクションがどう動いているのかよく確認していなかったため、
何故かメモリリークしてるなぁというところからの原因→今回の疑問でした。
都度動きをよくみて、適当な列挙・破棄処理をしたいと思います。

大変参考になりました。みなさんありがとうございました。
解決済み
引用返信 編集キー/
■76601 / inTopicNo.6)  Re[3]: For Each文でNextステートメント
□投稿者/ shu (764回)-(2015/07/27(Mon) 14:18:44)
No76575 (しゃむこ さん) に返信

解決済のようですが、コントロールを作成・破棄を繰り返すよりは
表示・非表示の切り替えだけにした方が良いです。


解決済み
引用返信 編集キー/
■76603 / inTopicNo.7)  Re[4]: For Each文でNextステートメント
□投稿者/ しゃむこ (12回)-(2015/07/27(Mon) 15:15:42)
2015/07/27(Mon) 19:19:49 編集(投稿者)
2015/07/27(Mon) 19:19:22 編集(投稿者)

ご指摘ありがとうございます。
実際、表示は遅いですし、処理中にコントロールへの操作が割り込むと予期せぬ動きをしますね。

今回の画面では、
パネルに多数のコントロールを作成してパネル内スクロール表示しているため、
作成・破棄でなく最初からコントロールを配置したままvisibleで制御を行う場合だと、
表示件数が少ない時に、visible=falseで何もないコントロールがある最下段まで
スクロール表示されてしまう、という問題があり、このような実装になっています。

実装ありきでなく仕様ありき開発なので。。少し調整します。ありがとうございます。


No76601 (shu さん) に返信
> ■No76575 (しゃむこ さん) に返信
>
> 解決済のようですが、コントロールを作成・破棄を繰り返すよりは
> 表示・非表示の切り替えだけにした方が良いです。
>
>
解決済み
引用返信 編集キー/


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

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -