DOBON.NET DOBON.NETプログラミング掲示板過去ログ

独自コレクションの検索、削除が失敗する

環境/言語:[OS : Windows XP Professional / 言語 : Visual Basic .NET / .NET Framework : 2.0]
分類:[.NET]

【解決したい問題】
お世話になっております。

独自コレクションを作成したのですが追加、挿入、RemoveAt による削除処理は、うまくいくのですが、含まれているか、検索、削除処理がうまくいきません。
よろしくお願い致します。

【解決するために何をしたか】

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim m_MyDataCollection As New MyDataCollection

m_MyDataCollection.Add(New MyData(0, "a"))
m_MyDataCollection.Add(New MyData(1, "b"))
m_MyDataCollection.Add(New MyData(2, "c"))
m_MyDataCollection.Add(New MyData(3, "d"))
m_MyDataCollection.Add(New MyData(4, "e"))
m_MyDataCollection.Add(New MyData(5, "f"))

Dim i As Integer
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("追加処理 " & m_MyDataCollection.Count)

m_MyDataCollection.RemoveAt(2)
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("RemoveAt による削除処理 " & m_MyDataCollection.Count)

m_MyDataCollection.Insert(2, New MyData(2, "C"))
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("挿入処理 " & m_MyDataCollection.Count)


'以下の含まれているか、検索、削除処理がうまくいきません。

'True のはずが False を返す
If m_MyDataCollection.Contains(New MyData(3, "d")) = True Then
System.Diagnostics.Debug.Print("含まれている")
Else
System.Diagnostics.Debug.Print("含まれているいません")
End If

'3のはずが -1 を返す
Dim n As Integer = m_MyDataCollection.IndexOf(New MyData(3, "d"))
System.Diagnostics.Debug.Print("IndexOf による検索処理 " & n)

'指定された項目は、指定されたコレクションに見つからなかったため、削除できませんになる。
m_MyDataCollection.Remove(New MyData(3, "d"))
System.Diagnostics.Debug.Print("Remove による削除処理 " & n)

End Sub


Public Class MyData
Private m_Position As Integer
Private m_Name As String
Public Sub New()
m_Position = -1
m_Name = ""
End Sub 'New
Public Sub New(ByVal _Position As Integer, ByVal _Name As String)
m_Position = _Position
m_Name = _Name
End Sub
Public Property Position() As Integer
Get
Return m_Position
End Get
Set(ByVal Value As Integer)
m_Position = Value
End Set
End Property
Public Property Name() As String
Get
Return m_Name
End Get
Set(ByVal Value As String)
m_Name = Value
End Set
End Property
End Class

Public Class MyDataCollection
Inherits CollectionBase
Default Public Property Item(ByVal index As Integer) As MyData
Get
Return CType(List(index), MyData)
End Get
Set(ByVal value As MyData)
List(index) = value
End Set
End Property
Public Function Add(ByVal value As MyData) As Integer
Return List.Add(value)
End Function 'Add
Public Function IndexOf(ByVal value As MyData) As Integer
Return List.IndexOf(value)
End Function 'IndexOf
Public Sub Insert(ByVal index As Integer, ByVal value As MyData)
List.Insert(index, value)
End Sub 'Insert
Public Sub Remove(ByVal value As MyData)
List.Remove(value)
End Sub 'Remove
Public Function Contains(ByVal value As MyData) As Boolean
Return List.Contains(value)
End Function 'Contains
Protected Overrides Sub OnValidate(ByVal value As Object)
If Not GetType(MyData).IsAssignableFrom(value.GetType()) Then
Throw New ArgumentException("value must be of type MyData.", "value")
End If
End Sub 'OnValidate


End Class 'MyDataCollection
■No21714に返信(YZRさんの記事)


New MyData(3, "d")ってNewしてたら意味なくない?
値型と参照型で検索して参照型について理解をしてください。

Dim x As New MyData(0, "A")
Dim y As New MyData(0, "A")

MyDataという双子を想像してください。
それぞれの名札に「0, "A"」と書いてあります。
参照型の場合、見た目は関係ありません。
双子はそれぞれが人間で別人ですので、二人を比較するとNot Equalです。
#xとyの比較

最初にデータを作っているところでNewしてAddしています。
これは無いものを新たに作って追加する処理ですので正しいです。
ところが検索と削除でNewした「新たに作ったもの」を探したり削除したりしようとしています。
つまり上記で言うと、x が入っているコレクションから y を探したり削除しようとしているのです。

Addの引数でNewせずにNewしたものを変数に格納してその変数をAddして
検索や削除でその変数を指定してみてください。
やじゅ 様、まどか様 早々の返信ありがとうございます。

New MyData(3, "d")ってNewしてたら意味なくない?

このホームページの自作クラスのコレクションや配列でSortやBinarySearchを行うを参考にしているConsole.WriteLine(al.BinarySearch(New TestClass("おはよう", 100)))で検索していたのでOKかと思いました。

下記のように変更したら含まれているか、検索、削除処理は、出来るようですが
最初の追加処理がおかしくなりました。

Addの引数でNewせずにNewしたものを変数に格納してその変数をAddして
検索や削除でその変数を指定してみてください。というのは、これでよいのでしょうか?お手数ですがよろしくお願いします。

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim m_MyDataCollection As New MyDataCollection()
Dim m_MyData As New MyData

m_MyData.Position = 0
m_MyData.Name = "a"
m_MyDataCollection.Add(m_MyData)
m_MyData.Position = 1
m_MyData.Name = "b"
m_MyDataCollection.Add(m_MyData)
m_MyData.Position = 2
m_MyData.Name = "c"
m_MyDataCollection.Add(m_MyData)
m_MyData.Position = 3
m_MyData.Name = "d"
m_MyDataCollection.Add(m_MyData)
m_MyData.Position = 4
m_MyData.Name = "e"
m_MyDataCollection.Add(m_MyData)
m_MyData.Position = 5
m_MyData.Name = "f"
m_MyDataCollection.Add(m_MyData)

Dim i As Integer
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("追加処理 " & m_MyDataCollection.Count)

m_MyDataCollection.RemoveAt(2)
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("RemoveAt による削除処理 " & m_MyDataCollection.Count)

m_MyDataCollection.Insert(2, New MyData(2, "C"))
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("挿入処理 " & m_MyDataCollection.Count)



m_MyData.Position = 5
m_MyData.Name = "f"

If m_MyDataCollection.Contains(m_MyData) = True Then
System.Diagnostics.Debug.Print("含まれている")
Else
System.Diagnostics.Debug.Print("含まれているいません")
End If

Dim n As Integer = m_MyDataCollection.IndexOf(m_MyData)
System.Diagnostics.Debug.Print("IndexOf による検索処理 " & n)

m_MyDataCollection.Remove(m_MyData)
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("削除処理 " & m_MyDataCollection.Count)
End Sub
重ねて言いますが、参照型と値型との違いを勉強して下さい。

> Dim m_MyData As New MyData
> m_MyData.Position = 0
> m_MyData.Name = "a"
> m_MyDataCollection.Add(m_MyData)
> m_MyData.Position = 1
> m_MyData.Name = "b"
> m_MyDataCollection.Add(m_MyData)

参照型の場合、Addは参照を追加することになります。
(この2つのAddは同じものを追加しています)
同じものを書き換えているのでコレクションの中身も同時に書き換わります。

値型の場合、Addでコピーを追加することになります。
追加した後に書き換えても、コレクションの中身は書き換わりません。
2008/03/27(Thu) 07:36:56 編集(投稿者)

値型(構造体)と参照型(クラス)の違いは認識しておかないと後々問題になりますので押さえておいて下さい。
-----
最初のコードでうまくいかない理由はヘルプを辿ると分かります。
(コレクションがList(Of T)かは分かりませんが、他のものも大概同じ実装かもしれません。別途確認して下さい)

List(Of T).ContainsやRemoveのヘルプを見て下さい。
http://msdn2.microsoft.com/ja-jp/library/bhkz42b3.aspx

比較にはEqualityComparer(Of T).Defaultを使用するとの記載があります。
続いて、EqualityComparer(Of T)を見て下さい。
http://msdn2.microsoft.com/ja-jp/library/ms132123.aspx

Defaultプロパティで返されるインスタンスの動作について説明されています。
参照型(クラス)でContainsやRemoveを期待結果通りに動かすためには、IEquatable(Of T)を実装するか、EqualsやGetHashCodeを適切にオーバーライドしなければなりません。
既定のobject.Equalsは参照同士の比較であるため、同じ参照でなければfalseを返します。別々にnewしたものは必ずfalseを返すので最初のような結果になります。

※値型(構造体)はEqualsやGetHashCodeがある程度、期待結果通りに動いてくれるかもしれませんが、メンバーに参照型が含まれるとアウトです。

※元となった記事はArrayListであり、BinarySearchやSortはIComparableを使用するとありますので、期待結果通りに動作します。
ロッカーを想像してください。 ※ただし、ロッカーの存在自体は重要ではありません。メモリの番地だと思ってください。

新しくりんごを作ります。
Dim x As New Apple
仮にロッカーの1番にできたとします。
x には「1番」と書かれた紙切れが入ります。りんごが入るわけではありません。
りんごそのものはロッカーの1番に入っています。
この x が参照型と呼ばれる変数です。

もうひとつ別の新しいりんごを作ります。
Dim y As New Apple
仮にロッカーの2番にできたとします。
y には「2番」と書かれた紙切れが入ります。りんごはロッカーの2番に入っています。

ここで、別の変数を作ります。
Dim x2 As Apple
そして x を代入します。
x2 = x

x2 には x の内容のコピーが入ります。
つまり、「1番」と書かれた紙切れがもう一枚できてx2に入ります。
重要なのは1番に入っているりんごはそのままで新しく作られるわけではありません。

コレクションは別の入れ物です。
Add(x)、Add(New Apple)は上記でいう紙切れを追加することになります。

Add(y)
Add(x)
とすると
コレクションの1番目に「2番と書かれた紙切れ」が、2番目に「1番と書かれた紙切れ」が追加されます。
同様に削除は紙切れを削除することになり、りんごが削除されるわけではありません。

もともと、Remove(New Apple)としていました。
これは新しく作ったものを削除しようとしています。
当然New Appleを指す紙切れはコレクションにはいないので削除できません。

次を比較してみてください。

> Dim m_MyData As New MyData
> m_MyData.Position = 0
> m_MyData.Name = "a"
> m_MyDataCollection.Add(m_MyData)
> m_MyData.Position = 1
> m_MyData.Name = "b"
> m_MyDataCollection.Add(m_MyData)



> Dim m_MyData As New MyData
> m_MyData.Position = 0
> m_MyData.Name = "a"
> m_MyDataCollection.Add(m_MyData)
>
> Dim m_MyData2 As New MyData ※
> m_MyData2.Position = 1
> m_MyData2.Name = "b"
> m_MyDataCollection.Add(m_MyData2)

※は
> m_MyData = New MyData
> m_MyData.Position = 1
> m_MyData.Name = "b"
> m_MyDataCollection.Add(m_MyData)
でもかまいません。
紙切れが上書きされるだけです。

#あぁ、説明しにくい。
Azulean 様、まどか様 お手数かけてすいません。

以下のような考えかたでよろしいのでしょうか?

'1.みかんやリンゴを入れるための袋を準備する。(これで何も入っていないが1つの袋は完成いている)
Dim m_MyDataCollection As New MyDataCollection()

'2.みかんやリンゴを準備して袋に入れる。
Dim m_MyData0 As New MyData
m_MyData0.Position = 0
m_MyData0.Name = "みかん"
m_MyDataCollection.Add(m_MyData0)

Dim m_MyData1 As New MyData
m_MyData1.Position = 1
m_MyData1.Name = "リンゴ"
m_MyDataCollection.Add(m_MyData1)

Dim m_MyData2 As New MyData
m_MyData2.Position = 2
m_MyData2.Name = "なし"
m_MyDataCollection.Add(m_MyData2)

Dim m_MyData3 As New MyData
m_MyData3.Position = 3
m_MyData3.Name = "バナナ"
m_MyDataCollection.Add(m_MyData3)

'3.これで みかん、リンゴ、なし、バナナが入った袋になった。
Dim i As Integer
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("追加処理 " & m_MyDataCollection.Count)
> 以下のような考えかたでよろしいのでしょうか?

考え方はYZRさんがどのように理解したかなのでコードを見ただけではわかりませんが。。。

m_MyDataCollection.Remove(m_MyData0)は存在するので削除できますが

Dim m_MyData9 As New MyData
m_MyData9.Position = 0
m_MyData9.Name = "みかん"
m_MyDataCollection.Remove(m_MyData9)は存在しないので削除できません。
#削除できないのではなくそもそも無い。

つまりMyDataの実物を特定する必要があると。
同じ鉛筆が入っている同じ形の筆箱が二つあり、どの筆箱かを特定しなければいけないということ。

理解した後の話ですが、このようにインスタンスを削除したり検索したりするにはその参照を覚えておかなければなりません。
あとたとえば、名前がみかんのものを削除するといった場合は、コレクションの外の概念ですから自分で作る必要があります。
#コレクションの中身をひとつひとつ見ながら見つかったらそれを削除する

For Each data As MyData In m_MyDataCollection
If data.Name = "みかん" Then
m_MyDataCollection.Remove(data)
Exit For
End If
Next
または
For index As Integer = 0 To m_MyDataCollection.Count - 1
If m_MyDataCollection(index).Name = "みかん" Then
m_MyDataCollection.RemoveAt(index)
Exit For
End If
Next
Azulean 様、まどか様 何度もご丁寧な返信ありがとうございます。

Azulean様
値型(構造体)と参照型(クラス)の違いは認識しておかないと後々問題になりますので押さえておいて下さい。
とは、
http://www.atmarkit.co.jp/fdotnet/vb6tonet2/vbnet2_02/vbnet2_02_05.html#vbnet0215
の値型と参照型の振る舞いの相違のことですよね。じっくり考えてみます。

まどか様
とりあえず下記の様にしたらなんとか出来ましたが(いまいち理解できてないけど)
インスタンス、参照型(クラス)のキーワードで調べてみます。

'1.みかんやリンゴを入れるための袋を準備する。(何も入っていないが1つの袋は完成いている)
Dim m_MyDataCollection As New MyDataCollection()

'2.みかんやリンゴを準備して袋に入れる。(それぞれ違うものなので4個(m_MyData0〜m_MyData4)必要)
Dim m_MyData0 As New MyData
m_MyData0.Position = 0
m_MyData0.Name = "みかん"
m_MyDataCollection.Add(m_MyData0)

Dim m_MyData1 As New MyData
m_MyData1.Position = 1
m_MyData1.Name = "リンゴ"
m_MyDataCollection.Add(m_MyData1)

Dim m_MyData2 As New MyData
m_MyData2.Position = 2
m_MyData2.Name = "なし"
m_MyDataCollection.Add(m_MyData2)

Dim m_MyData3 As New MyData
m_MyData3.Position = 3
m_MyData3.Name = "バナナ"
m_MyDataCollection.Add(m_MyData3)

'3.これで みかん、リンゴ、なし、バナナが入った袋になった。
Dim i As Integer
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("追加処理完了 " & m_MyDataCollection.Count)

'4.なしを削除
m_MyDataCollection.RemoveAt(2)
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("2番目のなしを削除" & m_MyDataCollection.Count)

'5.なしを挿入(これ以下の操作は作成したインスタンス変数で操作する)
m_MyDataCollection.Insert(2, m_MyData2)
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("なしを挿入 " & m_MyDataCollection.Count)

'6.なしは、含まれているか
If m_MyDataCollection.Contains(m_MyData2) = True Then
System.Diagnostics.Debug.Print("なしは、含まれている")
Else
System.Diagnostics.Debug.Print("なしは、含まれているいません")
End If

'7.なしは、何番目
Dim n As Integer = m_MyDataCollection.IndexOf(m_MyData2)
System.Diagnostics.Debug.Print("なしは、 " & n & "番目です")

'8.なしを削除
m_MyDataCollection.Remove(m_MyData2)
For i = 0 To m_MyDataCollection.Count - 1
System.Diagnostics.Debug.Print(m_MyDataCollection(i).Name & "," & m_MyDataCollection(i).Position)
Next i
System.Diagnostics.Debug.Print("なしを削除 " & m_MyDataCollection.Count)

DOBON.NET | プログラミング道 | プログラミング掲示板