- PR -

VB.NETでhashtableに入れた構造体の再代入でエラー

1
投稿者投稿内容
piromi
会議室デビュー日: 2004/07/27
投稿数: 3
お住まい・勤務地: 東京
投稿日時: 2004-07-27 09:41
初めて質問します。よろしくお願いします。
VB.NETは今だ2002です。
やりたいことはhashtableを使ってkeyごとにtest構造体のa,bに引数のa,bを加算し、
加算し終わったらkeyごとにtest構造体のcにa/bを代入したいのです。

==============================================================================
'集計クラス
Public Structure Test
Public a As Double
Public b As Double
Public c As Double
End Structure

Public Class Calc
Private hs As New Hashtable()

't.keyごとにt.a,t.bを加算する
Public Sub sum(ByVal key As String, ByVal a As Double, ByVal b As Double)
If Not hs.ContainsKey(key) Then
Dim t As Test
t.a = a
t.b = b
hs.Add(key, t)
Else
Dim t As Test = CType(hs(key), Test)
t.a += a
t.b += b
hs(key) = t
End If
End Sub

't.c=t.a/t.b
Public Sub div()
Dim key As String
For Each key In hs.Keys
Dim t As Test = CType(hs(key), Test)
t.c = t.a / t.b
hs(key) = t
Next

End Sub
End Class
==============================================================================
'呼び出し
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim clc As New Calc()

clc.sum("A", 1.0, 2.0)
clc.sum("A", 2.0, 3.0)
clc.sum("B", 4.0, 2.0)
clc.sum("B", 1.0, 5.0)
clc.sum("A", 1.0, 2.0)

clc.div() '<- ここでエラー

End Sub
==============================================================================
Calcクラスのsumメソッドは正常に動きますが、divメソッドを呼ぶと

'System.InvalidOperationException' のハンドルされていない例外が mscorlib.dll で
発生しました。
追加情報 : コレクションが変更されました。列挙操作は実行されない可能性がありま
す。

というエラーが出てしまいます。sumメソッドのときは構造体を再代入しても問題なかっ
たのに、divメソッドではなぜエラーになるのか分かりません。for eachで回しているせ
いでしょうか?この場合どのようにコードを書けばいいのでしょうか?
みなさまの知恵をお貸しください。
あみゅせる
常連さん
会議室デビュー日: 2004/07/08
投稿数: 33
お住まい・勤務地: 神奈川県
投稿日時: 2004-07-27 10:17
こんにちは。

うすうすは感づいていらっしゃるようで。
引用:

for eachで回しているせいでしょうか?


列挙インターフェース(IEnumerator)の性質で、
要素をいじると以降の順序アクセス(たぶん内部的にMoveNext)
で怒られるみたいですね。

さて、問題の 「hs(key) = t」ですが、
実はこれを無くしても期待する動作を行うような気がします。(未確認)

一連のtの代入はオブジェクト参照なので書き戻しをするまでもなく、
ハッシュテーブルのコンテナ内部のTestオブジェクトのメンバ
「a.b.c」にアクセスがすんでいませんか?

それとも構造体の代入はコピーなんでしたっけ?
_________________
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2004-07-27 10:18
div()内でFor Eachで繰り返していますよね。
で、For Each内でHashTableが書き換えられているために、内部で呼び出されているMoveNext()メソッドが無効状態になっているわけです。

MSDNライブラリのIEnumerator.MoveNext()メソッドの説明に以下のように書いてあります。
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/cpref/html/frlrfsystemcollectionsienumeratorclassmovenexttopic.asp
コレクションが変更されない限り、列挙子は有効なままです。要素の追加、変更、削除などの変更がコレクションに対して実行されると、列挙子は回復不可能な無効状態になり、次に MoveNext または Reset を呼び出すと、 InvalidOperationException がスローされます。

>あみゅせるさん
>それとも構造体の代入はコピーなんでしたっけ?
そうですね。ボックス化されて参照型になってはいますが、
CType(hs(key), Test)
とボックス化を解除した時点で、その参照型のインスタンスとは別の構造体がメモリ上に作られます。

ソースを見る限り、For Eachで回しても要素のキーや数は変わらなそうですから、Keysプロパティのコレクションをコピーして、それからそのコピーしたコレクションでFor Eachってのはどうです?


[ メッセージ編集済み 編集者: 一郎 編集日時 2004-07-27 10:27 ]
piromi
会議室デビュー日: 2004/07/27
投稿数: 3
お住まい・勤務地: 東京
投稿日時: 2004-07-27 10:54
あみゅせるさま、一郎さま、早速ご返答ありがとうございます。

構造体の代入はコピーだったので、Test構造体をクラスにしてdivメソッドで
hs(key) = t
をはずすときちんとできました。

そもそも構造体にしたのはいちいち生成しなくてよいこと、構造体の方が
メモリ消費量が少ないということからだったのです。

参考記事:プロフェッショナルVB.NETプログラミング 第3回 構造体の宣言とその効能
http://www.atmarkit.co.jp/fdotnet/vb6tonet/vb6tonet03/vb6tonet03_03.html

実際keyの数は非常に多くなりそうなので、構造体のままで対処を考えたいと思って
います。

>一郎さま
>ソースを見る限り、For Eachで回しても要素のキーや数は変わらなそうですから、Keys
>プロパティのコレクションをコピーして、それからそのコピーしたコレクションでFor
>Eachってのはどうです?
具体的にどうやればいいのか分かりませんでした。何かサンプルを教えていただけると
助かります。
一郎
ぬし
会議室デビュー日: 2002/10/11
投稿数: 1081
投稿日時: 2004-07-27 11:26
引用:

piromiさんの書き込み (2004-07-27 10:54) より:
そもそも構造体にしたのはいちいち生成しなくてよいこと、構造体の方が
メモリ消費量が少ないということからだったのです。

実際keyの数は非常に多くなりそうなので、構造体のままで対処を考えたいと思って
います。


なるほど、そういう理由で構造体ですか。
ボックス化とボックス化解除について意識をされるとよろしいかと思います。
http://www.microsoft.com/japan/msdn/library/default.asp?url=/japan/msdn/library/ja/csref/html/vclrfboxingunboxingpg.asp
今回の場合はHashTableに入れる段階でボックス化されていますので、HashTableに入れた構造体と同じ数の参照型のインスタンスが生成されています。(残念ながら)
そして値を取り出すときにはまた同じ数の構造体が生成されます。

現在のソースでは、構造体の悪い部分(HashTableに入っている構造体の値だけを書き換えられない)とクラスの悪い部分(インスタンスの生成の影響)の両方の影響を受けているということですね。
クラスのほうがパフォーマンスが良いのではないでしょうか。
piromi
会議室デビュー日: 2004/07/27
投稿数: 3
お住まい・勤務地: 東京
投稿日時: 2004-07-27 11:40
一郎さま

>今回の場合はHashTableに入れる段階でボックス化されていますので、HashTableに入れ
>た構造体と同じ数の参照型のインスタンスが生成されています。(残念ながら)
>そして値を取り出すときにはまた同じ数の構造体が生成されます。

つまりメモリ消費を気にして構造体にした意味がないということですね。
むしろクラスの方がコードもすっきりするので、もう一度検討します。

構造体とクラスについては中途半端な知識しかなかったので、ボックス化については
とても参考になりました。ありがとうございました。

あみゅせる
常連さん
会議室デビュー日: 2004/07/08
投稿数: 33
お住まい・勤務地: 神奈川県
投稿日時: 2004-07-27 14:04
こんにちは。
一郎さん。ありがとうございます。
引用:

そうですね。ボックス化されて参照型になってはいますが、
CType(hs(key), Test)
とボックス化を解除した時点で、その参照型のインスタンスとは別の構造体がメモリ上に作られます。


う〜む そうですよね。
「何でも積めるコンテナ」はホントに積むだけだった...
コード:
object x=new Test();
((Test)x).c=((Test)x).a/((Test)x).b;


これが通らないのには一瞬固まりました。
よく理解していないことが確認できました。

piromiさん
引用:

つまりメモリ消費を気にして構造体にした意味がないということですね。
むしろクラスの方がコードもすっきりするので、もう一度検討します。


今回は適用しにくかったり、構造体も一気に大量確保するわけではないので
クラスで実装したほうがいいと思います。
でも、これらを検討したことが大変に意味があると思います。
私も、勉強させていただきました
_________________
1

スキルアップ/キャリアアップ(JOB@IT)