Removing reference type members from a generic list clone

J

Joel Merk

I have created a custom class with both value type members and reference type
members. I then have another custom class which inherits from a generic list
of my first class. This custom listneeds to support cloning:
Public Class RefClass
Public tcp As TcpClient
Public name As String
End Class
Public Class RefClassList
Inherits List(Of RefClass)
Implements ICloneable

Public Function Clone() As Object Implements System.ICloneable.Clone
Return MemberwiseClone()
End Function

Public Sub RemoveItem(ByVal item As RefClass)
Dim newList As RefClassList = Me.Clone
newList.Remove(item)
End Sub
End Class

Until now, MemberwiseClone() has served my purposes well. Because RefClass
contains a TcpClient, a deep clone is not desirable, because a Tcp Connection
is not duplicatable (logically). I need to clone because I need to modify the
collection while in a For Each loop; so creating a clone beforehand works for
me. When adding elements to the clone, I have no problems. However, when I
remove an object from the cloned list by referencem using RemoveItem(), the
associated item in the original list is set to Nothing and moved to the end
of the list. ReferenceEquals() applied to the original list and the clone
returns false, and the object has not been deleted (I added it to a separate
class, and it remained unchanged). I would like to know if this is a side
effect of MemberwiseClone, or perhaps Remove, and how I should fix it. I
tried several things, and one option that did not show this effect was:

Public Function Clone() As Object Implements System.ICloneable.Clone
Dim newList As New UserList
newList.AddRange(Me.GetRange(0, Me.Count))
Return newList
End Function

I would like to know if there is a better way to work around this problem,
and I am also curious as to exactly why this happens. I was under the
impression that the member array of a list created through MemberwiseClone()
would not be linked to the original member list, but the references in the
new list would be the same.
Thanks!
 
T

Terry

I think that what is going on is that the only 'member' of the RefClassList
is the list object (that you inherit), hence the newlist, simply points to
the original list, since that is the way that reference types are cloned.
There are other ways (as you have shown) to copy the emebers of the original
list to a new list. The doc's for memberwise clone of thge generic list
object say ..."Creates a shallow copy of the current Object. (inherited from
Object)". No special override mentioned. So cloning a list object is
exactly the same as setting another reference to the original list. My
thoughts anyhow. You should be able to verify this by cloning one of your
objects and checking if the two underlying lists are the same reference.
 
T

Terry

Well, I was sure wrong about that...should of tried it first. Works fine for
me, both adding and removing. Neither operation on one list affects the
other list for me. Not sure what your removeitem is suppose to do, certainly
wont affect the instance that it is invoked on. Don't think you want to
clone another list there.
 
J

Joel Merk

I have a function that sends a message to the TcpClient of each RefClass
using a for loop. I need to use the close, because sometimes I want to omit
one or many instances of RefClass in my list, and rather than using two For
Each loops, I thought it would be more efficient to to create a clone array,
and remove the members to whom I do not wish to send a message. After this
operation, the clone would be discarded. The examples I have provided are
extremely simplified; I am just using them as an example.
As a contrast, there are several other ways of copying the list members that
still observe my problem. For example:

Public Function Clone() As Object Implements System.ICloneable.Clone
Dim newList As New RefClassList
newList.AddRange(Me)
Return newList
End Function

I guess what I'm trying to understand, is if the two lists are indeed
references to the same object, then why, when removing an item from a clone
(Using Remove or RemoveAt), does the original list's item simply get set to
Nothing and moved to the end instead of completely removed. If you want to
get the same effect as me, what I did is created a new list, added several
items with incremental names, created a clone, and removed one of the middle
items using RemoveAt().

Something else I would now like to understand is why or how a clone would
affect a list this way. When Memberwiseclone is applied, is a new
RefClassList created, with an idental reference to the default property?

I guess I will have to experiment with different workarounds and choose the
one that I like best, unless someone has a recommendation?

Thanks for the reply!
 
T

Terry

Hi Joel,
What I was trying to say, is that I can't duplicate your problem. I
created a simple console app and don't seem to have the same trouble you are
talking about. BTW, I had to turn Option strict off to compile your code.
And to make the RemoveItem work, got rid of the newlist - that was also what
I was saying, I don't understand what your removeitem was suppose to do. It
created a brand new clone, removed the item and then the brand new clone is
discarded. What's the purpose? I assume that the code is suppose to remove
the item from itself, so that is what I made it do. The 2 lists seem totally
independent to me. Here is the code I tested it with.

Option Strict Off

Module Module1

Sub Main()
Dim rc As New RefClass
Dim rc2 As New RefClass
Dim rcl1 As New RefClassList
rcl1.Add(rc)
rcl1.Add(rc2)
Dim rcl2 As RefClassList = rcl1.Clone
rcl2.RemoveItem(rc)
Console.WriteLine(rcl1.Count)
Console.WriteLine(rcl2.Count)
Console.ReadLine()
End Sub

End Module
Public Class RefClass
Public tcp As Object
Public name As String
End Class
Public Class RefClassList
Inherits List(Of RefClass)
Implements ICloneable

Public Function Clone() As Object Implements System.ICloneable.Clone
Return MemberwiseClone()
End Function

Public Sub RemoveItem(ByVal item As RefClass)
'Dim newList As RefClassList = Me.Clone
Remove(item)
End Sub
End Class
 
J

Joel Merk

Thanks again for the help, Terry!
I tested your code, and you are indeed experiencing the same problem as I,
but you are not looking it in the right way. The count of the original array
remains the same, but the item that was removed from the clone becomes
nothing and is moved to the end. If you were to use the code:

Sub Main()
Dim rc As New RefClass
Dim rc2 As New RefClass
Dim rcl1 As New RefClassList
rcl1.Add(rc)
rcl1.Add(rc2)
Dim rcl2 As RefClassList = rcl1.Clone
rcl2.RemoveItem(rc)
Console.WriteLine(rcl1(0).ToString)
Console.WriteLine(rcl1(1).ToString)
Console.WriteLine(rcl2(0).ToString)
Console.ReadLine()
End Sub

as your sub main, you would encounter a NullReferenceException at
Console.WriteLine(rcl1(1).ToString)
because rcl1(1) is a reference to Nothing, even though rcl1 still has 2
items. This is the problem I am having. Also, the reason that my RemoveItem
appeared to do nothing is because I felt that whatever else it would need to
do was not important; what I was actually going to do is perform a loop
operation on the new modified (smaller) clone before discarding it, but this
is not actually relevant to the problem that I am having.
Thanks!
 
T

Terry

Ok - Now I understand. Now my first answer is looking better! I am still
in the process of learning all this stuff myself, and this is one of the ways
that I use to learn. I really think the first answer I gave had us going in
the right direction. When you inherit from the list type, you become a list.
I would guess that inside the list type somewhere there is a reference
(type) that points to the actual data (maybe an array, not sure). A shallow
copy will just point to this same data structure. So now, you have 2 list
objects that maintain their own count, but both point to the same underlying
data structure. When you update that structure through one of the list
objects, the other does not know about it! So it is not that removing an
item from the clone causes the original to move the item to the end of the
list, it is the item was removed from both underlying structures, but the
original does not know that the list is one shorter now! The item itself is
not nothing. It is that the list is one shorter! Here is the console app
one more time. Are you an MSDN subscriber? The reason I ask, is that I am
surprised no ms person has jumped in here. If not, maybe I can reask the
question under my name and we can get an expert to explain it ot us.
Any how, here is the code that I think shows a little better what is
happening.
Module Module1
Sub Main()
Dim rc1 As New RefClass
Dim rc2 As New RefClass
Dim rcl1 As New RefClassList
rcl1.Add(rc1)
rcl1.Add(rc2)
rcl1(0).name = "One"
rcl1(1).name = "Two"
Dim rcl2 As RefClassList = DirectCast(rcl1.Clone, RefClassList)
rcl2.Remove(rc1)
Console.WriteLine(rcl1.Count) ' 2
Console.WriteLine(rcl2.Count) ' 1
Console.WriteLine(rcl2(0).name) ' Two - now the first (only)
Console.WriteLine(rcl1(0).name) 'Two!!!!!!!!!!!!!
Console.WriteLine(rc1.name) 'One - it still exists!
Console.WriteLine(rcl1(1).name) 'Null Reference exception!!!!!!
Console.ReadLine()
End Sub
End Module
Public Class RefClass
Public name As String
End Class
Public Class RefClassList
Inherits List(Of RefClass)
Implements ICloneable

Public Function Clone() As Object Implements System.ICloneable.Clone
Return MemberwiseClone()
End Function
End Class
 
T

Terry

Ok, think I have thought of a better(?) more general way to look at this.
Whenever you clone (shallow) a 'wrapper' type class, you end up with 2
wrappers wrapping the same object!
I *think* that this is a correct statement. And I *think* that a List is a
wrapper around something (probably just an array).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top