VB Assignment of member field programmatically - vb.net

This question is a follow-on to VB ReDim of member field programmatically. After the arrays are dimensioned appropriately, I try to set the values of the elements, but I get an exception at run time when I try to assign the first value (MySB.AssignValues(0, "B", 0, 7.6))
System.InvalidCastException was unhandled
HResult=-2147467262
Message=Object cannot be stored in an array of this type.
Source=mscorlib
Module TestSetArray
Public Class BS
Public A As String
Public B() As Double
Public C() As Double
End Class
Public Class SB
Public MyBS() As BS
'ReadFieldString is a function that returns a string of the field name of Class BS,
'i.e., A, B or C. For test purpose, retun a constant
Public Function ReadFieldString() As String
Return "B"
End Function
'GetArrayDim is a function that returns an integer, which is the size of the array
'of that field name. For test purpose, retun a constant
Public Function GetArrayDim() As Integer
Return 2
End Function
Public Sub DimArrays()
ReDim MyBS(3)
Dim i As Integer
For i = 0 To MyBS.Length - 1
MyBS(i) = New BS()
Dim f = GetType(BS).GetField(ReadFieldString())
f.SetValue(MyBS(i), Array.CreateInstance(f.FieldType.GetElementType(), GetArrayDim()))
Next
End Sub
Public Sub AssignValues(MainIndex As Integer, TheName As String, TheIndex As Integer, TheValue As Double)
Dim f = MyBS(MainIndex).GetType.GetMember(TheName)
f.SetValue(TheValue, TheIndex)
End Sub
End Class
Sub Main()
Dim MySB As SB = New SB
MySB.DimArrays()
MySB.AssignValues(0, "B", 0, 7.6)
MySB.AssignValues(0, "B", 1, 8.2)
End Sub
End Module
Thanks in advance.

The problem is that the GetMember method returns an array of type MemberInfo, not the double array of the class. You'd probably have an easier time if you used GetField instead. You have to call GetValue and cast its result to an Array in order to use SetValue to set the value.
Public Sub AssignValues(MainIndex As Integer, TheName As String, TheIndex As Integer, TheValue As Double)
Dim f = MyBS(MainIndex).GetType().GetField(TheName)
Dim doubleArray = DirectCast(f.GetValue(MyBS(MainIndex)), Array)
doubleArray.SetValue(TheValue, TheIndex)
End Sub
or if you know that the array will always be an array of Double, you can cast it directly to that:
Public Sub AssignValues(MainIndex As Integer, TheName As String, TheIndex As Integer, TheValue As Double)
Dim f = MyBS(MainIndex).GetType().GetField(TheName)
Dim doubleArray = DirectCast(f.GetValue(MyBS(MainIndex)), Double())
doubleArray(TheIndex) = TheValue
End Sub

Related

Return value gets assigned but always returns as 0

I'm trying to calculate the total count and value of orders for a customer, using the code below.
Dim iOrderCount As Integer
Dim iLineCount As Integer
Dim cTotalGoods As Decimal
Dim cTotalVat As Decimal
Dim cTotalDelivery As Decimal
Using manOrders As New CManOrders(m_dbSql)
manOrders.GetOrdersInProgress(sAccountCode, iOrderCount, iLineCount, cTotalGoods, cTotalVat, cTotalDelivery)
When I assign values to these variables in the GetOrdersInProgress subroutine, the values are being assigned correctly, which I can see when I step through the code.
Public Sub GetOrdersInProgress(sAccountCode As String, ByRef RET_orderCount As Integer, ByRef RET_lineCount As Integer,
ByRef RET_totalGoods As Decimal, ByRef RET_totalVat As Decimal, RET_totalDelivery As Decimal)
...
For Each dr As DataRow In m_dbSql.getDataTable(sql).Rows
RET_orderCount = dbToInt(dr(ORDER_COUNT))
RET_lineCount = dbToInt(dr(LINE_COUNT))
RET_totalGoods = dbToDecimal(dr(TOTAL_GOODS))
RET_totalVat = dbToDecimal(dr(TOTAL_VAT))
RET_totalDelivery = dbToDecimal(dr(2))
Return
Next
However, once I step through and move back into where the GetOrdersInProgress subroutine is called from, all of the values in the variables are returned correctly, except for RET_totalDelivery - the new one I've added to another developer's project.
The value in the RET_totalDelivery variable in the Public Sub GetOrdersInProgress... line is correct and it's correct after the assignment, but when it reaches Return and the variables are then used in the parent subroutine, for some reason they're all correct except for the new one I've added, RET_totalDelivery. I'd understand if the value wasn't being assigned correctly, however it is.
Why would it be returning 0 all the time?
By default, arguments passed to vb.net methods are passed by value, or ByVal. You did not specify ByRef in your RET_totalDelivery argument in GetOrdersInProgress.
Changes made to arguments passed by value are not retained when the method ends.
Your Sub should now be...
Public Sub GetOrdersInProgress(sAccountCode As String, ByRef RET_orderCount As Integer, ByRef RET_lineCount As Integer, ByRef RET_totalGoods As Decimal, ByRef RET_totalVat As Decimal, ByRef RET_totalDelivery As Decimal)
I prefer to write the method as a function. Write a class to hold all the return values. Then change the method into a function with just input parameters and return the values you fetch from sql.
Sub Main
Dim bl = New OrdersInProgressBusinessLogic()
Dim ordersInProgress = bl.GetOrdersInProgress("some account code")
End Sub
Public Class OrdersInProgress
Public Property OrderCount As Integer
Public Property LineCount As Integer
Public Property TotalGoods As Decimal
Public Property TotalVat As Decimal
Public Property TotalDelivery As Decimal
End Class
Public Class OrdersInProgressBusinessLogic
Public Function GetOrdersInProgress(sAccountCode As String) As OrdersInProgress
Dim ordersInProgress = New OrdersInProgress()
' some code here to fetch data from sql
For Each dr As DataRow In m_dbSql.getDataTable(sql).Rows
With ordersInProgress
.OrderCount = dbToInt(dr(ORDER_COUNT))
.LineCount = dbToInt(dr(LINE_COUNT))
.TotalGoods = dbToDecimal(dr(TOTAL_GOODS))
.TotalVat = dbToDecimal(dr(TOTAL_VAT))
.TotalDelivery = dbToDecimal(dr(2))
End With
Next
Return ordersInProgress
End Function
' some other functions/subs for OrdersInProgress class
End Class

Determine the length of a generic type?

How can I at run-time determine the length of a generic type (with constraint Structure).
I need the byte count to create a view accessor of a MemoryMappedFile (which is initialized in the constructor).
Private goVirtualFile As MemoryMappedFile
Private glLength As Long = 0
Public Class CTest(Of T As Structure)
Public Sub Append(tData As T())
Dim iNumStruct As Integer = tData.Length 'Number of structures.
Dim iStructLen As Integer = ... 'Length of 1 structure in bytes.
Dim iBytes As Integer = iNumStruct * iStructLen 'Total length in bytes.
Using goViewStream = goVirtualFile.CreateViewAccessor(
glLength, iBytes)
...
End Using
End Sub
Public Function Read(lOffset As Long, iNumStruct As Integer) As T()
...
End Sub
End Class

checking datatype in an overload doesn't work for decimal

I'm using vb.net. I am doing some checking and scrubbing of my data when i get it from the database. I'm using an overload but it doesn't seem to be working for decimal numbers. Decimal numbers get treated as integers.
Public Class CheckData
Public Shared Function Check(row As DataRow, columnName As String, pDefaultValue As Decimal) As Decimal
Dim x As Object = ReplaceDBNullAndColumnExists(row, columnName)
Dim y As Decimal = CDec(Dempsey.fnIsNull.IsNull(x, pDefaultValue))
Return y
End Function
Public Shared Function Check(row As DataRow, columnName As String, pDefaultValue As DateTime) As DateTime
Dim x As Object = ReplaceDBNullAndColumnExists(row, columnName)
Dim y As DateTime = Dempsey.fnIsNull.IsNull(x, pDefaultValue)
Return y
End Function
Public Shared Function Check(row As DataRow, columnName As String, pDefaultValue As Integer) As Integer
Dim x As Object = ReplaceDBNullAndColumnExists(row, columnName)
Dim y As Integer = Dempsey.fnIsNull.IsNull(x, pDefaultValue)
Return y
End Function
Public Shared Function Check(row As DataRow, columnName As String, pDefaultValue As String) As String
Dim x As Object = ReplaceDBNullAndColumnExists(row, columnName)
Dim y As String = Dempsey.fnIsNull.IsNull(x, pDefaultValue)
Return y
End Function
Public Shared Function Check(row As DataRow, columnName As String, pDefaultValue As Boolean) As Boolean
Dim x As Object = ReplaceDBNullAndColumnExists(row, columnName)
Dim y As Boolean = Dempsey.fnIsNull.IsNull(x, pDefaultValue)
Return y
End Function
So if i pass in some data and the type is a string, integer, boolean or datetime it goes to the propert function. if i pass in a decimal it goes to integer. If i set a breakpoint on the public shared function check that is a integer function and do a
row.Table.Columns(columnName).DataType.Name
I get back - "Decimal" (doing this in the immediate window)
So my question is what have i done wrong that it doesn't take it to the decimal overload and return a decimal value. By the way, the actual data value is 37.50.
Thanks
shannon
I use this to populate list.
Public Function populate(mDs As DataSet) As List(Of SR_SalaryRange_Current)
Dim rows As DataRowCollection
Dim drow As DataRow
Dim oSR_SalaryRange_Current As SR_SalaryRange_Current
Dim oSR_SalaryRange_Currents As List(Of SR_SalaryRange_Current) = New List(Of SR_SalaryRange_Current)
Dim dt As New DataTable
Try
dt = mDs.Tables("SR_SalaryRange_Currents")
rows = dt.Rows
For Each drow In rows
oSR_SalaryRange_Current = New SR_SalaryRange_Current
With oSR_SalaryRange_Current
.tblSR_SalaryRange_CurrentID = SitePlumbing.CheckData.Check(drow, "intTblSR_SalaryRange_CurrentID", 0)
.EffectiveDate = SitePlumbing.CheckData.Check(drow, "dtmEffectiveDate", CDate("1/1/1900"))
.WorkWeekHours = SitePlumbing.CheckData.Check(drow, "decWorkWeekHours", 0)
End With
oSR_SalaryRange_Currents.Add(oSR_SalaryRange_Current)
Next
Catch ex As Exception
ErrorMsg = "Populate Error:" & ex.InnerException.ToString
Return oSR_SalaryRange_Currents
End Try
Return oSR_SalaryRange_Currents
End Function
from there it goes into the checkdata that i mentioned before. In the code above it correctly goes to an integer and a datetime when hitting the overload, just isn't doing it for the decimal.
A decimal is implicitly covertable into an integer so the compiler is confused if you have OPTION STRICT set to off. You have two options:
1) Set OPTION STRICT to ON. Then the compiler will not convert implicitly.
2) Pass in a decimal literal like this: 10D (i.e. there is a D on the end telling the compiler it is a decimal).
For example:
'Sub Routine 1
public sub Test(ByVal d as decimal)
end sub
'Sub Routine 2
public sub Test(ByVal i as integer)
end sub
//Client
dim d1 as decimal={Number}D 'where {number} is replaced with a number
dim d2 as decimal={Number} 'where {number} is replaced with a number
Test(d1) 'this will always go to Sub Routine 1 regardless of whether OPTION strict is ON or OFF
Test(d2) 'this will go to Sub Routine 1 if option strict is ON. If it is OFF, then it may go to Sub Routine 2 depending on the size of the number.
I was given a way to work with this by Viorel on another forum. He suggested I do
Check(drow, "decWorkWeekHours", 0#)
and that did indeed work. When i asked what it does, this was his response.
With ‘#’, the constant ‘0#’ becomes a decimal one. Then VB.NET chooses the definition of Check that takes a Decimal parameter, since it is more suitable comparing with other candidates.
You can also write ‘0d’ [https://learn.microsoft.com/en-us/dotnet/visual-basic/programming-guide/language-features/data-types/type-characters].
hope that will help someone else down the road.
Thanks
shannon

Merge two Array Properties in Class

I have a Class (TestClass) with the following three properties:
Private myArr1() As String
Private myArr2() As String
Private myFinalArray() As String
Private Sub Class_Initialize()
ReDim myArr1(0 To 3)
ReDim myArr2(0 To 2)
ReDim myFinalArray(0 To 6)
End Sub
Public Property Get arr1(ByVal index As Long) As Double
arr1 = myArr1(index)
End Property
Public Property Let arr1(ByVal index As Long, ByVal myvalue As Double)
myArr1(index) = myvalue
End Property
Public Property Get arr2(ByVal index As Long) As Double
...
Public Property Let arr2(ByVal index As Long, ByVal myvalue As Double)
...
Public Property Get FinalArray(ByVal index As Long) As Double
...
Public Property Let FinalArray(ByVal index As Long, ByVal myvalue As Double)
...
Here we have just two arrays that I fill with data:
Sub test()
Dim t As TestClass
Set t = New TestClass
For i = 0 To 3
t.arr1(i) = i
Next
For i = 0 To 2
t.arr2(i) = i
Next
t.GetFinalValues (t)
End Sub
My Problem now is that these array elements must be rearranged according to a confused pattern I want to write a property for that but it is not working. My idea was to add the following function to my class:
Public Function GetFinalValues(ByRef t As TestClass) As Double()
'Imput parameter arrX can ben the Value as well as the Bench arrays.
Dim arr1(2) As Double
Dim arr2(3) As Double
Dim i As Integer
Dim arrCollection(6) As Double
arrCollection(0) = t.arr1(0)
arrCollection(1) = t.arr2(0)
arrCollection(2) = t.arr2(1)
arrCollection(3) = t.arr1(1)
arrCollection(4) = t.arr2(2)
arrCollection(6) = t.arr1(2)
arrCollection(5) = t.arr2(3)
'Assign return object
For i = 0 To 6
FinalArray(i) = arrCollection(i)
Next i
GetFinalValues
End Function
If I run this the code stops at t.GetFinalValues(t) giving me the Errormessage: Object doesent support property or method. Anyone who can help me get this working? Or has a rebuild idea for a even better solution to that problem?
EDIT: I added vb.net since this might be a construction problem that is not specified to vba
You have two issues:
First you should remove the parentheses from this line:
t.GetFinalValues (t)
so it's just:
t.GetFinalValues t
Then your function needs to return a String array not Double, since that's the type of the private variable. Your class code becomes something like this:
Private Sub Class_Initialize()
ReDim myArr1(0 To 3)
ReDim myArr2(0 To 2)
ReDim myFinalArray(0 To 6)
End Sub
Public Property Get arr1(ByVal index As Long) As Double
arr1 = myArr1(index)
End Property
Public Property Let arr1(ByVal index As Long, ByVal myvalue As Double)
myArr1(index) = myvalue
End Property
Public Property Get arr2(ByVal index As Long) As Double
arr2 = myArr1(index)
End Property
Public Property Let arr2(ByVal index As Long, ByVal myvalue As Double)
myArr2(index) = myvalue
End Property
Public Property Get FinalArray(ByVal index As Long) As Double
FinalArray = myArr1(index)
End Property
Public Property Let FinalArray(ByVal index As Long, ByVal myvalue As Double)
myFinalArray(index) = myvalue
End Property
Public Function GetFinalValues(ByRef t As TestClass) As String()
'Imput parameter arrX can ben the Value as well as the Bench arrays.
Dim arr1(2) As Double
Dim arr2(3) As Double
Dim i As Integer
Dim arrCollection(6) As Double
arrCollection(0) = t.arr1(0)
arrCollection(1) = t.arr2(0)
arrCollection(2) = t.arr2(1)
arrCollection(3) = t.arr1(1)
arrCollection(4) = t.arr2(2)
arrCollection(6) = t.arr1(2)
arrCollection(5) = t.arr1(3)
'Assign return object
For i = 0 To 6
FinalArray(i) = arrCollection(i)
Next i
GetFinalValues = myFinalArray
End Function
Note that you were also trying to use t.arr2(3) which exceeds the number of elements in arr2 so I assumed you meant t.arr1(3)

VB ReDim of member field programmatically

I am trying to ReDim a member array based on reading a file. I cannot figure out how to do it. This is what I tried, but it does not work.
Public Class BS
Public A() As String
Public B() As Double
Public C() As Double
End Class
Public Class SB
Public MyBS() As BS
'ReadFieldString is a function that returns a string of the field name of Class BS,
'i.e., A, B or C. For test purpose, retun a constant
Public Function ReadFieldString() As String
Return "B"
End Function
'GetArrayDim is a function that returns an integer, which is the size of the array
'of that field name. For test purpose, retun a constant
Public Function GetArrayDim() As Integer
Return 1
End Function
Public Sub DimArrays()
ReDim MyBS(3)
Dim i As Integer
For i = 0 To MyBS.Length - 1
'Try to ReDim the member of MyBS
ReDim MyBS(i).GetType.GetField(ReadFieldString)(GetArrayDim)
Next()
End Sub
End Class
The ReDim statement has the error "Expression is a value and therefore cannot be the target of an assignment."
Thanks in advance.
I'm not sure ReDim works like that. Changing the code to this will achieve what I believe you are after:
Public Sub DimArrays()
ReDim MyBS(3)
Dim i As Integer
For i = 0 To MyBS.Length - 1
MyBS(i) = New BS()
Dim f = GetType(BS).GetField(ReadFieldString())
f.SetValue(MyBS(i), Array.CreateInstance(f.FieldType.GetElementType(), GetArrayDim()))
Next
End Sub
However, I think a better approach would be to specify the array size in the BS constructor.