.NET: Quick way of using a List(Of String) as the Values for a new Key/Value collection? - vb.net

I have a List(Of String) object and need to create a new collection based on the strings' values. The new collection will be of a custom class with two string fields - call them Key and Value (but it's not the built-in KeyValue class, it's a custom class).
All the values of Key will be the same, it's just Value that I want to source from the string list. Example:
Dim slValues = New List(Of String)({"Cod", "Halibut", "Herring"})
Dim myList = New List(Of myClass)( ... amazing initialisation line here? )
Class myClass
Public Key As String ' This will always be "Fish"
Public Value As String ' This will be the fish name.
End Class
(Note I don't actually have access to the class I'm using, so can't just change it to Public Key As String = "Fish" as a default. Key must be set at runtime.)
I can of course just do a loop and do it manually, but I'm wondering if there's a whiz-bang way to achieve this as part of the initialisation line?

How about this
Dim myList = slValues.ConvertAll(Of myClass)( _
Function(s) New YourClass With {.Key = "Fish", .Value = s})

Here's an alternative to Jodrell's answer using Linq's Select function:
Dim myList = slValues.Select(Function(fish) New CustomClass With {.Key = "Fish", .Value=fish}).ToList()

from x in Enumerable.Range(1, slValues.count-1).ToArray
select new KeyValuePair(of string,myClass) with {.Key=slValues(x),.Value=myList(x)}
hope it helps.

Related

Retrieve list that is saved in a datatable

I created a datatable containing the list of notes for songs:
Private Table As New DataTable
Public Sub New()
With Table
.Columns.Add("Name")
.Columns.Add("NoteList")
.Rows.Add("GOT", GOT)
.Rows.Add("Yesterday", Yesterday)
End With
End Sub
GOT and Yesterday are lists of notes (notes is a class containing note, duration etc..)
On the form I then assign the datatable to a combobox:
ComboSongs.DisplayMember = Songs.DataTable.Columns(0).ColumnName
ComboSongs.ValueMember = Songs.DataTable.Columns(1).ColumnName
ComboSongs.DataSource = Songs.DataTable
I try to get the list of notes like this:
Dim songToPlay As List(Of Note) = CType(ComboSongs.SelectedValue, List(Of Note))
When I try to get the list I get the error:
System.InvalidCastException: 'Unable to cast object of type 'System.String' to type 'System.Collections.Generic.List`1[test.Note]'.'
Now I am unsure where I am getting it wrong. What would be the correct way to do this?
Your ValueMember is what is returned through the ComboBox.SelectedValue. So since you set the ValueMember like this
ComboSongs.ValueMember = Songs.DataTable.Columns(1).ColumnName
you only get the ColumnName. I assume that's a string and, well, the error message tells you it is one
... Unable to cast object of type 'System.String' ...
I guess that should be "NoteList", since that would be returned by Songs.DataTable.Columns(1).ColumnName
But this all doesn't make much sense, as I guess you are selecting a song there, either "Yesterday" or "GOT". At the point you're at it's so convoluted to return the DataTable rows, and index them. You will need to find the row by name and that is just too complicated when you could just create a class with strong names. I'll give you a class based solution but I'm not sure if you can make that change.
Private Class Song
Public Property Name As String
Public Property NoteList As List(Of Note)
End Class
Private Class Note
' your implementation
End Class
Dim songs As New List(Of Song)()
songs.Add(New Song() With {.Name = "GOT", .NoteList = New List(Of Note)})
songs.Add(New Song() With {.Name = "Yesterday", .NoteList = New List(Of Note)})
' need to populate those NoteLists first
ComboSongs.DisplayMember = "Name"
ComboSongs.DataSource = songs
Dim songToPlay = songs.SingleOrDefault(Function(s) s.Name = ComboSongs.SelectedValue)
Dim noteList = songToPlay.NoteList

vb.net create objects in for each loop from partial info in Ienumerable

Summary:I want to create objects in a for each loop.
I have a class - Dashboard, which has some properties - length, height, etc.
This info is contained within an XML document, but my class' properties is only a small subset of the information in the XML.
I have created a Collection of XElement, and I can iterate over these, but how do I create my object on each iteration of the loop?
Dim Col_DashBoards As IEnumerable(Of XElement) = From XDashboard In XDocument.Load(filename).Descendants("dashboard")
For Each XDashboard In Col_DashBoards
'PseudoCode
Dim Xdashboard.Name as New DashboardClassObject
Xdashboard.Name.Height = XDashboard.Element("Height").value
...
Next
If I have understood your question correctly, you wish to create a new object based on a subset of data from within an XML document?
The below is a function that will generate a new DashboardClassObject for every matching node and populate that object. A list of type DashboardclassObject is returned.
Public Function GenerateDashBoardFromXML(filename as string) As List(Of DashboardClassObject)
Dim dashboardList As List(Of DashboardClassObject) = New List(Of DashboardClassObject)()
Dim Col_DashBoards As IEnumerable(Of XElement) = From XDashboard In XDocument.Load(filename)?.Descendants("dashboard")
For Each XDashboard In Col_DashBoards
Dim dashboard As DashboardClassObject = New DashboardClassObject () With {
.Name = XDashboard.Element("Height")?.value
}
dashboardList.Add(dashboard)
Next
return dashboardList
End Function
It should be noted that Null checking is used here. The below is only populated if the matching element is found. Null coalescing operator is also an option here.
.Name = XDashboard.Element("Height")?.value

Variables refer to the same instance

Within my learning curve I play around with converting List and IEnumerable between each other.
What I am surprised with is that after executing EditMyList procedure MyIEnumerable contains the same data for each DBTable object as MyList. However I have modified MyList only, without assigning it to MyIEnumerable once List has been modified.
Can you explain what happened here and why MyList and MyEInumerable refer to the same instance?
Public Class DBTable
Public Property TableName As String
Public Property NumberOfRows As Integer
End Class
Public Sub EditMyList
Dim MyList As New List(Of DBTable)
MyList.Add(New DBTable With {.TableName = "A", .NumberOfRows = 1})
MyList.Add(New DBTable With {.TableName = "B", .NumberOfRows = 2})
MyList.Add(New DBTable With {.TableName = "C", .NumberOfRows = 3})
Dim MyIEnumerable As IEnumerable(Of DBTable) = MyList
For Each item In MyList
item.NumberOfRows += 10
Next
End Sub
UPDATE: string case where at the end b is not equal to a. String is also reference type, so assigning one variable to other one we shall copy just reference. However at the end there is different result than in the first example (explained by #Sefe)
Dim a As String
Dim b As String
a = "aaa"
b = "bbb"
a = b
' At this point a and b have the same value of "bbb"
a = "xxx"
' At this point I would expect a and b equal to "xxx", however a="xxx" but b="bbb"
A List is a reference type. That means it is created on the heap and your MyList variable contains just a reference (sometimes incorrectly called "pointer") to the list. When you assign MyList to MyEnumerable you don't copy the whole list, you just copy the reference. That means all changes you make to the (the one) list, is reflected by all the references.
If you want a new list you need to create it. You can use the list constructor:
Dim MyIEnumerable As IEnumerable(Of DBTable) = New List(Of DBTable)(MyList)
Since you don't need a list, but an IEnumerable you can also call the list's ToArray method:
Dim MyIEnumerable As IEnumerable(Of DBTable) = MyList.ToArray
You can also use LINQ:
Dim MyIEnumerable As IEnumerable(Of DBTable) = MyList.ToList
As far as the behavior of String is concerned, strings in .net are immutable. That means once created, they can not be changed. String operations (for example concatinations) will always create new strings. In other words: the copy operation you have to do manually for your lists is done automatically for strings. That's why you see similar behavior for strings as for value types.
Also, the assignment operation in your question would also still behave the same if strings were mutable. When you assign a = "xxx", you update the reference of afrom "bbb" to "xxx". That however does not affect b, which still keeps its old reference.
Use ToList() extension method for creating another List
Dim newCollection = MyList.ToList()
But notice that instances of DBTable still will reference to the same items
For creating "full" copy you need create new instances of DBTable for every item in the collection
Dim newCollection = MyList.Select(Function(item)
return new DBTable
{
.TableName = item.TableName,
.NumberOfRows = item.NumberOfRows
}
End Function).ToList()
For Each item in MyList
item.NumberOfrows += 10 ' will not affect on the newCollection items
Next

Get data from a collection

I want to make a collection to have data available
Example:
Dim b As New Collection
colb = New Collection
b.Add("system", "1001", "SYSTEM")
b.Add("network", "1002", "NETWORKA")
b.Add("networksecond", "1010", "NETWORKB")
colb.Add(b, "list")
im looking for a function to get data from this collection:
I want to, based on the ID (Second number) get the first and third value
So if I search for 1010, I need to have the value Network and NETWORKA
VB6 called, they want their Collection back.
No, seriously, please consider using a Dictionary instead of the old, legacy Collection class. Behold the beauty of generics and strong typing:
Dim dic As New Dictionary(Of Integer, Tuple(Of String, String))
dic.Add(1001, Tuple.Create("system", "SYSTEM"))
dic.Add(1002, Tuple.Create("network", "NETWORKA"))
dic.Add(1010, Tuple.Create("networksecond", "NETWORKB"))
' Search
Dim t As Tuple(Of String, String) = Nothing
If dic.TryGetValue(1002, t) Then
Console.WriteLine(t.Item1) ' prints "network"
Console.WriteLine(t.Item2) ' prints "NETWORKA"
End If
As soon as you have more than two values, I suggest that you use a specialized class instead of a Tuple to increase readability.
Also, you can simply use List(Of T). In most cases this is enough. Dictionary is good for fast search out long list by a single key.
'declare model
Public Class NetworkModel
Public Property Id As Integer
Public Property Name1 As String
Public Property Name2 As String
End Class
' load list of models
Private _modelList As New List(Of NetworkModel)()
.......
' search using LINQ
Dim model As NetworkModel = _modelList.FirstOrDefault(Function(m) m.Id = 1001)
If model IsNot Nothing Then . . . . .

Defining the type of a List in vb.net at runtime

I'm creating a class at run time using typebuilder and after I create this class I want to define its type for a list like
dim fooList as new List(of DynamicClassName)
Since this doesn't exist at compile time of course it throws an error. When I generate this type I return the type so I can't do something like
dim newType = createNewType(foobar)
dim fooList as new List(of getType(newType))
How do I assign the type of a List at runtime?
You can create a List(Of T), but AFAIK you won't be able to cast it to a typed object. I've used the String type in the following example.
Dim list As Object = Activator.CreateInstance(GetType(List(Of )).MakeGenericType(New Type() {GetType(String)}))
Debug.WriteLine((TypeOf list Is List(Of String)).ToString())
Output
True
So in your case it would look like this:
Dim newType = createNewType(foobar)
'Creates a List(Of foobar):
Dim list As IList = Ctype(Activator.CreateInstance(GetType(List(Of )).MakeGenericType(New Type() {newType})), IList)
'Creates a BindingList(Of foobar):
Dim bindingList As IBindingList = Ctype(Activator.CreateInstance(GetType(BindingList(Of )).MakeGenericType(New Type() {newType})), IBindingList)
This does not answer your question, but may solve your problem.
Another option would be to use an ArrayList (for which you don't have to assign a type). You can see the details here: http://msdn.microsoft.com/en-us/library/system.collections.arraylist(v=vs.110).aspx.
Here is a basic example:
Dim anyArrayList as new ArrayList()
anyArrayList.add("Hello")
anyArrayList.add("Testing")