For some time I have been wondering if it is possible to enumerate, or set and index for the properties of an object or structure.
I currently have a set of custom graph generator classes for different reports, but they all accept the same structure as a parameter.
The structures' properties' values get set from a SQL reader that reads columns as they are set up in a table in the database. Now ideally want to loop though these column values sequentially and write them to the properties of the structure.
My Structure is as follows:
Public Structure MyStructure
Dim GraphName As String
Dim GraphValue As Integer
Dim Red As Integer
Dim Green As Integer
Dim Blue As Integer
End Structure
Now I want to be able to loop through these properties and assign values to each. E.g.:
Dim Struct as MyStructure
For i as integer = 0 to 4
Struct.i = "A value retrieved from database"
Next i
The main idea is that i want to avoid using a case statement:
Dim Struct as MyStruct
For i as integer = 0 to 4
Select Case i
Case 0
Struct.GraphName = "A value retrieved from database"
Case 1
Struct.GraphValue = "A value retrieved from database"
'Etc.
End Select
Next i
Any insight on this will be much appreciated.
To "dynamically" access the fields, you would use reflection:
Private Structure foo
Public i As Integer
Public s As String
End Structure
Private Sub bar()
Dim f As foo
Dim fields = GetType(foo).GetFields(BindingFlags.Public Or BindingFlags.Instance)
For Each fi As FieldInfo In fields
fi.SetValue(f, GetValueFromDBForName(fi.Name))
Next
End Sub
Be aware that Reflection is NOT fast. "FasterFlect" (via NuGet) or other "fast" replacements might be an alternative. OR you consider to use an ORM (object relational mapping) "out-of-the-box"
Related
I have an object called Statistics. Inside this object there are 6 more objects. Each contains properties with values.
So for example, Statistics(0) contains an 'age' field, a 'sex' field, 'vehicle' etc...
same goes for Statistics(1), Statistics(2) etc...
What I want to do is compare Statistics(0) all the way to Statistics(6) and find out if any one of them is identical to another.
If all of the fields contained within Statistics(0) have the same values as the fields in Statistics(1), I want to do something.
How can I compare these objects to one-another?
what I have tried
For Each Stat As ExportStatistics In Statistics
Insert_VehicleStats(Stat) 'Insert values into main Vehicle table
If Statistics.Length > 1 Then
Dim i = 0
Dim y = 0
Dim previousObject As ExportStatistics
For Each Stat2 In Statistics
If Stat2.Equals(previousObject) Then
Dim sadXml = "do something"
End If
previousObject = Stat2
Next
End if
You can either override the Equals function in your class or overload it then create your own comparison code. Here's an example (it assumes your object is of type VehicleStatisticsItem):
Public Class VehicleStatisticsItem
Public Overrides Function Equals(obj As Object) As Boolean
'Test for type and define tests or just pass to base function
Return MyBase.Equals(obj)
End Function
Public Overloads Function Equals(item As VehicleStatisticsItem) As Boolean
'Define your tests here
End Function
End Class
The 2 objects you are comparing will not equal each other even if their properties do. You need to specify that you are comparing their properties.
Change
If vehicleStatistic.Equals(previousObject) Then
Dim sadXml = "do something"
End If
To
If vehicleStatistic.GetType().GetProperties.Equals(previousObject.GetType().GetProperties) Then
Dim sadXml = "do something"
End If
This way you are comparing their properties.
The text file contains lines with the year followed by population like:
2016, 322690000
2015, 320220000
etc.
I separated the lines substrings to get all the years in a list box, and all the population amounts in a separate listbox, using the following code:
Dim strYearPop As String
Dim intYear As Integer
Dim intPop As Integer
strYearPop = popFile.ReadLine()
intYear = CInt(strYearPop.Substring(0, 4))
intPop = CInt(strYearPop.Substring(5))
lstYear.Items.Add(intYear)
lstPop.Items.Add(intPop)
Now I want to add the population amounts together, using the .Items to act as an array.
Dim intPop1 As Integer
intPop1 = lstPop.Items(0) + lstPop.Items(1)
But I get an error on lstPop.Items(1) and any item other than lstPop.Items(0), due to out of range. I understand the concept of out of range, but I thought that I create an index of several items (about 117 lines in the file, so the items indices should go up to 116) when I populated the list box.
How do i populate the list box in a way that creates an index of list box items (similar to an array)?
[I will treat this as an XY problem - please consider reading that after reading this answer.]
What you are missing is the separation of the data from the presentation of the data.
It is not a good idea to use controls to store data: they are meant to show the underlying data.
You could use two arrays for the data, one for the year and one for the population count, or you could use a Class which has properties of the year and the count. The latter is more sensible, as it ties the year and count together in one entity. You can then have a List of that Class to make a collection of the data, like this:
Option Infer On
Option Strict On
Imports System.IO
Public Class Form1
Public Class PopulationDatum
Property Year As Integer
Property Count As Integer
End Class
Function GetData(srcFile As String) As List(Of PopulationDatum)
Dim data As New List(Of PopulationDatum)
Using sr As New StreamReader(srcFile)
While Not sr.EndOfStream
Dim thisLine = sr.ReadLine
Dim parts = thisLine.Split(","c)
If parts.Count = 2 Then
data.Add(New PopulationDatum With {.Year = CInt(parts(0).Trim()), .Count = CInt(parts(1).Trim)})
End If
End While
End Using
Return data
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim srcFile = "C:\temp\PopulationData.txt"
Dim popData = GetData(srcFile)
Dim popTotal = 0
For Each p In popData
lstYear.Items.Add(p.Year)
lstPop.Items.Add(p.Count)
popTotal = popTotal + p.Count
Next
' popTotal now has the value of the sum of the populations
End Sub
End Class
If using a List(Of T) is too much, then just use the idea of separating the data from the user interface. It makes processing the data much simpler.
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
I am using VS 2012.
I can't figure out how to dynamically declare variables while my code is running. I am trying to write a program that pulls data for EMS dispatches off a website. The website updates every several second, and each call that is posted has a unique ID number. I want to use the unique ID number from each dispatch, and declare a new incident variable using that unique id number and add it to a collection of active dispatches. The only part I cant figure out, is how do I declare a variable for each dispatch as it is posted, and name it with its unique ID number?
something like:
Structure Incident
Dim id As Integer
Dim numer As String
Dim date1 As String
Dim time As String
Dim box As String
Dim type As String
Dim street As String
Dim crosstreet As String
Dim location As String
Dim announced As Boolean
Dim complete As Boolean
End Structure
UniqueIDstringfromwebsite = "1234"
Dim (code to get variable declared with unique ID as variable name) as incident
This is my first post, and I cant quite get the codesample to work quite right in the post.
This is my first answer - so you are in good company!
Do you not need to implement a class instead of a structure - that way you can implement a constructor. I don't think you want to create a variable with the ID as it's name, but you should add the ID to the dispatches collection (this is could be a list(of Incident):
e.g.
Public Class Incident
'define private variables
Private _id as integer
Private _number as string
Private _date1 as String
Private _time as String
'etc...
'define public properties
Public Property Number as string
Get
Return _number
End Get
Set (value as string)
_number = value
End Set
End Property
'Repeat for each Public Property
'Implement Constuctor that takes ID
Public Sub New(id as integer)
_id = id
'code here to get incident properties based on id
End Sub
End Class
Hope this helps!
You can use a Dictionary to identify your var:
Dim myVar As New Dictionary(Of Integer, Incident)
UniqueIDstringfromwebsite = 1234
myVar.Add(UniqueIDstringfromwebsite, New Incident)
I don't think you can change the name of a variable with a value dinamically, and sincerily, and don't get when it can be useful.
And, in this way, better turn your structure into a class.
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.