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.
Related
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)
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)
I am using a dictionary object from the MS Scripting Runtime library to store a series of arrays and perform operations on the array cells as necessary. There is a for loop to go through the process of creating all of these entries. My issue is that when using the .exists property, it is returning True even before the item has been added.
Closer debugging indicates that the key is being added to the dictionary at the beginning of the for loop, even though no .add command is used and will not be used until the end of the loop.
I have tried a few different configurations, but here is a simple example that fails:
Dim dTotals As Dictionary
Set dTotals = New Dictionary
dTotals.CompareMode = BinaryCompare
For Each cell In rAppID
If Not dTotals.Exists(cell) Then
Set rAppIDCells = Find_Range(cell, rAppID)
Set rAppIDValues = rAppIDCells.Offset(0, 6)
dAppIDTotal = WorksheetFunction.Sum(rAppIDValues)
dTotals.Add Key:=cell.Value, Item:=dAppIDTotal
End If
Next cell
Where each cell contains a string / unique id. At the If statement, the code is returning false, even on the first iteration.
In the official documentation‌​ for the scripting runtime it says "If key is not found when attempting to return an existing item, a new key is created and its corresponding item is left empty."
...and yea, when you're debugging in a loop, it appears to pop right out of the sky before the '.exists' function is even called. All is well...
Instead of attempting to add the item that just got added, as in:
dTotals.Add Key:=cell.Value, Item:=dAppIDTotal
...just set the empty object currently at your key to your new one:
dTotals(cell.Value) = dAppIDTotal
So your code block becomes:
If Not dTotals.Exists(cell) Then
Set rAppIDCells = Find_Range(cell, rAppID)
Set rAppIDValues = rAppIDCells.Offset(0, 6)
dAppIDTotal = WorksheetFunction.Sum(rAppIDValues)
dTotals(cell.Value) = dAppIDTotal
End If
Voila. I tend to rediscover this "feature" on every revisit to VBA. You may also notice the effects of it if you are having a memory leak caused by adding new keys that you do not intend to store.
I had this problem manifest itself while debugging when I had a watch that attempted to return the "missing" key's item. Actually, further frustrated debugging had the same problem when I literally had a watch for the [scriptingdictonaryObject].exists() condtional); I suggest that the "missing" key is added because of the watch. When I removed the watch and instead created a temporary worksheet to copy the array to while running, the unwanted keys were no longer added.
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.
Update
I decided to iterate through the Data.DataTable and trimmed the values there.
Utilizing SirDemon's post, I have updated the code a little bit:
Sub test(ByVal path As String)
Dim oData As GSDataObject = GetDataObj(path)
EmptyComboBoxes()
Dim oDT As New Data.DataTable
Try
Dim t = From r In oData.GetTable(String.Format("SELECT * FROM {0}gsobj\paths ORDER BY keyid", AddBS(path))) Select r
If t.Count > 0 Then
oDT = t.CopyToDataTable
For Each dr As Data.DataRow In oDT.Rows
dr.Item("key_code") = dr.Item("key_code").ToString.Trim
dr.Item("descript") = dr.Item("descript").ToString.Trim
Next
dataPathComboBox.DataSource = oDT
dataPathComboBox.DisplayMember = "descript"
dataPathComboBox.ValueMember = "key_code"
dataPathComboBox.SelectedIndex = 0
dataPathComboBox.Enabled = True
End If
Catch ex As Exception
End Try
End Sub
This works almost as I need it to, the data is originally from a foxpro table, so the strings it returns are <value> plus (<Field>.maxlength-<value>.length) of trailing whitespace characters. For example, a field with a 12 character length has a value of bob. When I query the database, I get "bob_________", where _ is a space.
I have tried a couple of different things to get rid of the whitespace such as:
dataPathComboBox.DisplayMember.Trim()
dataPathComboBox.DisplayMember = "descript".Trim.
But nothing has worked yet. Other than iterating through the Data.DataTable or creating a custom CopyToDataTable method, is there any way I can trim the values? Perhaps it can be done in-line with the LINQ query?
Here is the code I have so far, I have no problem querying the database and getting the information, but I cannot figure out how to display the proper text in the ComboBox list. I always get System.Data.DataRow :
Try
Dim t = From r In oData.GetTable("SELECT * FROM ../gsobj/paths ORDER BY keyid") _
Select r
dataPathComboBox.DataSource = t.ToList
dataPathComboBox.SelectedIndex = 0
'dataPathComboBox.DisplayMember = t.ToList.First.Item("descript")
dataPathComboBox.Enabled = True
Catch ex As Exception
Stop
End Try
I know that on the DisplayMember line the .First.Item() part is wrong, I just wanted to show what row I am trying to designate as the DisplayMember.
I'm pretty sure your code tries to set an entire DataRow to a property that is simply the name of the Field (in a strongly type class) or a Column (in a DataTable).
dataPathComboBox.DisplayMember = "descript"
Should work if the DataTable contains a retrieved column of that name.
Also, I'd suggest setting your SelectedIndex only AFTER you've done the DataBinding and you know you actually have items, otherwise SelectedIndex = 0 may throw an exception.
EDIT: Trimming the name of the bound column will trim just that, not the actual bound value string. You either have to go through all the items after they've been bound and do something like:
dataPathComboBox.Item[i].Text = dataPathComboBox.Item[i].Text.Trim()
For each one of the items. Not sure what ComboBox control you're using, so the item collection name might be something else.
Another solution is doing that for each item when it is bound if the ComboBox control exposes an onItemDataBound event of some kind.
There are plenty of other ways to do this, depending on what the control itself offers and what you choose to do.
DisplayMember is intended to indicate the name of the property holding the value to be displayed.
In your case, I'm not sure what the syntax will by since you seem to be using a DataSet, but that should be
... DisplayMember="Item['descript']" ...
in Xaml, unless you need to switch that at runtime in which case you can do it in code with
dataPathComboBox.DisplayMember = "Item['descript']"
Again, not 100% sure on the syntax. If you are using a strongly typed DataSet it's even easier since you should have a "descript" property on your row, but given hat your error indicates "System.DataRow" and not a custom type, I guess you are not.
Because I can't figure out the underlying type of the datasource you are using I suggest you to change commented string to
dataPathComboBox.DisplayMember = t.ElementType.GetProperties.GetValue(0).Name
and try to determine correct index (initially it is zero) in practice.