List with different object types? - vb.net

Can I have a List containing one string and two numbers? Or I can only have one type of element?

If that's the kind of functionality you want, then I would look at the non-generic System.Collections.ArrayList class.
Update
For those of you who aren't going to read the huge comment chain...it looks like Adam Robinson is on to something using List<object> over ArrayList. Both will work but on large collections it seems like List<object> is measurably faster than ArrayList.

You can. A list of Objects can do that. But, you lose type safety with that and also design time intelliSense.
What do you want to do? You could also use a class with 3 members.

No, containers like List(Of T) store exactly one type T of elements. You can, though, make this one type consist of one string and two numbers.
Structure Foo
Public Desc As String
Public x As Integer, y As Integer
End Structure
Dim List = New List(Of Foo)

Yes, you can.
dim myVehicles as new list(of object)
dim myCar as new car
dim myBike as new bike
dim mySecondCar as new car
myVehicles.add(myCar)
myVehicles.add(myBike)
myVehicles.add(mySecondCar)

Related

Using IEnumerable(Of String) to read from different kinds of data sources

I am using the below variable to store a list of user ID strings. I then use the list to search for each user using an LDAP query.
Dim userIds As IEnumerable(Of String) =
{"testid1", "testid2", "testid3", "testid4", "testid5"}
That works, but the ID's are hard-coded. How do I make it read the ID's from a ListBox control instead? Would it be something like:
Dim userIds As IEnumerable(Of String) = ListBox1???
I would like to use the ListBox because I will plan to load the ListBox with a bunch of ID's from a text file.
Better yet, is it possible to use a TextBox? If it was a TextBox, I could just copy and paste the ID's that I need to query.
The contents of a ListBox control can be accessed using the ListBox.Items property. It returns a ListBox.ObjectCollection object, which implements IList, ICollection, and IEnumerable.
This is assuming you've added the contents programmatically, rather than binding to a DataSource. If you bound to a DataSource, as LarsTech suggests, you should use ListBox.DataSource.
If you wanted to use a TextBox control, you'd have to manually delimit each ID somehow. You could do this by putting only one ID per line, and then use the Split method to get each ID:
Dim ids as String() = myTextBox.Text.Split(new String() { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
I was going to write this as a comment, but it got a bit long and started involving code examples so I figured it would be better to post it as an answer, even though there is already an accepted answer which is very good.
Since I'm the one who originally gave you the code that used IEnumerable I feel like I should explain why I used it... IEnumerable is the lowest level interface that is implemented by all lists, collections, dictionaries, arrays, etc. Basically, anything that stores multiple data which can be looped through by a For Each loop, implements the IEnumerable interface. In fact, essentially the only thing that the IEnumerable interface supports is the ability to enumerate through its contents with a For Each loop. IEnumerable is just an interface, it's not a concrete object type. Therefore, when you create an IEnumerable variable, that means that variable can be used to reference (i.e. point to) any object that implements that interface (i.e. any object that can be enumerated with a For Each loop.
Therefore, in the following line, it's not creating an IEnumerable type of object. Or at least not in the concrete type sense. It's creating a specific type of object which happens to implement the IEnumerable interface and then sets the ids variable to point to it.
Dim userIds As IEnumerable(Of String) = {"1", "2", "3"}
The phrase {"1", "2", "3"} is actually a literal expression to represent an array of strings. In other words, that literal expression is the equivalent of doing the following:
Dim stringArray(2) As String
stringArray(0) = "1"
stringArray(1) = "2"
stringArray(2) = "3"
So, since the object containing the list of ID's is actually an array of strings, it could have been done like this:
Dim userIds() As String = {"1", "2", "3"}
However, since I wanted the code to work, regardless of the data source, I used the more general IEnumerable interface. Since the only thing that I actually required of the data was that it could be enumerated with a For Each loop, I didn't want to limit the flexibility of the code by requiring the input list of ID's to be of some higher-level specific object type. I didn't really care that the ID's were specifically an array, or a list, or a dictionary, or a collection. As long as it was something that I could loop through, that's all I cared about. By doing so, that made the code more flexible so that you could set the variable to any enumerable data source, such as the Items property of the ListBox. For instance, all of the following would have worked, without changing the rest of the code:
Dim userIds As IEnumerable(Of String) = {"1", "2", "3"}
Or
Dim userIdsArray() As String = {"1", "2", "3"}
Dim userIds As IEnumerable(Of String) = userIdsArray
Or
Dim userIdsArray(2) As String
userIdsArray(0) = "1"
userIdsArray(1) = "2"
userIdsArray(2) = "3"
Dim userIds As IEnumerable(Of String) = userIdsArray
Or
Dim userIds As IEnumerable(Of String) = ListBox1.Items.OfType(Of String)()
Or
Dim userIds As IEnumerable(Of String) = File.ReadAllLines("IDs.txt")
Or
Dim userIds As IEnumerable(Of String) = TextBox1.Text.Split({Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
Etc.
Since all of those above data sources implement the IEnumerable interface, the same userIds variable can be used to reference all of them.

How to Save/Reload data in vb.net after .exe close?

I am new to vb.net, and this is my first project where I'm fairly certain there is an obvious answer that I just can't find.
Problem: I have a list of a structure I have defined with many properties. I want to be able to edit and load that list with the values I have saved to it before hand after closing the program and loading it backup. What is the best way to do this?
This isn't a simple string or bool, otherwise I would use the user settings that is commonly suggested, in the project's properties. I've seen others that save it into an xml and take it back up, but I'm not inclined to do so since this is going to be distributed to others in mass. Since it's a complex structure, what's the commonly held preferred method?
Example
Here's a structure:
Structure animal
Dim coloring as string
Dim vaccinesUpToDate as Boolean
Dim species as string
Dim age as integer
End structure
And there's a List(Of animal) that the user will add say 1 cat, 2 dogs, etc. I want it so that once the programs is closed after the user has added these, that structure will be saved to still have that 1 cat and 2 dogs with those settings so I can display them again. What's the best way to save the data in my program?
Thanks!
Consider serialization. For this, a class is more in order than an old fashioned Struct:
<Serializable>
Class Animal
Public Property Name As String
Public Property Coloring As String
Public Property VaccinesUpToDate As Boolean
Public Property Species As String
Public Property DateOfBirth As DateTime
Public ReadOnly Property Age As Integer
Get
If DateOfBirth <> DateTime.MinValue Then
Return (DateTime.Now.Year - DateOfBirth.Year)
Else
Return 0 ' unknown
End If
End Get
End Property
' many serializers require a simple CTor
Public Sub New()
End Sub
Public Overrides Function ToString() As String
Return String.Format("{0} ({1}, {2})", Name, Species, Age)
End Function
End Class
The ToString() override can be important. It is what will display if you add Animal objects to a ListBox e.g.: "Stripe (Gremlin, 27)"
Friend animalList As New List(of Animal) ' a place to store animals
' create an animal
a = New Animal
a.Coloring = "Orange"
a.Species = "Feline" ' should be an Enum maybe
a.Name = "Ziggy"
a.BirthDate = #2/11/2010#
animalList.Add(a)
' animalList(0) is now the Ziggy record. add as many as you like.
In more complex apps, you might write an Animals collection class. In that case, the List might be internal and the collection could save/load the list.
Friend Sub SaveData(fileName as String)
Using fs As New System.IO.FileStream(fileName,
IO.FileMode.OpenOrCreate)
Dim bf As New BinaryFormatter
bf.Serialize(fs, animalList)
End Using
End Sub
Friend Function LoadData(fileName as String) As List(Of Animal)
Dim a As List(of Animal)
Using fs As New FileStream(fileName, FileMode.Open, FileAccess.Read)
Dim bf As New BinaryFormatter
a = CType(bf.Deserialize(fs), List(Of Animal))
End Using
Return a
End Function
XMLSerialization, ProtoBuf and even json are much the same syntax. For a small amount of data, a serialized list is an easy alternative to a database (and have many, many other uses, like a better Settings approach).
Calculated Fields as Properties
Notice that I added a BirthDate property and changed Age to calculate the result. You should not save anything which can be easily calculated: in order to update the Age (or VaccinesUpToDate) you'd have to 'visit' each record, perform a calculation then save the result - which might be wrong in 24 hours.
The reason for exposing Age as a Property (rather than a function) is for data binding. It is very common to use a List<T> as the DataSource:
animalsDGV.DataSource = myAnimals
The result will be a row for each animal with each Property as a column. Fields as in the original Structure won't show up. Nor would an Age() function display, wrapping the result as a readonly property displays it. In a PropertyGrid, it will show disabled because it is RO.
Class versus Structure
So if a Structure using Properties will work, why use a Class instead? From Choosing Between Class and Struct on MSDN, avoid using a Structure unless the type meets all of the following:
It logically represents a single value, similar to primitive types (int, double, etc.)
It has an instance size under 16 bytes
It is immutable
It will not have to be boxed frequently
Animal fails the first 3 points (while it is a local item it is not a value for #1). It may also fail the last depending on how it is used.

Dictionary containing an array of strings as value

Is it possible to have a string array as the value in a dictionary? I need to save the description (Hour2) as the key and as value being able to access both the price (elements_PR(4)) and the time offset (1). Is there a good way to do that?
Dim pr_val(2) As String
Dim PR As New Dictionary(Of String, pr_val)
PR.Add("Hour2", {elements_PR(4), "1"})
There is no reason why you can't do it - you could also use values of type Tuple(Of String, String), if the number of elements is fixed in advance - in this case, 2. It'd be easier to perform comparisons on, and would also be immutable, which is often a good idea.
You sure can. Try something like this:
Dim PR As New Dictionary(Of String, IEnumerable(Of String))
PR.Add("Hour2", {elements_PR(4), "1"})
It seems to me that you could create a class representing the price and the time offset. Therefore, you could do something like PR.Add("Hour2", instanceOfClass).
Depending on the meaning of the description in your situation, you could even include it in your class. It would allow you to use another approach with a List(Of YourClass) containing a list of items with the description, the price and the time offset. To retrieve an item by "key", you could then use some Linq.
Short answer - yes. Assuming the following was declared somewhere:
Dim elements_PR(4) As String : elements_PR(4) = "Hello"
Dim PR As New Dictionary(Of String, String())
You can either do:
PR.Add("Hour2", {elements_PR(4), "1"})
or
Dim pr_val() As String = {elements_PR(4), "1"}
PR.Add("Hour1", pr_val)

Don't use ArrayList!

People often tell me not to use ArrayList for making my arrays in VB.NET.
I would like to hear opinions about that, why shouldn't I? What is the best method for creating and manipulating array contents, dimensions etc?
Thanks.
Use generic lists instead. ArrayList is not typed, meaning that you can have a list with strings, numbers, +++. Rather you should use a generic list like this:
Dim list1 As New List(Of String) ' This beeing a list of string
The lists-class also allows you to expand the list on the fly, however, it also enforces typing which helps write cleaner code (you don't have to typecast) and code that is less prone to bugs.
ArrayList is gennerally speaking just a List(Of Object).
ArrayLists are not type checked so you will need to do a lot of boxing/unboxing. Use a .net collection instead that support generics like List.
Because List does not have to unbox your objects it boasts a surprisingly better performance than Arraylist.
ArrayLists are less performant and memory-extensive:
Dim list1 As New ArrayList
For i As Integer = 1 To 100000000
list1.Add(i)
Next
' --> OutOfMemoryException after 13.163 seconds, having added 67.108.864 items
Dim list2 As New List(Of Integer)
For i As Integer = 1 To 100000000
list2.Add(i)
Next
' --> finished after 1.778 seconds, having added all values
Because its not strongly typed. Use a List(Of T) which T is your type.

Iterate through generic list of unknown type at runtime in VB.Net

Does anyone know how to iterate over a generic list if the type of that list isn't known until runtime?
For example, assume obj1 is passed into a function as an Object:
Dim t As Type = obj1.GetType
If t.IsGenericType Then
Dim typeParameters() As Type = t.GetGenericArguments()
Dim typeParam As Type = typeParameters(0)
End If
If obj is passed as a List(Of String) then using the above I can determine that a generic list (t) was passed and that it's of type String (typeParam). I know I am making a big assumption that there is only one generic parameter, but that's fine for this simple example.
What I'd like to know is, based on the above, how do I do something like this:
For Each item As typeParam In obj1
'do something with it here
Next
Or even something as simple as getting obj1.Count().
The method that iterates over your list can specify a generic type:
Public Sub Foo(Of T)(list As List(Of T))
For Each obj As T In list
..do something with obj..
Next
End Sub
So then you can call:
Dim list As New List(Of String)
Foo(Of String)(list)
This method makes the code look a little hairy, at least in VB.NET.
The same thing can be accomplished if you have the objects that are in the list implement a specific interface. That way you can populate the list with any object type as long as they implement the interface, the iteration method would only work on the common values between the object types.
If you know that obj is a Generic List. Then you're in luck.
Generic List implements IList and IEnumerable (both are non-generic). So you could cast to either of those interfaces and then For Each over them.
IList has a count property.
IList also has a Cast method. If you don't know the type to cast to, use object. This will give you an IEnumerable(Of object) that you can then start using Linq against.