Listコレクションについて

タグの編集
投稿者 たかくん  (社会人) 投稿日時 2012/6/7 11:37:36
こんにちは、今ある自作クラスをメソッドの引数にしてそのメソッドの中でList.Add()で追加しています。
ですがListをいざ取り出すと0番目から全てのオブジェクトが一番最後に登録された物になってしまいます。
理由は同じオブジェクトのアドレスを追加しているからだろうと思いますが解決手段が見つかりません。
誰か解る人、よろしくお願いします。
投稿者 YuO  (社会人) 投稿日時 2012/6/7 12:17:49
参照型であるならば,Addするオブジェクトはそれぞれ別々にNewされたものでなければなりません。

例えば,
Class Foo
    Public Value As Integer
End Class
に対して,
Dim items As New List(Of Foo)
Dim f As New Foo
f.Value = 1
items.Add(f)
f.Value = 2
items.Add(f)
ではなく,
Dim items As New List(Of Foo)
Dim f As Foo
f = New Foo()
f.Value = 1
items.Add(f)
f = New Foo()
f.Value = 2
items.Add(f)
のように,それぞれNewする必要があります。
投稿者 たかくん  (社会人) 投稿日時 2012/6/7 13:20:01
YuOさん、ありがとうございます、
  ''' <summary>
    ''' リプレイデータを盤に追加する。
    ''' </summary>
    ''' <param name="bords ">石の二次元配列</param>
    ''' <param name="position">一手分の盤の論理位置</param>
    ''' <param name="state">石の種類</param>
    ''' <remarks></remarks>
    Public Sub Add(ByRef bords As Bord, ByVal position As Point, ByVal state As Stone.StoneState)
        '盤をNewしました。
        Dim bord As Bord = New Bord(KeyWordMaxStoneCollShort,KeyWordMaxStoneRowShort)        
        bord.BordStones = bords.BordStones   <-引数で受け取ったStoneクラスを代入しました。
        'Me.StoneLogicalPointList.Add(position)
        'Me.StoneEnumColorList.Add(state)
        Me.ReplayStoneBordList.Add(bord)      <-代入された盤クラスほリストに追加しました。
    End Sub
まだ奮闘してます。
言われる意味は解るのですがうまくいきません。
簡単に説明するとリストに登録したいものはbordクラスの中にあるStone(,)クラス(BordStonesの事)
です。
StoneクラスはBordクラスがNewされた時点でインスタンス化されています。
後にGUIのListBoxからReplayStoneBordListを参照するのですがやっぱり全部同じアドレスの参照に
なってます。
気になって食事もできない・・・
説明が足りなければまた聞いて下さい。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/6/7 13:24:52
たとえば自作クラスではなく、Form1 の TextBox1 を登録する場合を考えてみてください。
 myList.Add( TextBox1 )
 myList.Add( TextBox1 )
とした場合、myList(0) も myList(1) も同じテキストボックスが参照されますよね。


もし、List(Of TextBox) に登録する前に、Textプロパティを修正して
 TextBox1.Text = "ABC"
 myList.Add( TextBox1 )
 TextBox1.Text = "XYZ"
 myList.Add( TextBox1 )
のようにしたとしても、myList(0).Text と myList(1).Text は同じ値を返すことになります。

別の値を返すなら、TextBox1 と TextBox2 のように、異なるインスタンスを登録する必要があります。
これと同様の状況に陥っているのではないでしょうか。
投稿者 たかくん  (社会人) 投稿日時 2012/6/7 13:48:55
魔界の仮面弁士さんありがとうございます。
僕のAdd()は呼び出す度にbordクラスのインスタンスを作成しています。
なので呼び出す度に違うインスタンスのはずなんですけどね。
行き詰まってるので少し休憩して頭を休めてみます。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/6/7 13:50:32
bord は「坑道(鉱山などの採掘通路)」を意味します。
「盤」の意味なら board ですよ。


> Public Sub Add(ByRef bords As Bord, ByVal position As Point, ByVal state As Stone.StoneState)
この場面で、第一引数を ByRef にする必要は無いはずです。

通常は ByVal のみを使用するようにしてください。
ByRef 指定が必要になる場合というのは、そのメソッド内で
 bords = 新しいデータ
のように、引数に新しいデータを代入し、それを呼び出し側で
受け取るような場合ぐらいです。(あとは一部のAPI利用時など)


> Stone(,)クラス
Stone クラスの二次元配列という事ですか?


> bord.BordStones = bords.BordStones   '<-引数で受け取ったStoneクラスを代入しました。
自作クラスだと想像しにくいと思いますが、Label や TextBox などの
画面部品を想像してみると分かりやすいと思います。

たとえば上記が、
 bord.MessageLabel = Form1.Label1
のように書かれていた場合、これはラベルのコピーでは無く、
常に Form1 上にある Label1 というラベルを扱う事になるでしょう。

それを好しとするのであれば問題はありませんが、Form1/bords に依存しない
単独の Label /Stone としたいのであれば、代入時点で新たに New Label / New Stone しなおすか、
Stone の Clone Copy を作成するなどして「別のインスタンス」を用意せねばなりません。

もしくは、クラスの代わりに構造体を採用するという選択肢もあります。


> StoneクラスはBordクラスがNewされた時点でインスタンス化されています。
それは Form1.Label1 や Form1.TextBox1 の場合も同じですよね。
Form1 が New された時点でインスタンス化されているわけですから。
投稿者 ズッカ  (社会人) 投稿日時 2012/6/7 15:14:14
 >      bord.BordStones = bords.BordStones   <-引数で受け取ったStoneクラスを代入しました。

この代入式により、bord.BordStones とbords.BordStonesは、当然、同じStone クラスのインスタンスを指すことになります。
なので、魔界の仮面弁士さんの指摘のように、クラスの値をひとつずつコピーする(あるいはStone クラス内に
コピーを行うメソッドを作成する)必要があります。
投稿者 (削除されました)  () 投稿日時 2012/6/8 00:24:00
(削除されました)
投稿者 m190  (社会人) 投稿日時 2012/6/8 00:36:39
すみません、余計に混乱させちゃうかもしれませんが。。。
#私自身こういうのスッゴイ苦手なんです(なので一緒に勉強したいです)


なんだか最初は
参照渡しで受け取った bords を、Newせずそのまま boad に入れてる様な気がしたので・・・

たとえば
> Dim MyBords As New Bord = bords (←坊主が暴動なのか頭が混乱してきますが)
> bord.BordStones = MyBords.BordStones

みたいなことを考えてたんですが、でも↑これじゃ、bords が変更されたら
結局 bord も道連れだよ、ってことになっちゃうんですよね?(おそらく)
#受け取った ByRef bords As Bord は、呼出し側の bordsへの参照

だから
引数を ByVal で受けるとか、あるいは、参照渡しされた boads の値を 1つづつコピーして
リストに追加する作業が必要になる、みたいな感じの捉え方で宜しいのでしょうか?
#ByValなら、その時点での boads のスナップショットが渡される見たいなイメージですか?


似たようなサンプル作って試してる内に、何だか気付くとドツボにはまり込んじゃいまして、、、
#って言うか、なかなか上手く再現できなくって。。。
でも、どーにも気になっちゃったものですから、つい・・・
#あースミマセン、ますます混乱させちゃったみたいです(涙

・・・っていうか、正直言うと
> 気になって食事もできない・・・
実はこっち↑の方が気になってたりして。。。 (^^; <ちゃんとご飯食べました?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/6/8 12:26:13
> 僕のAdd()は呼び出す度にbordクラスのインスタンスを作成しています。
> なので呼び出す度に違うインスタンスのはずなんですけどね。
問題は bord ではなく、BordStones の方です。

元のコードを簡略化して記述すると、
  Private ReplayStoneBordList As New List(Of Board)()
  Public Sub Add(ByVal bords As Bord)
    Dim bord As New Bord()
    bord.BordStones = bords.BordStones   '★ 
    ReplayStoneBordList.Add( bords )
  End Sub
となりますよね。問題は ★ の右辺です。
左辺の bord は新規インスタンスですが、右辺の bords はどうでしょうか。

Add メソッドを呼び出す際に、引数に渡す Bord オブジェクトが
新規に作られた物であれば、BordStones も新規インスタンスでが、
既存のインスタンスなら、BordStones も既存インスタンスのはずです。

その結果、ReplayStoneBordList(n) が返す Bord 自体は新規インスタンスでも、
ReplayStoneBordList(n).BordStones は既存インスタンスとなる可能性があります。
投稿者 たかくん  (社会人) 投稿日時 2012/6/8 23:25:22
今晩は、あれからオブジェクトの中のプロパティをコピーする事で解決はしましたが
正直、新しいオブジェクトを作る必要がある度に一つ一つコピーしないといけないのかと思うと
他に簡単なメソッドはないものかと思いますね。
もしもプロパティがクラス内に10以上あれば一つ一つコピーしてたら大変です。
今回は4つで済みましたけど・・・
Copy()、Clone()はどうやら新しいオブジェクトではコピーしてくれないようです。
それとズッカさん、心配してくれてありがとうございます。
僕は問題が発生すると解決するまで考えてしまうんです。
通勤の時、休憩の時、風呂、布団の中、自分にストップがかけれないのが欠点ですね。
その内脳がパンクするんです、これがいつものパターン・・・
でも情熱がないとプログラミングなんてできないですよね。
頑張りましょうね。
投稿者 (削除されました)  () 投稿日時 2012/6/9 01:13:59
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2012/6/9 01:15:13
> Copy()、Clone()はどうやら新しいオブジェクトではコピーしてくれないようです。
 「ディープコピー」と「シャローコピー」の違いです。

 標準の Copy はシャローコピーであり、参照情報のみがコピーされます。
 実態の複製が作られるわけではありません。
(VBに限らず、ほとんどのオブジェクト指向言語では参照のみがコピーされます)



 > 正直、新しいオブジェクトを作る必要がある度に一つ一つコピーしないといけないのかと思うと
値型(Integer, Date, Boolean, 自作構造体など)に関しては、そうした作業は不要です。
 問題となるのは、参照型(自作クラス、配列、コレクションなど)の場合ですね。


 > もしもプロパティがクラス内に10以上あれば一つ一つコピーしてたら大変です。
 「シリアライズ」という仕組みを使えば、一括してデータを複製することができます。
http://d.hatena.ne.jp/tekk/20100131/1264913887

あるいは、DataSet などを利用するという手もあります。
DataSet はシリアライザブルですし、データの複製機能もあります。 
投稿者 たか  (社会人) 投稿日時 2012/6/11 23:34:31
魔界の仮面弁士さん、お返事が遅くなりました。
シリアライズ見てみましたよ、ありがとうございます。
きっとどこかに解決方法があると思ってました。
ではまた