Clone a List(Of Class) - vb.net

I've done some reading and cant seem to wrap my head around what the best approach would be to clone a List(of class) in my VB2010 project. I have three classes that are related like so
Public Class City
'here are many fields of type string and integer
Public Roads As New List(Of Road)
End Class
Public Class Road
'here are many fields of type string and integer
Public Hazards As New List(Of Hazard)
End Class
Public Class Hazard
Implements ICloneable
'here are many fields of type string and integer and double
Public Function Clone() As Object Implements System.ICloneable.Clone
Return Me.MemberwiseClone
End Function
End Class
So lets say I have a city I'm working on, there are cases where I want to create, as a base one road and its hazards, then add another road but using the prior roads hazards as a starting point and then tweak the fields.
Dim rd As New Road
'add road fields
dim hz1 as New Hazard
'add hazard fields
dim hz2 as New Hazard
'add hazard fields
'add the hazard objects to the road
rd.Hazards.Add(hz1)
rd.Hazards.Add(hz2)
'add the road to the city
myCity.Roads.Add(rd)
'here I want to start a new road based on the old road
Dim rdNew As New Road
'copy or clone the hazards from old road
rdNew.Hazards = rd.Hazards '<============
'over-write some of the hazard fields
rdNew.Hazards(0).Description = "temp"
So I know that copying a class will copy the pointer and not the contents. I used the ICloneable interface in the hazard class but cant say I'm using it right. The Hazards variable is a list of Hazard class. How would i go about cloning that class?

Implementing IClonable doesn't mean that it replaces the regular assignment, it will still just copy the reference. And you aren't even copying the items, you are copying the list, which means that you still only have one list, but two references to it.
To use the Clone method you have to call it for each item in the list:
rdNew.Hazards = rd.Hazards.Select(Function(x) x.Clone()).Cast(Of Hazard).ToList()

Imports System.IO
Imports System.Xml.Serialization
Public Function CopyList(Of T)(oldList As List(Of T)) As List(Of T)
'Serialize
Dim xmlString As String = ""
Dim string_writer As New StringWriter
Dim xml_serializer As New XmlSerializer(GetType(List(Of T)))
xml_serializer.Serialize(string_writer, oldList)
xmlString = string_writer.ToString()
'Deserialize
Dim string_reader As New StringReader(xmlString)
Dim newList As List(Of T)
newList = DirectCast(xml_serializer.Deserialize(string_reader), List(Of T))
string_reader.Close()
Return newList
End Function

I know this is old.
rdNew.Hazards = rd.Hazards.ToList()
Even though it's already a list, ToList will create a new list based on it.
With VB 2019, this will create a shallow copy, but that's useful in some circumstances. That means that the list is new, but the elements of both rd.Hazards and rdNew.Hazards point to the same thing.
If you edit any particular hazard, the changes will be seen in both.
If you add a hazard to one list, the other list will not have it.
If you delete a hazard from one list, the other list will still have it.
If Hazard were a primitive type (like a string or integer), then editing items would not be reflected in the other list.

Related

Get data from a collection

I want to make a collection to have data available
Example:
Dim b As New Collection
colb = New Collection
b.Add("system", "1001", "SYSTEM")
b.Add("network", "1002", "NETWORKA")
b.Add("networksecond", "1010", "NETWORKB")
colb.Add(b, "list")
im looking for a function to get data from this collection:
I want to, based on the ID (Second number) get the first and third value
So if I search for 1010, I need to have the value Network and NETWORKA
VB6 called, they want their Collection back.
No, seriously, please consider using a Dictionary instead of the old, legacy Collection class. Behold the beauty of generics and strong typing:
Dim dic As New Dictionary(Of Integer, Tuple(Of String, String))
dic.Add(1001, Tuple.Create("system", "SYSTEM"))
dic.Add(1002, Tuple.Create("network", "NETWORKA"))
dic.Add(1010, Tuple.Create("networksecond", "NETWORKB"))
' Search
Dim t As Tuple(Of String, String) = Nothing
If dic.TryGetValue(1002, t) Then
Console.WriteLine(t.Item1) ' prints "network"
Console.WriteLine(t.Item2) ' prints "NETWORKA"
End If
As soon as you have more than two values, I suggest that you use a specialized class instead of a Tuple to increase readability.
Also, you can simply use List(Of T). In most cases this is enough. Dictionary is good for fast search out long list by a single key.
'declare model
Public Class NetworkModel
Public Property Id As Integer
Public Property Name1 As String
Public Property Name2 As String
End Class
' load list of models
Private _modelList As New List(Of NetworkModel)()
.......
' search using LINQ
Dim model As NetworkModel = _modelList.FirstOrDefault(Function(m) m.Id = 1001)
If model IsNot Nothing Then . . . . .

DirectCast One derived Collection into base Collection

I'm working with old VB 6.0 code that in now in VB.NET. So it is using some obsolete collection types. I'm trying to upgrade these as much as possible without breaking stuff.
Say I have a collection of Books HashSet(Of Book) and I have a collection of Premium Books HashSet(Of PremiumBook).
PremiumBook is derived from Book. The only difference is that I override the EQUALS and HASHCODE methods. Everything else is the same.
Since PremiumBook is a Book I can do:
Dim anyBook as Book
Dim goldBook as PremiumBook = New PremiumBook()
anyBook = goldBook
So why can't I do
DirectCast(HashSet(Of PremiumBook), HashSet(Of Book))
The error that I'm getting is:
"Value of type System.Collections.Generic.HashSet(Of SameNamespace.Different.frmBookManager.PremiumBook)' cannot be converted to 'System.Collections.Generic.HashSet(Of SameNamespace.Something.Book)'.
Is it because the namespaces are different? That doesn't make any sense to me.
I feel as if i have a collection of objects, any derived type should be able to fit in that collection.
Thanks!
You can not cast two different object type collections even if they are inherited, but you can cast the individual members to the inherited class Like This example:
Option Strict On
Option Explicit On
Module Module1
Sub Main()
Dim Booklist As List(Of Book) = New List(Of Book)
Booklist.Add(New PremiumBook())
Booklist.Add(New PremiumBook())
Booklist.Add(New PremiumBook())
Booklist.Add(New Book())
For Each bk As Book In Booklist
If bk.GetType() Is GetType(PremiumBook) Then 'If your collection contains multiple types, if not this check can be omitted
Dim premiumBk As PremiumBook = DirectCast(bk, PremiumBook)
End If
Next
Dim premiumBk2 As PremiumBook = DirectCast(Booklist(2), PremiumBook)
End Sub
End Module

Declare New without using With or From

I would like to know if its possible to write multiple lines, the equivalent of this line bellow, but instead of using With and From use multiple lines to declare data.
Dim Price = New PriceStruc() With { _
.Bids = New List(Of Prices)() From {New Prices() With {.Price = 1101, .Size = 1}},
.Offers = New List(Of Prices)() From {New Prices() With {.Price = 1102, .Size = 1}}}
You can add parameters to the constructor to set properties when you create an instance. This is especially helpful for an object which should only exist when this or that property is known. Another use in designer serialization, among others.
Warning: taking it as far as requested doesnt make the code any easier to read
I dont know exactly what these are, so I made up my own and fixed some of the nomenclature (it looks like Prices has data for one item, yet it is plural).
Friend Class Price
Public Property Value As Integer
Public Property Size As Integer
Public Sub New()
End Sub
Public Sub New(v As Integer, s As Integer)
Value = v
Size = s
End Sub
End Class
The simple constructor (no params) is there because many serializers require one. Depending on the serializer, you can it set it as Friend to force your code to use the overload and specify Value and Size when you create an new Price object. This is useful when an object has no right being created without certain key information. (You can still use it in For Each loops because you do not need a new object for that).
In this case, the Value (named to avoid Price.Price) and Size might be required elements, but the constructor overload is mainly a convenience. To use it:
Dim p As New Price(1101, 1)
Now, those objects can be instanced without With. The holder looks like this:
Friend Class PriceItem
Public Property Bids As List(Of Price)
Public Property Offers As List(Of Price)
Public Sub New()
End Sub
Public Sub New(b As List(Of Price), o As List(Of Price))
Bids = b
Offers = o
End Sub
End Class
There is likely more to it, like a Name indicating what the Bids and Offers are for, but the idea is the same: pass the lists in the constructor.
Now you can initialize the Price objects and PriceItem using their constructors:
Dim Prices = New PriceItem(New List(Of Price)(New Price() {New Price(1101, 1),
New Price(1102, 1)}),
New List(Of Price)(New Price() {New Price(1106, 7),
New Price(1104, 1)}))
No With
No From
1101 and 1102 are the Bid items, 1106 and 1104 are the offer items.
As I said, it doesnt make it any easier to read, debug or code. It perhaps makes sense for a Price item but passing lists to the PriceItem ctor seems a bit much. Rather than trying to squeeze everything into the initialization, this seems to strike the best balance between readability and conciseness:
Dim PItem As New PriceItem
PItem.Bids = New List(Of Price)(New Price() {New Price(1101, 1), New Price(1102, 1)})
PItem.Offers = New List(Of Price)(New Price() {New Price(1106, 7), New Price(1104, 1)})
No With
No From

Generic Lists copying references rather than creating a copiedList

I was developing a small function when trying to run an enumerator across a list and then carry out some action. (Below is an idea of what I was trying to do.
When trying to remove I got a "Collection cannot be modified" which after I had actually woken up I realised that tempList must have just been assigned myLists reference rather than a copy of myLists. After that I tried to find a way to say
tempList = myList.copy
However nothing seems to exist?? I ended up writing a small for loop that then just added each item from myLsit into tempList but I would have thought there would have been another mechanism (like clone??)
So my question(s):
is my assumption about tempList receiving a reference to myList correct
How should a list be copied to another list?
private myList as List (Of something)
sub new()
myList.add(new Something)
end sub
sub myCalledFunction()
dim tempList as new List (Of Something)
tempList = myList
Using i as IEnumerator = myList.getEnumarator
while i.moveNext
'if some critria is met then
tempList.remove(i.current)
end
end using
end sub
By writing tempList = myList you don't make a copy oh the collection, you only make tempList reference myList. Try this instead : dim tempList as new List (Of Something)(myList)
I think if you called myCalledFunction(byVal aListCopy as Something) you can let the framework do the work.
If your list consists of value types you can just create a new list with the old list passed in the constructor. If you are going to be doing a deep copy of a reference object your best bet is to have your reference type implement ICloneable (example). You can then loop through and clone each object or you could add an extension method (like this c# example).
Try this - use LINQ to create a new list from the original, for example:
Sub Main()
Dim nums As New List(Of Integer)
nums.Add(1)
nums.Add(2)
nums.Add(3)
nums.Add(4)
Dim k = (From i In nums _
Select i).ToList()
For Each number As Integer In nums
k.Remove(number)
Next
End Sub
k will then be a new list of numbers which are not linked to the source.

Can I use generics to populate List(of t) with custom classes?

I have several different lists I want to call. They all have the same format for the class:
id, value, description, order. Instead of creating a bunch a classes to return the all of the many lists, I wanted to use generics and just TELL it what kind of list to return. However, I can not figure out how to populate the classes.
Here are 2 examples of function in my calling code. This should indicate the type of list and the stored proc used to get the data:
Public Function getTheEyeColors()
Dim glEyeColors As New GenericList
Return glEyeColors.GetALList(Of EyeColor)("GetAllEyeColors")
End Function
Public Function getTheHairColors()
Dim glHairColors As New GenericList
glHairColors.GetALList(Of HairColor)("GetAllHairColors")
End Function
And here is the code I am trying to use to build the generic list...
Public Function GetALList(Of t)(ByVal storedproc As String) As List(Of t)
Dim lstGenericList As New List(Of t)
Dim oGenericListItem As t
Dim oProviderFactory As New ProviderFactory
Dim oConnection As DbConnection
Dim oReader As System.Data.IDataReader
Dim oFactory As DbProviderFactory
Dim oFileMgt As New FileMgt
Dim oCmd As DbCommand
oFactory = oProviderFactory.GetFactory
oConnection = oProviderFactory.GetProviderConnection(oFactory)
oCmd = oConnection.CreateCommand
oCmd.CommandType = CommandType.StoredProcedure
oCmd.CommandText = storedproc
Using (oConnection)
oConnection.Open()
oReader = oCmd.ExecuteReader()
While oReader.Read
HERE IS WHERE I AM NOT SURE HOW TO POPULATE THE EYECOLOR OR HAIRCOLOR CLASS
lstGenericList.Add(oGenericListItem)
End While
oConnection.Close()
End Using
Return lstGenericList
End Function
You could add two generic constraints; I don't know how to express them in VB, but here's the C# version:
T : new() - there has to be a parameterless constructor
T : ICommonInterface - T has to implement an interface
You then put the common properties (ID, Value, Description, Order) into the interface, and you'll be able to create a new T(), set the properties and add it to the list.
EDIT:
The VB Syntax to specify that it must both be creatable and implement an interface is:
(Of T As {ICommonInterface, New})
The way Jon recommends to do it is probably a better way to go, but another way I've seen this done is the FillObject method in the DotNetNuke architecture. Basically it's a conventions based method that uses reflection to match the properties on an object to the values of a dataset.
I personally don't like this method, but it does mean you don't have to create a new implementation of the code to hydrate the object for each stored procedure.
The code is available in the full source download in the DNN project.
While oReader.Read
HERE IS WHERE I AM NOT SURE HOW TO POPULATE THE EYECOLOR OR HAIRCOLOR CLASS
Check out LinqToSql (System.Data.Linq) . You might be re-inventing it.