Difference between two Lamba Expressions in LINQ - vb.net

I am trying to understand the difference between these two Lambda Expressions within LINQ Join method.
The two lines I do not understand are those starting with Function(aaa) ... and Function(bbb) ....
Why do I have explicitly name the field Name in the second example for both aaa and bbbwhile in the first one it works without it?
On the other hand in the first example bbb.Owner points to Owner field of Pet but it does not tell that Name field of the Person shall be used to make the join.
First example (comes from .Net Framework documentation with variable names changed):
Structure Person
Public Name As String
Public SecondName As String
End Structure
Structure Pet
Public Name As String
Public Owner As Person
End Structure
Dim magnus As New Person With {.Name = "Hedlund, Magnus"}
Dim terry As New Person With {.Name = "Adams, Terry"}
Dim charlotte As New Person With {.Name = "Weiss, Charlotte"}
Dim barley As New Pet With {.Name = "Barley", .Owner = terry}
Dim boots As New Pet With {.Name = "Boots", .Owner = terry}
Dim whiskers As New Pet With {.Name = "Whiskers", .Owner = charlotte}
Dim daisy As New Pet With {.Name = "Daisy", .Owner = magnus}
Dim people As New List(Of Person)(New Person() {magnus, terry, charlotte})
Dim pets As New List(Of Pet)(New Pet() {barley, boots, whiskers, daisy})
Dim query = people.Join(pets,
Function(aaa) aaa,
Function(bbb) bbb.Owner,
Function(ccc, ddd) _
New With {.OwnerName1 = ccc.Name, .Pet1 = ddd.Name})
Second example (my code)
Structure MyObject
Public Name As String
Public Value As Integer
End Structure
Dim Test1 As New List(Of MyObject) From {
New MyObject With {.Name = "a", .Value = 1},
New MyObject With {.Name = "b", .Value = 2},
New MyObject With {.Name = "c", .Value = 3}
}
Dim Test2 As New List(Of MyObject) From {
New MyObject With {.Name = "a", .Value = 11},
New MyObject With {.Name = "b", .Value = 22},
New MyObject With {.Name = "c", .Value = 33}
}
Dim Joined = Test1.Join(Test2,
Function(aaa) aaa.Name,
Function(bbb) bbb.Name,
Function(ccc, ddd) New With {
.Name1 = ccc.Name,
.Value1 = ccc.Value,
.Value2 = ddd.Value})

IF you look at the documentation, you'll see that the first function is the selector for the outer sequence and the second is the selector for the inner sequence.
The value form the inner and outer selectors must have the same type (TKey) and will be compared using the default comparer for that type (there's an overload that allows you to pass a comparer).
The third function is the result selector that receives a TOuter and a TInner instances that match according to the selected keys and returns a TResult value.
So, in the first example TKey is Person. outerSelector and innerSelector must return a Person, thus aaa and bbb.
On the second example you chose to compare the Name field and not the entire MyObject structure.

Related

List(OF T).Contains Problem (Search through String or Integer Props of the Class)

List(Of T).Contains function help.
Here is my simple object.
Public Class Person
Name as string
Age as Integer
End Class
In the Sub:
Dim Person as new Person
Person.Name = "John"
Person.Age = 32
Dim myPersonList as new List(of Person)
myPersonList.Add(Person)
How would I exactly look up if the List Contains a person named "John" with the myPersonList.Contains() function, Or how would i look up myPersonListContains(AGE)???. I can't neccessarily put "John" or an Age because the Contains function is asking for the Person Class, and not the string "John". I'd like to use this method instead of writing a function that iterates through every Persons "Name"
I used the Find method of List(Of T). I made your Name and Age in the Person class real Properties.
Public Class Person
Public Property Name As String
Public Property Age As Integer
End Class
Private Sub OPCode()
Dim myPersonList As New List(Of Person)
Dim p1 As New Person
p1.Name = "John"
p1.Age = 32
myPersonList.Add(p1)
Dim p2 As New Person
p2.Name = "Mathew"
p2.Age = 73
myPersonList.Add(p2)
Dim p3 As New Person
p3.Name = "Mark"
p3.Age = 90
myPersonList.Add(p3)
Dim foundInList = myPersonList.Find(Function(x) x.Name = "John")
If foundInList Is Nothing Then
MessageBox.Show("John not found")
Else
Dim JohnsAgeIs = foundInList.Age
Debug.Print(JohnsAgeIs.ToString)
'32 appears in Immediate window
End If
End Sub
Corrected per comment by #jmcilhinney. Without checking first if a person was returned by .Find a NRE could occur.
Unless your item type implements IEquatable(Of T), Contains will only check for reference equality. If you do implement IEquatable(Of Person) in the Person class, that implementation will be specific and unchanging, so you still can't compare in different ways at different times. What you should do is use the LINQ Any method instead, e.g.
If myPersonList.Any(Function(p) p.Name = "John") Then
'...
End If
If myPersonList.Any(Function(p) p.Age = 32) Then
'...
End If

Can I force an object to be passed as "ByVal" (as a copy) in a call in .NET?

In the VB.NET code below I want to add three persons to a list of persons. I thought that I could use the same person-object over and over again and simply change it's name before next Add-call. I was hoping that each call should copy the object and add that copy to the list... But I end up with a list of three "Kim". It seems that the list elements refers to the same object.
Class Person
Public Name As String
End Class
Sub Foo()
Dim Persons as New List(Of Person)
Dim aPerson As New Person
aPerson.Name = "John"
Persons.Add(aPerson)
aPerson.Name = "Emma"
Persons.Add(aPerson)
aPerson.Name = "Kim"
Persons.Add(aPerson)
End Sub
Can I force that aPerson is copied in each Add-call? Or do I need to create separate objects for John, Emma and Kim?
What you are doing isn't good practice as you shouldn't use a class that way.
If your names come from a source like a list you could use a For Each loop.
Dim MyNames As New List(Of String)
MyNames.Add("John")
MyNames.Add("Emma")
MyNames.Add("Kim")
For Each person_name As String In MyNames
Dim aPerson As New Person
aPerson.Name = person_name
Persons.Add(aPerson)
Next
This approach could be very practical if the data comes from a file, SQL request or JSON for example.
Otherwise you would just have:
Dim Persons as New List(Of Person)
Dim aPerson As New Person
Dim bPerson As New Person
Dim cPerson As New Person
aPerson.Name = "John"
Persons.Add(aPerson)
bPerson.Name = "Emma"
Persons.Add(bPerson)
cPerson.Name = "Kim"
Persons.Add(cPerson)

Need To Return GroupBy Data With Counts But Selecting Multiple Fields Using LINQ

Okay, I'm sure this is simple for someone but it is starting to cause me to tear my hear out so I apologise in advance if this seems like a simple question:
I have the following class:
Public Class Ticket
Public Property ticket_id As String
Public Property ticket_assignee As String
Public Property ticket_email As String
End Class
I am trying to use LINQ to group my list by the ticket_email field but allow me to get the ticket_assignee field and count of each group.
So for example:
For each email address, return me the assignee name and how many tickets in that email group like below:
'fred#someplace.com' = Fred, 4
'joe#someplace.com' = Joe, 2
'bob#someplace.com' = Bob, 8
etc...
I am still trying to get my head around LINQ and would love to understand it more. Any help would be appreciated thankyou.
A Query Syntax Solution:
Sub Main
Dim q = from entry in Ticket.GetTickets
group entry by entry.ticket_assignee Into NewGroup = Group, Count()
select new with{
.Assignee = NewGroup.Select(function(x) x.ticket_assignee).First,
.Email = NewGroup.Select(function(x) x.ticket_email).First,
.Count = Count}
'q.dump()
End Sub
' Define other methods and classes here
Public Class Ticket
Public Property ticket_id As Integer
Public Property ticket_assignee As String
Public Property ticket_email As String
Public Shared Function GetTickets as list(of Ticket)
Return New List(of Ticket) From {New Ticket With {.ticket_id = 1, .ticket_assignee = "Fred", .ticket_email = "fred#ticket.de"},
New Ticket With {.ticket_id = 2, .ticket_assignee = "Fred", .ticket_email = "fred#ticket.de"},
New Ticket With {.ticket_id = 3, .ticket_assignee = "Fred", .ticket_email = "fred#ticket.de"},
New Ticket With {.ticket_id = 4, .ticket_assignee = "Anna", .ticket_email = "Anna#ticket.de"},
New Ticket With {.ticket_id = 5, .ticket_assignee = "Anna", .ticket_email = "Anna#ticket.de"},
New Ticket With {.ticket_id = 6, .ticket_assignee = "Jack", .ticket_email = "Jack#ticket.de"}}
End Function
End Class
Gives you:
For learning Linq I highly recommend LinqPad. You can choose VB.NET Program, copy my code, uncomment the .dump() line and run it.
Dim tickets As New List(Of Ticket)
For i = 1 To 3
tickets.Add(New Ticket With {.ticket_id = i.ToString, .ticket_email = "a", .ticket_assignee = "me"})
Next
For i = 4 To 6
tickets.Add(New Ticket With {.ticket_id = i.ToString, .ticket_email = "b", .ticket_assignee = "me2"})
Next
Dim group = tickets.GroupBy(Function(x) x.ticket_assignee).Select(Function(y) New Tuple(Of String, Integer)(y.First.ticket_assignee, y.Count))

ObjectListView adding items to it

I am a bit lost and I would like to add some items to a Fast ObjectListView. what I have is not working, and I cant seem to find anything online with vb.net samples
Dim LvItm As BrightIdeasSoftware.OLVListItem = lstMain.Items.Add("title")
With LvItm
.SubItems.Add("name")
.SubItems.Add("last")
.SubItems.Add("phone")
.SubItems.Add("address")
.EnsureVisible()
End With
ObjectListView works completely different from normal ListView, usually you dont add individual items.
In short:
- create columns
- set aspect names of created columns to property names of your objects
- point objectlistview to list of objects
See example below:
Imports BrightIdeasSoftware
Public Class Person
Public Property name As String
Public Property last As String
Public Property phone As String
Public Property address As String
End Class
Dim LvItm As New Person With {.name = "John",
.last = "Smith",
.phone = "555-69997-44",
.address = "Main Str. 1"}
Dim LvLst As New List(Of Person)
LvLst.Add(LvItm)
ObjectListView1.View = View.Details
ObjectListView1.Columns.Add(New OLVColumn With {.Text = "Name",
.AspectName = "name"})
ObjectListView1.Columns.Add(New OLVColumn With {.Text = "Last Name",
.AspectName = "last"})
ObjectListView1.Columns.Add(New OLVColumn With {.Text = "Phone",
.AspectName = "phone"})
ObjectListView1.Columns.Add(New OLVColumn With {.Text = "Address",
.AspectName = "address"})
ObjectListView1.SetObjects(LvLst)
With everything set-up you can add items to the list or manipulate in any way,
hitting ObjectListView1.SetObjects(LvLst) again to refresh view.
You can also add items to ObjectListView directly:
Dim p As New Person
p.name = "Steve"
p.last = "Wilson"
p.phone = "777-888-9987"
p.address = "First Str. 1"
ObjectListView1.AddObject(p)
Remember that items added directly were not added to your List(Of Person)

Add object to listbox and use different string representations depending on the listbox?

I have a coupple of listboxes with the same object type in all of them, the problem i have is that i dont want the object displayed with the same ToString() method in all of the listboxes. Is there a way to solve this?
At the moment im adding strings to the listboxes and then i use the selected string to search a list of objects for the correct one but i dont like that solution at all.
Suppose to have a class for Employees like this one:
Public Class Employee
Public Property ID As Integer
Public Property FirstText As String
Public Property SecondText As String
' and go on with other properties
....
End Class
Now when you populate your listboxes, you set the DisplayMember and ValueMember of your listboxes to two different property of Employee
Dim myList As ArrayList = New ArrayList()
myList.Add(New Employee() With {.ID = 1, .FirstText = "John Doe", .SecondText = "Doe John"})
myList.Add(New Employee() With {.ID = 2, .FirstText = "Mark Ross", .SecondText = "Ross Mark"})
ListBox1.DataSource = myList
ListBox2.DataSource = myList
ListBox1.ValueMember = "ID"
ListBox1.DisplayMember = "FirstText"
ListBox2.ValueMember = "ID"
ListBox2.DisplayMember = "SecondText"