Why won't this list of struct allow me to assign values to the field? - vb.net

Public Structure testStruct
Dim blah as integer
Dim foo as string
Dim bar as double
End Structure
'in another file ....
Public Function blahFooBar() as Boolean
Dim tStrList as List (Of testStruct) = new List (Of testStruct)
For i as integer = 0 To 10
tStrList.Add(new testStruct)
tStrList.Item(i).blah = 1
tStrList.Item(i).foo = "Why won't I work?"
tStrList.Item(i).bar = 100.100
'last 3 lines give me error below
Next
return True
End Function
The error I get is: Expression is a value and therefore cannot be the target of an assignment.
Why?

I second the opinion to use a class rather than a struct.
The reason you are having difficulty is that your struct is a value type. When you access the instance of the value type in the list, you get a copy of the value. You are then attempting to change the value of the copy, which results in the error. If you had used a class, then your code would have worked as written.

try the following in your For loop:
Dim tmp As New testStruct()
tmp.blah = 1
tmp.foo = "Why won't I work?"
tmp.bar = 100.100
tStrList.Add(tmp)
Looking into this I think it has something to do with the way .NET copies the struct when you access it via the List(of t).
More information is available here.

Try creating the object first as
Dim X = New testStruct
and setting the properties on THAT as in
testStruct.blah = "fiddlesticks"
BEFORE adding it to the list.

Related

How do I copy Array values to a structure

I would like to to copy that values of an array into a Structure.
Example:
' The Array
Dim Columns(2) As String
' The Structure
Private Structure Fields
Public FName As String
Public LName As String
Public Email As String
End Structure
' I would like to map it like so:
Fields.FName = Columns(0)
Fields.LName = Columns(1)
Fields.Email = Columns(2)
Obviously I could write a function if it was so simple, but really there are over 25 columns and it's a pain to write a function that would map it.
Is there some way to do this?
There really is no simple way that will work in all cases. What you are complaining is too much effort is the only way to guarantee that it will work in all cases.
That said, if you can guarantee that the number of elements in the array matches the number of properties/fields in the structure/class and that they are in the same order and of the same types then you could use Reflection in a loop, e.g.
Private Function Map(source As Object()) As SomeType
Dim result As New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return result
End Function
EDIT:
The code I have provided works as is if SomeType is a class but, as I missed the first time around, not for a structure. The reason is that structures are value types and therefore a copy of the original object is being sent to SetValue, so the field value never gets set on that original object. In theory, to prevent a copy being created, you should be able to simply box the value, i.e. wrap it in an Object reference:
Private Function Map(source As Object()) As SomeType
Dim result As Object = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function
As it turns out though, the VB compiler treats that a little differently than the C# compiler treats the equivalent C# code and it still doesn't work. That's because, in VB, the boxed value gets unboxed before being passed to the method, so a copy is still created. In order to make it work in VB, you need to use a ValueType reference instead of Object:
Private Function Map(source As Object()) As SomeType
Dim result As ValueType = New SomeType
Dim resultType = result.GetType()
Dim fields = resultType.GetFields()
For i = 0 To source.GetUpperBound(0)
fields(i).SetValue(result, source(i))
Next
Return DirectCast(result, SomeType)
End Function

Values in list of structures don't change

I have defined a structure in my code and have a list of this structures"
Structure Parcel
Public name As String
Public type As String
End Structure
Dim ParcelList As New List(Of Parcel)
Then I'm trying to set some values to an element of the list which name is known to me
For Each myParcel As Parcel In ParcelList
If (myParcel.name = "Parcel1") Then
myParcel.type="Type1"
End If
Next
Unfortunately values in my list don't change at all. what am I doing wrong?
As Parcel is a Structure, it is passed by value so when iterating through collection, you are modifying a copy of your structure.
To better understand this case, you should understand what For Each really is. Your code can be translated into:
Dim enumerator As List(Of Parcel).Enumerator = ParcelList.GetEnumerator()
While enumerator.MoveNext()
' Here you have a local copy of your Structure
Dim myParcel As Parcel = enumerator.Current
Dim flag As Boolean = Operators.CompareString(myParcel.name, "Parcel1", False) = 0
If flag Then
' Here you modify your local copy
myParcel.type = "Type1"
End If
End While
If Parcel was a Class, it would be passed by reference so no local copy would be created and line myParcel.type = "Type1" would change proper object existing in your collection.
As already Stated this is because you are modifying a local copy of a value type. One way round this is to access the items in the list by ordinal and replace the ordinal value type with a new type:
For i As Integer = 0 To ParcelList.Count - 1
If ParcelList(i).name = "Parcel1" Then
ParcelList(i) = New Parcel With {.name = ParcelList(i).name, .type = "Type1"}
End If
Next
But really you should change the Sturcture to a Class
When checking for strings use Equals instead of '='.
If (myParcel.name.equals("Parcel1")) Then
myParcel.type="Type1"
End If
Strings are in fact 'Objects'. When you compare Strings (example StringA = StringB), you check the allocation of the String in Memory instead of the contents of the string.
Even better would be:
If (myParcel.name.ToUpper().equals(Cstr("Parcel1").toUpper())) Then
myParcel.type="Type1"
End If
That way you ignore any difference case-wise.
example:
myParcel.name = "teST"
myParcel.name.equals("test")
is False
myParcel.name.ToUpper().equals(Cstr("test").toUpper())
is true

Exception in assigning to CurrencyDecimalDigits in Globalization.NumberFormatInfo

I'm using Globalization.NumberFormatInfo in vb code and trying to assign value to CurrencyDecimalDigits which is a member of this class:
Dim a As Globalization.NumberFormatInfo
a = New Globalization.NumberFormatInfo()
Dim b As Integer = 4
a.CurrencyDecimalDigits = b
In MSDN it's written that it is a read only member, though it is shown a code with assigning to it: http://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo.currencydecimaldigits.aspx
Is there a way to assign value to CurrencyDecimalDigits?
Does anybody had this problem before?
Thanks
Found the solution, NumberFormatInfo is read only, therefore need to call clone() method when assigning to it:Dim a As Globalization.NumberFormatInfo a = New Globalization.NumberFormatInfo() a = a.Clone() Dim b As Integer = 4 a.CurrencyDecimalDigits = b

How to assign a value to an array from a combobox

The code I have is:
Dim Dbase() As String = Nothing
Dbase(0) = Db_ComboBox.Text
I have declared Dbase as array and assigned Nothing, Db_ComboBox is a combobox.
For that assignment statement, I'm getting the following error: "Reference 'Dbase' has a value of 'Nothing'"
What is the reason for this error, and how can I take the value from the combobox and save it in the array?
You need to change this:
Dim Dbase() As String = Nothing
to this (declare an array of 1 element):
Dim Dbase(0) As String
And then this line will work:
Dbase(0) = Db_ComboBox.Text
If you need to change your array size you can use Redim or Redim preserve, as required.
If you anticipate contents of Dbase to change often, I am all with #Joel's suggestion about switching to List(Of String) instead of handling array sizes manually.
Let's look at your code:
Dim Dbase() As String = Nothing
Dbase(0) = Db_ComboBox.Text
Especially the first line. That first line creates a variable that can refer to an array, but the = Nothing portion explicitly tells it, "Do not create a real array here yet". You have, effectively, a pointer that doesn't point to anything.
I get here that what you really need is a List collection that you can append to over time:
Dim Dbase As New List(Of String)()
Dbase.Add(Db_ComboBox.Text)
Dbase() IS NOTHING. Look at this example:
cargoWeights = New Double(10) {}
atmospherePressures = New Short(2, 2, 4, 10) {}
inquiriesByYearMonthDay = New Byte(20)()() {}
That's how you declare arrays.
More examples: http://msdn.microsoft.com/en-us/library/vstudio/wak0wfyt.aspx

How do you assign values to structure elements in a List in VB.NET?

I have a user-defined structure in a list that I am trying to change the value for in an individual element within the list of structures. Accessing the element is not a problem. However, when I try to update the value, the compiler complains:
"Expression is a value and therefore cannot be the target of the
assignment"
For example:
Public Structure Person
Dim first as String
Dim last as String
Dim age as Integer
End Structure
_
Public Sub ListTest()
Dim newPerson as Person
Dim records as List (Of Person)
records = new List (Of Person)
person.first = "Yogi"
person.last = "bear"
person.age = 35
records.Add(person)
records(0).first = "Papa" ' <<== Causes the error
End Sub
As the other comments said, when you refer to records(0), you get a copy of the struct since it is a value type. What you can do (if you can't change it to a Class) is something like this:
Dim p As Person = records(0)
p.first = "Papa"
records(0) = p
Although, I think it's just easier to use a Class.
There are actually two important concepts to remember here.
One is that, as Hans and Chris have pointed out, Structure Person declares a value type of which copies are passed between method calls.
You can still access (i.e., get and set) the members of a value type, though. After all, this works:
Dim people(0) As Person
people(0).first = "Yogi"
people(0).last = "Bear"
people(0).age = 35
So the other important point to realize is that records(0) accesses the List(Of Person) class's special Item property, which is a sugary wrapper around two method calls (a getter and setter). It is not a direct array access; if it were (i.e., if records were an array), your original code would actually have worked.
I had the same problem, and I fixed it by adding a simple Sub to the structure that changes the value of the property.
Public Structure Person
Dim first as String
Dim last as String
Dim age as Integer
Public Sub ChangeFirst(value as String)
me.first = value
End Sub
End Structure