Adding items to list results in duplicates. What is a better way? - vb.net

I have this code to return a list of fund sources for our organization.
Dim FundSourceList As New List(Of FundSource)
Dim fs As New FundSource
If results.Count > 0 Then
For Each result In results
fs.FundID = result.Item("strFundID")
fs.FundDescription = result.Item("txtFundIDDescr")
fs.ShortFundDescription = result.Item("txtFundIDDescrShort")
FundSourceList.Add(fs)
Next
End If
Return FundSourceList
The problem is that when I loop through the resulting FundSourceList all it shows is the last value. For example, if I have three fund sources (state, federal, athletic), then when I use this code to loop through all I get listed is athletic, athletic, athletic.
For Each FundSource In FundSources
Debug.Print(FundSource.FundDescription)
Next
So I change the code to this. I moved the creation of the fs variable inside the loop.
Dim results = From result In dsResult.Tables(0) Select result
Dim FundSourceList As New List(Of FundSource)
If results.Count > 0 Then
For Each result In results
Dim fs As New FundSource
fs.FundID = result.Item("strFundID")
fs.FundDescription = result.Item("txtFundIDDescr")
fs.ShortFundDescription = result.Item("txtFundIDDescrShort")
FundSourceList.Add(fs)
Next
End If
Return FundSourceList
This works fine but now I'm creating a new class over and over again. It seems a little inefficient to me. Can I not create the class outside the loop and use it over and over again? Thanks.

If you have 3 fund sources, you need three FundSource objects. It's as simple as that. I don't know what's inefficient about it...
How can you add 3 fund sources to your list but just create one?

You're not actually creating a class - the class is the code definition for the methods and properties. When you use the New operation, you're creating an instance of that class, which results in an object. When you have a list of objects, like FundSourceList, you want the items in it to be individual objects. So yes, the solution you have at the bottom is correct. You mention efficiency concerns - when you instantiate the object, basically all that is happening (in this case) is some memory is being allocated to store the variables (and some references for the managed memory, but you don't need to worry about that here). This is necessary and is optimized under-the-hood, so you shouldn't need to worry about that either.

You can't instantiate the object outside of the loop to achieve the result you're after.
This is because your object would be a reference type.
By instantiating outside of the loop, you would create one reference to your object.
When iterating through your results and setting the properties, you'll be using that same reference over and over.
All you're adding to the list on each iteration is the same reference, which by the end of the loop, will refer to an object containing the last values in your result set.
By creating new objects inside the loop, you create new references - each pointing to a new FundSource. Your loop now writes into a fresh object, and get your desired results.

Related

A null reference could result in runtime

Dim policy_key() As RenewalClaim.PolicyKeyType
policy_key(0).policyEffectiveDt = date_format_string(ld_EffectiveDate)
Getting error at Line2.
An Error occured - Object reference not set to an instance of an object.
Each element of object arrays also needs to be declared as a new object too.
Dim policy_key() As RenewalClaim.PolicyKeyType
Redim policy_key(0)
policy_Key(0) = new RenewalClaim.PolicyKeyType
policy_key(0).policyEffectiveDt = date_format_string(ld_EffectiveDate)
QUICK TIP: When declaring classes structures etc, it is useful to name them so you can see what type they are....
e.g.
cls_Policy_Key for a class
str_Policy_Key for a structure etc.
When you come back to your code after a year.. you will thank yourself for doing so.
Dim policy_key() As RenewalClaim.PolicyKeyType
is part of your problem. When you are declaring policy_key() you are actually declaring it as an array with no elements. If you don't particularly need to use an array, for example, if you don't need to add objects to a particular element number, you might be better using a list and declaring it like this
Dim policy_key As New List(Of RenewalClaim.PolicyKeyType)
This way, you can add items easily without having to resize your array each time - The code is a little longer than Trevor's answer, but less prone to errors when you extend your code -
dim newPolicy_Key as RenewalClaim.PolicyKeyType
newPolicy_Key.policyEffectiveDt = date_format_string(ld_EffectiveDate)
policy_Key.add(newPolicyKey)

Remove reference from List Of Objects in vb.net

I want to copy the content of one List(Of Object) to another and modify a single value. Is there a way to remove the reference?
In the code sample I get the output 'test2 test2' when I expect 'test1 test2'.
Module Module1
Sub Main()
Dim ListOfSample As New List(Of sample)
Dim var1 As New sample
var1.Name = "test"
ListOfSample.Add(var1)
ListOfSample.Add(var1)
Dim NewListOfSample As New List(Of sample)
NewListOfSample.AddRange(ListOfSample)
NewListOfSample(1).Name = "test2"
Console.Write(NewListOfSample(0).Name & " " & NewListOfSample(1).Name)
End Sub
End Module
Public Class sample
Public Name As String
End Class
Since your list is a list of Objects, when you perform add range, you are not adding "copies", instead you are adding the pointers (references) to the same objects that are in your original list.
You will need to clone all of your objects in the first list, and then add those clones to your second list. When it comes to cloning there are several different ways in .NET. Here's a post on getting deep copies of objects that does a good job explaining your options: Deep Copy of an Object
You can either create a clone method on your "sample" object to return a newly initialized copy of itself, or you can use some of the serialization methods mentioned in the post I linked to.
In the line NewListOfSample.AddRange(ListOfSample) you're adding references to your new list. So whatever you change in your new list will update the reference in your original list (they're both pointing to the same objects). You need to add new instances of Sample to the second list for it to contain independent items.

Sort all DataTable in Dataset for vb.net

I need to Sort all DataTables in mt Dataset. I have tried using DefaultView, It's sorting my datatable but after the loop the datatable looks same without sorting.
This is what i tried:
For Each Dt As DataTable In AlbumListDs.Tables
Dt.DefaultView.Sort = "ImageData Asc"
Dt = DataTable.DefaultView.ToTable
Dt.AcceptChanges()
AlbumListDs.AcceptChanges()
Next
Please correct me if i did anything wrong.
The changes that you made to the DataTable when inside the loop are local to the element returned by the Iterator of the For Each.
MSDN says
Modifying Collection Elements. The Current property of the enumerator
object is ReadOnly (Visual Basic), and it returns a local copy of each
collection element. This means that you cannot modify the elements
themselves in a For Each...Next loop. Any modification you make
affects only the local copy from Current and is not reflected back
into the underlying collection.
So, when you recreate the DataTable with
Dt = DataTable.DefaultView.ToTable
the new Dt instance is not the same instance contained in the DataSet. And so your changes are lost at the same moment when you loop over another DataTable element.
This is in striking contrast on what you can do in C# where an attempt to change the iterator instance is immediately caught by the compiler and signaled as an error at compile time
Perhaps you could just change the DefaultView sort expression and leave the DataTable in its original order (Surely it will be better for your memory usage). When you need to loop in an ordered way, just use the DataView
For Each drv As DataRowView in DataTable.DefaultView
Console.WriteLine(drv("YourField").ToString())
Next
Or use a normal for...loop (BUT IN BACKWARD direction)
For x as Integer = AlbumListDs.Tables.Count - 1 To 0 Step -1
Dim dt = AlbumListDs.Tables(x)
dt.DefaultView.Sort = "ImageData Asc"
AlbumListDs.Tables.RemoveAt(x)
AlbumListDs.Tables.Add(dt.DefaultView.ToTable)
Next
AlbumListDs.AcceptChanges
Notice that you need to remove the previous table from the collection (Tables is readonly) and then add the new one. This is safer if you loop backward from the end of the collection to the first element to avoid possible indexing errors
I was able to achieve it in a different way. It might not be the best solution but works for what I needed to do.
objReturnDS.Tables(1).DefaultView.Sort = "RowID asc"
objReturnDS.Tables(1).DefaultView.ToTable(True)
Dim sortedTable = objReturnDS.Tables(1).DefaultView.ToTable(True)
objReturnDS.Tables(1).Clear()
objReturnDS.Tables(1).Merge(sortedTable)

Setting the Item property of a Collection in VBA

I'm surprised at how hard this has been to do but I imagine it's a quick fix so I will ask here (searched google and documentation but neither helped). I have some code that adds items to a collection using keys. When I come across a key that already exists in the collection, I simply want to set it by adding a number to the current value.
Here is the code:
If CollectionItemExists(aKey, aColl) Then 'If key already has a value
'add value to existing item
aColl(aKey).Item = aColl(aKey) + someValue
Else
'add a new item to the collection (aka a new key/value pair)
mwTable_ISO_DA.Add someValue, aKey
End If
The first time I add the key/value pair into the collection, I am adding an integer as the value. When I come across the key again, I try to add another integer to the value, but this doesn't work. I don't think the problem lies in any kind of object mis-match or something similar. The error message I currently get is
Runtime Error 424: Object Required
You can't edit values once they've been added to a collection. So this is not possible:
aColl.Item(aKey) = aColl.Item(aKey) + someValue
Instead, you can take the object out of the collection, edit its value, and add it back.
temp = aColl.Item(aKey)
aColl.Remove aKey
aColl.Add temp + someValue, aKey
This is a bit tedious, but place these three lines in a Sub and you're all set.
Collections are more friendly when they are used as containers for objects (as opposed to containers for "primitive" variables like integer, double, etc.). You can't change the object reference contained in the collection, but you can manipulate the object attached to that reference.
On a side note, I think you've misunderstood the syntax related to Item. You can't say: aColl(aKey).Item. The right syntax is aColl.Item(aKey), or, for short, aColl(aKey) since Item is the default method of the Collection object. However, I prefer to use the full, explicit form...
Dictionaries are more versatile and more time efficient than Collections. If you went this route you could run an simple Exists test on the Dictionary directly below, and then update the key value
Patrick Matthews has written an excellent article on dictionaries v collections
Sub Test()
Dim MyDict
Set MyDict = CreateObject("scripting.dictionary")
MyDict.Add "apples", 10
If MyDict.exists("apples") Then MyDict.Item("apples") = MyDict.Item("apples") + 20
MsgBox MyDict.Item("apples")
End Sub
I think you need to remove the existing key-value pair and then add the key to the collection again but with the new value

For each variable acting like a local var?

I stumbled upon something i don't quite see the logic of. Let's ork with following piece of code:
For Each ds As DerivedScale In List
If ds.ScaleID = scaleId Then
ds.ScaleID = ds.ScaleID + scaleStep
CType(List(myCounter + scaleStep), DerivedScale).ScaleID = scaleId
myDerivedScale = ds
ds = List(myCounter + scaleStep) <---------------------
List(myCounter + scaleStep) = myDerivedScale
Exit For
End If
myCounter += 1
Next
This piece is written for 2 records to change place and change the sequence number (scaleid). The arrow indicates where the issue occurs. The item "ds" is replaced by the object 1 indexnumber higher/lower. This does however not effect that object in the List. So when i check, item ds isn't set.
However, when i look at ds.ScaleId = ds.ScaleID + scaleStep, this is reflected in the List.
So what I am wondering is: is "ds" acting like a local variable here, and can i only make changes to it's properties?
Thanks in advance.
ds is a reference to an object that is also referenced by the list. So when you set properties on it, those changes are reflected in the list as well. But since ds is just reference, as you surmise, changing what it refers to will not affect the list.
You need to iterate through the list by index instead of by the enumerator (all you're getting is a reference here). Then you can swap the objects by index and change their properties.
Objects in .Net are passed by reference.
You have a single DerivedScale instance in your list; the For Each loop iterates over the same instances that are in the list.
No copies are made; you're modifying the objects themselves.
That variable is scoped to the loop in which it is declared. Reassigning ds will not alter the list because both ds and the list have an object reference to some item, you are merely changing which item ds refers to without affecting the list.