Avatar of Thomasian
ThomasianFlag for Philippines

asked on 

Copy a collection

How do I create a shallow copy of a collection (SortedList)?
.NET ProgrammingEditors IDEsVisual Basic.NET

Avatar of undefined
Last Comment
Thomasian
Avatar of philipjonathan
philipjonathan
Flag of New Zealand image

You can call the Clone method.
Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

Ups, I am not clear enough. I am referring to the strictly typed SortedList (Of TKey, TValue).
Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

And I meant a deep copy not a shallow copy of the collection.
SOLUTION
Avatar of philipjonathan
philipjonathan
Flag of New Zealand image

Blurred text
THIS SOLUTION IS ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
See Pricing Options
Start Free Trial
Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

It's not of value type. It is a custom class.
SortedList have a Clone() method ready implemented, so you can use:

sorted2 = sorted1.Clone()

in case you need a deeper copy, you can use:
foreach (KeyValuePair kvp in sorted1)
    sorted2.Add(kvp.Key, kvp.Value);

Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Hi Thomasian;

Here is some sample code using the same Employee and EmployeeDeep classes as yesterday showing the Employee does a shallow copy and EmployeeDeep does a deep copy of the custom class. If you look at the diagram of both class you will see that the custom class goes down three levels.

Also in the sample code for deep copy I use a SortedList to hold the EmployeeDeep objects.

Fernando

    Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click
 
 
        Dim theEmployee1 As New EmployeeDeep("John", "Smith")
        Dim theEmployee2 As New EmployeeDeep("Tom", "Jones")
        Dim theEmployee3 As New EmployeeDeep("Joseph", "Rattz")
        Dim theEmployee4 As New EmployeeDeep("Scott", "Duffy")
        theEmployee1.Boss = theEmployee2
        theEmployee2.Boss = theEmployee3
        theEmployee3.Boss = theEmployee4
 
        Dim theEmployee11 As New EmployeeDeep("Bill", "Smith")
        Dim theEmployee21 As New EmployeeDeep("Sam", "Jones")
        Dim theEmployee31 As New EmployeeDeep("Alice", "Rattz")
        Dim theEmployee41 As New EmployeeDeep("Marry", "Duffy")
        theEmployee11.Boss = theEmployee21
        theEmployee21.Boss = theEmployee31
        theEmployee31.Boss = theEmployee41
 
        Dim emplyeeSortedList As New SortedList(Of String, EmployeeDeep)
        emplyeeSortedList.Add("Employee1", theEmployee1)
        emplyeeSortedList.Add("Employee11", theEmployee11)
        emplyeeSortedList.Add("ClonedEmployee100", emplyeeSortedList("Employee1").Clone())
        emplyeeSortedList.Add("ClonedEmployee101", emplyeeSortedList("Employee11").Clone())
 
        MessageBox.Show("The object theEmployee1 is the same object ClonedEmployee : " _
            & (emplyeeSortedList("Employee1") Is emplyeeSortedList("ClonedEmployee100")).ToString & Environment.NewLine _
            & "The object theEmployee.Boss is the same object EmplyeeClone.Boss : " _
            & (emplyeeSortedList("Employee1").Boss Is emplyeeSortedList("ClonedEmployee100").Boss).ToString & Environment.NewLine _
            & "The object theEmployee.Boss.Boss is the same object EmplyeeClone.Boss.Boss : " _
            & (emplyeeSortedList("Employee1").Boss.Boss Is emplyeeSortedList("ClonedEmployee100").Boss.Boss).ToString & Environment.NewLine _
            & "The object theEmployee.Boss.Boss.Boss is the same object EmplyeeClone.Boss.Boss.Boss : " _
            & (emplyeeSortedList("Employee1").Boss.Boss.Boss Is emplyeeSortedList("ClonedEmployee100").Boss.Boss.Boss).ToString)
 
 
    End Sub
 
 
    Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click
 
        Dim theEmployee1 As New Employee("John", "Smith")
        Dim theEmployee2 As New Employee("Tom", "Jones")
        Dim theEmployee3 As New Employee("Joseph", "Rattz")
        Dim theEmployee4 As New Employee("Scott", "Duffy")
        theEmployee1.Boss = theEmployee2
        theEmployee2.Boss = theEmployee3
        theEmployee3.Boss = theEmployee4
 
        Dim ClonedEmployee As Employee = theEmployee1.Clone()
 
        MessageBox.Show("The object theEmployee1 is the same object ClonedEmployee : " _
            & (theEmployee1 Is ClonedEmployee).ToString & Environment.NewLine _
            & "The object theEmployee.Boss is the same object EmplyeeClone.Boss : " _
            & (theEmployee1.Boss Is ClonedEmployee.Boss).ToString & Environment.NewLine _
            & "The object theEmployee.Boss.Boss is the same object EmplyeeClone.Boss.Boss : " _
            & (theEmployee1.Boss.Boss Is ClonedEmployee.Boss.Boss).ToString & Environment.NewLine _
            & "The object theEmployee.Boss.Boss.Boss is the same object EmplyeeClone.Boss.Boss.Boss : " _
            & (theEmployee1.Boss.Boss.Boss Is ClonedEmployee.Boss.Boss.Boss).ToString)
 
 
    End Sub

Open in new window

Drawing1.jpg
Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

FernandoSoto,

I need to create a deep copy of the whole collection which will create a new copy (object) for each object (EmployeeDeep) in the collection.


Actually, philipjonathan's and jaime_olivares's suggestion (adding a deep copy of each object to the new collection) should do the trick. Just want to get another opinion if there is no other way like a "MemberwiseClone" method for a collection.
Probably you can also serialize the object, and then de-serialize it to another object. But never try it myself though ... sorry, can't really advise you on the details.
will be easier to copy if you do it database-style, that is, give every Employee an ID, and point them by ID, not by reference. So, the copy will be simpler.
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Something like this will do it.

Fernando

Dim emplyeeSortedList As New SortedList(Of String, EmployeeDeep)
Dim emplyeeSortedListCopy As New SortedList(Of String, EmployeeDeep)
 
' Create, add and process EmployeeDeep
' .....
 
' Make a deep copy of all EmployeeDeep
For Each empKey As String In emplyeeSortedList.Keys
    emplyeeSortedListCopy.Add(empKey, emplyeeSortedList(empKey).Clone())
Next

Open in new window

Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

philipjonathan,

I'm not familiar with serialization. When I serialize a collection, do I serialize the objects or just the pointer to the objects?

jaime_olivares,

Not sure I understood what you mean. If I only store the employee id in the collection, then I don't get another copy of the other properties.
i.e. If I change the employee name in the new collection, the employee name is also changed in the original collection since they are referenced to the same object.

FernandoSoto,

I also came up with something like that after suggestions from philipjonathan & jaime_olivares, but I used a keyvaluepair instead of the key like in http:#a22033182 .

Which way is better/recommended?
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

With this code, sorted2 = sorted1.Clone(), to use the Clone method you would have to use the non generic version of the SortedList and then you will need to cast the object returned to a EmployeeDeep object and this only gives a shallow copy.

With this code:

Code from "jaime_olivares"
foreach (KeyValuePair kvp in sorted1)
    sorted2.Add(kvp.Key, kvp.Value);

Translate to Visual Basic .Net would be this:

For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListOriginal
    emplyeeSortedListCopy.Add(kvp.Key, kvp.Value.Clone)
Next

Code from my post
For Each empKey As String In emplyeeSortedListOriginal.Keys
    emplyeeSortedListCopy.Add(empKey, emplyeeSortedListOriginal(empKey).Clone())
Next

Note that kvp.Value in the above code will only copy the reference and not create a new copy of EmployeeDeep that is why in the translated version I use, kvp.Value.Clone, of the EmployeeDeep class.

I do not think you will find much of a performance difference between the two. But I would venture to say that jaime_olivares code would have a slightly better  performance.

Fernando
I you want a separated copy of your sorted list, you can do something like:

Initial proposal in VB, as translated by Fernando:
' First stage
For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListOriginal
         emplyeeSortedListCopy.Add(kvp.Key, kvp.Value.Clone)
Next

then, all cloned employees are pointing to old bosses by reference, you have to change this reference to new employee objects:
' Second stage
For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListCopy
        kvp.Value.m_Boss = emplyeeSortedListCopy(kvp.Key)
Next


now all element of the new list point to new cloned bosses. Try to change the name of some boss in the original list and check if any change in new list

Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

@ jaime_olivares;

The EmployeeDeep class implements ICloneable which does a deep copy of the value types and any EmployeeDeep reference type. So all that is needed is this :

For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListOriginal
    emplyeeSortedListCopy.Add(kvp.Key, kvp.Value.Clone)
Next


I don't see how the cloned copy of a employee can automatically point to a cloned boss. Please check again.
 
Thomasian, check this tutorial out. It describes how to serialize a collection whose elements is a composite object (I mean an object that holds reference to another object). However, as I said, I never tried it myself, this is just an idea of mine :)
http://blog.paranoidferret.com/index.php/2007/04/27/csharp-tutorial-serialize-objects-to-a-file/
ASKER CERTIFIED SOLUTION
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Blurred text
THIS SOLUTION IS ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
@fernando,
I have tried to use your previous Clone code (from previous answer), look at my example, you are creating 2 different bosses when 2 employees share the same boss:

Imports System
Imports System.Collections.Generic
 
 
Module Module1
    Public Class EmployeeDeep
        Implements ICloneable
 
        Public m_FirstName As String
        Public m_LastName As String
        Public m_Boss As EmployeeDeep
 
        Public Sub New()
 
        End Sub
 
        Public Sub New(ByVal first As String, ByVal last As String)
            m_FirstName = first
            m_LastName = last
        End Sub
 
        Public Function Clone() As Object Implements System.ICloneable.Clone
            Dim theClone As New EmployeeDeep
            theClone.m_FirstName = Me.m_FirstName
            theClone.m_LastName = Me.m_LastName
 
            ' THIS IS NOT CORRECT, WILL GENERATE DIFFERENT OBJECTS FOR THE SAME BOSS OF 2 EMPLOYEES
            If m_Boss IsNot Nothing Then theClone.m_Boss = Me.m_Boss.Clone()
            ' THIS IS NOT CORRECT, WILL GENERATE A REFERENCE TO THE OLD OBJECT
            ' theClone.m_Boss = Me.m_Boss
 
            Return theClone
        End Function
    End Class
 
 
    Sub Main()
        Dim theEmployee0 As New EmployeeDeep("Jaime", "Olivares")
        Dim theEmployee1 As New EmployeeDeep("John", "Smith")
        Dim theEmployee2 As New EmployeeDeep("Tom", "Jones")
        Dim theEmployee3 As New EmployeeDeep("Joseph", "Rattz")
        Dim theEmployee4 As New EmployeeDeep("Scott", "Duffy")
 
        ' BOTH EMPLOYEE 0 AND 1 MUST SHARE THE SAME BOSS
        theEmployee0.m_Boss = theEmployee2
        theEmployee1.m_Boss = theEmployee2
        theEmployee2.m_Boss = theEmployee3
        theEmployee3.m_Boss = theEmployee4
 
        ' PUT ALL THEM IN A SORTED LIST
        Dim emplyeeSortedListOriginal As New SortedList(Of String, EmployeeDeep)
        emplyeeSortedListOriginal.Add("E0", theEmployee0)
        emplyeeSortedListOriginal.Add("E1", theEmployee1)
        emplyeeSortedListOriginal.Add("E2", theEmployee2)
        emplyeeSortedListOriginal.Add("E3", theEmployee3)
        emplyeeSortedListOriginal.Add("E4", theEmployee4)
 
        ' CLONE THE SORTED LIST
        Dim emplyeeSortedListCopy As New SortedList(Of String, EmployeeDeep)
        For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListOriginal
            emplyeeSortedListCopy.Add(kvp.Key, kvp.Value.Clone)
        Next
 
        ' CHANGE THE E0's BOSS NAME
        emplyeeSortedListCopy("E0").m_Boss.m_FirstName = "TEST"
 
        ' BOTH E0's AND E1's BOSS SHOULD HAVE THE SAME NAME 
        Console.WriteLine("E0's Boss name: {0}", emplyeeSortedListCopy("E0").m_Boss.m_FirstName)
        Console.WriteLine("E1's Boss name: {0}", emplyeeSortedListCopy("E1").m_Boss.m_FirstName)
        Console.ReadKey()
    End Sub
 
End Module

Open in new window

SOLUTION
THIS SOLUTION IS ONLY AVAILABLE TO MEMBERS.
View this solution by signing up for a free trial.
Members can start a 7-Day free trial and enjoy unlimited access to the platform.
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

@ jaime_olivares;

From Microsoft documentation:
Clone can be implemented either as a deep copy or  a shallow copy. In a deep copy, all objects are duplicated; whereas, in a  shallow copy, only the top-level objects are duplicated and the lower levels  contain references.


your comment doesn't contribute to clarification.
The author intention is to clone the List properly. Thant menas that both employees should point to the same object if both share the same boss.
 
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Hi jaime_olivares;

Some of the comments made by Thomasian in this thread:
  1. "And I meant a deep copy not a shallow copy of the collection."
  2. "It's not of value type. It is a custom class."
  3. "I need to create a deep copy of the whole collection which will createa new copy (object) for each object (EmployeeDeep) in the collection."
BTW; Thomasian custom class is not an employee class i don't think, it is something I put together from the last question and it carried over to this one.

Fernando

>>I need to create a deep copy of the whole collection which will createa new copy (object) for each object (EmployeeDeep) in the collection.
you are not creating a new copy for each object,
you are creating a new copy for each object AND extra objects for bosses
 
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

Hi jaime_olivares;

In my previous post, http:#a22045763 , I showed that the copy of the Boss EmployeeDeep object were not the same by doing a ReferenceEquals on the original Boss and the copy. I have created the same program in C# so that I can display the address of each of the Boss objects to see if they occupy the same memory location and as the ReferenceEquals stated they do not as shown by my results below.

EmployeeOriginal    Memory Location
Boss                           0x012ca170
Boss.Boss                0x012ca184
Boss.Boss.Boss     0x012ca198

EmployeeCopy         Memory Location
Boss                           0x012d6d7c
Boss.Boss                0x012d6d90
Boss.Boss.Boss     0x012d6da4

Now I may be totally misunderstanding you or have done something terribly wrong but I can not see it, sorry.

Fernando


>>have done something terribly wrong
Yes, you have, because you are evaluating just if cloned objects are different than original.
But there are more things you have to evaluate, but I think you are not reading carefully my posts, just you are trying to justify yourself (it is unnecessary)
Besides the fact you clone new objects, you are creating 8 objects instead of just 4:
4 objects cloned from the original
4 objects cloned from the original's bosses
you have never checked this, I did. Original collection has 4 objects, that point to another object in the same collection (the boss). So, the goal is to create a new collection with 4 cloned employees, that point to another object in the same collection.
You are not doing this, you are creating extra object for the bosses. This is more evindent when, as in my example, you make point 2 employees to the same boss. Try to evalate if both bosses have the same reference.
well, I think a graphic will explain better what I am trying to expose:
Cloning-comparison.jpg
Avatar of Fernando Soto
Fernando Soto
Flag of United States of America image

I see that we will not come to a meeting of the minds here. So lets agree to allow  Thomasian so see what fits his needs and leave it at that.

Thank you and have a great day;
Fernando
 
Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

This question is a related question based on a solution provided by Fernando.

Jaime does have a point that it does not make much sense to create a new object for the boss since the boss is also an employee. But I have never mentioned having a boss member in my class. That was only an example used by Fernando to show me how to create a deep copy of an object.

Actually, the class (for now) only have value type members, so a shallow copy will be enough. What I need is a deep copy of the collection because a shallow copy of the collection will only create a new instance of the collection, and not the objects in it. The question was already answered when looping through the list was suggested, I just want to know if there is any other way. The additional informations and ideas you provided will be very helpful in the future.

Jaime,

I'm sure you had tested your code, but I couldn't make it work without some modifications. And I think, we can replace the code in the Clone function to "Return Me.MemberwiseClone".


Imports System
Imports System.Collections.Generic
 
 
Module Module1
    Public Class EmployeeDeep
        Implements ICloneable
 
        Public m_FirstName As String
        Public m_LastName As String
        Public m_Boss As EmployeeDeep
 
        Public Sub New()
 
        End Sub
 
        Public Sub New(ByVal first As String, ByVal last As String)
            m_FirstName = first
            m_LastName = last
        End Sub
 
        Public Function Clone() As Object Implements System.ICloneable.Clone
            Dim theClone As New EmployeeDeep
            theClone.m_FirstName = Me.m_FirstName
            theClone.m_LastName = Me.m_LastName
 
            ' DO NOT CLONE THE BOSS <----------- MODIFIED
 
            ' THIS IS NOT CORRECT, WILL GENERATE DIFFERENT OBJECTS FOR THE SAME BOSS OF 2 EMPLOYEES
            'If m_Boss IsNot Nothing Then theClone.m_Boss = Me.m_Boss.Clone()
            ' THIS IS NOT CORRECT, WILL GENERATE A REFERENCE TO THE OLD OBJECT
            theClone.m_Boss = Me.m_Boss
 
            Return theClone
        End Function
    End Class
 
 
    Sub Main()
        Dim theEmployee0 As New EmployeeDeep("Jaime", "Olivares")
        Dim theEmployee1 As New EmployeeDeep("John", "Smith")
        Dim theEmployee2 As New EmployeeDeep("Tom", "Jones")
        Dim theEmployee3 As New EmployeeDeep("Joseph", "Rattz")
        Dim theEmployee4 As New EmployeeDeep("Scott", "Duffy")
 
        ' BOTH EMPLOYEE 0 AND 1 MUST SHARE THE SAME BOSS
        theEmployee0.m_Boss = theEmployee2
        theEmployee1.m_Boss = theEmployee2
        theEmployee2.m_Boss = theEmployee3
        theEmployee3.m_Boss = theEmployee4
 
        ' PUT ALL THEM IN A SORTED LIST
        Dim emplyeeSortedListOriginal As New SortedList(Of String, EmployeeDeep)
        emplyeeSortedListOriginal.Add("E0", theEmployee0)
        emplyeeSortedListOriginal.Add("E1", theEmployee1)
        emplyeeSortedListOriginal.Add("E2", theEmployee2)
        emplyeeSortedListOriginal.Add("E3", theEmployee3)
        emplyeeSortedListOriginal.Add("E4", theEmployee4)
 
        ' CLONE THE SORTED LIST
        Dim emplyeeSortedListCopy As New SortedList(Of String, EmployeeDeep)
        For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListOriginal
            emplyeeSortedListCopy.Add(kvp.Key, DirectCast(kvp.Value.Clone, EmployeeDeep))
        Next
 
        ' RE-SCAN TO SET BOSS PROPERLY <---------------- ADDED THIS BLOCK
        Dim index As Integer
        For Each kvp As KeyValuePair(Of String, EmployeeDeep) In emplyeeSortedListCopy
            index = emplyeeSortedListOriginal.IndexOfValue(emplyeeSortedListCopy(kvp.Key).m_Boss)
            If index <> -1 Then
                kvp.Value.m_Boss = emplyeeSortedListCopy.Values(index)
            End If
        Next
 
        ' CHANGE THE E0's BOSS NAME
        emplyeeSortedListCopy("E0").m_Boss.m_FirstName = "TEST"
 
        ' BOTH E0's AND E1's BOSS SHOULD HAVE THE SAME NAME 
        Console.WriteLine("E0's Boss name: {0}", emplyeeSortedListCopy("E0").m_Boss.m_FirstName)
        Console.WriteLine("E1's Boss name: {0}", emplyeeSortedListCopy("E1").m_Boss.m_FirstName)
        Console.ReadKey()
    End Sub
 
End Module

Open in new window

>>I'm sure you had tested your code,
Yes, I did with care, because I was trying to demonstrate my point.
>>but I couldn't make it work without some modifications.
Maybe because your class is not exactly as my sample class
>> And I think, we can replace the code in the Clone function to "Return Me.MemberwiseClone".
If it is pure value-member class, yes.


Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

I checked again and found that I just needed 2 modifications:

'Not all employees have a boss
If index <> -1 Then
    kvp.Value.m_Boss = emplyeeSortedListCopy.Values(index)
End If

'I had Option Strict On
emplyeeSortedListCopy.Add(kvp.Key, DirectCast(kvp.Value.Clone, EmployeeDeep))


>>If it is pure value-member class, yes.
It should still work even if it contains a non value-member class (what is it called?). It will be overwritten in the loop anyway, but we won't have to manually copy every value-members in the clone function.
>>Not all employees have a boss
Yes, sorry, I didn't evaluated this case in my code
>>I had Option Strict On
It is a good decision to avoid involuntary errors
Avatar of Thomasian
Thomasian
Flag of Philippines image

ASKER

I split the points:
  200 - Jaime
  200 - Fernando
  100 - Philip

Jaime's and Fernando's suggestions both work in different situations. Though Philip's suggestion would have worked for me, the factors they have considered will greatly help me as the project expands, so I gave them more points.

Hope this is ok with everyone.

Thanks for the help.
.NET Programming
.NET Programming

The .NET Framework is not specific to any one programming language; rather, it includes a library of functions that allows developers to rapidly build applications. Several supported languages include C#, VB.NET, C++ or ASP.NET.

137K
Questions
--
Followers
--
Top Experts
Get a personalized solution from industry experts
Ask the experts
Read over 600 more reviews

TRUSTED BY

IBM logoIntel logoMicrosoft logoUbisoft logoSAP logo
Qualcomm logoCitrix Systems logoWorkday logoErnst & Young logo
High performer badgeUsers love us badge
LinkedIn logoFacebook logoX logoInstagram logoTikTok logoYouTube logo