Function in VB.NET to match elements from a list (Custom class) with elements from another list (custom class) - vb.net

I have two custom classes in VB.NET and I am using them as lists of classes to contain multiple elements with these properties
One is called materialsDefinition which contains the following:
Public Property id As Integer
Public Property name As String
Public Sub New(ByVal identification As Integer, ByVal material As String)
id = identification
name = material
End Sub
A list of this class will be used as a materials dictionary, where you will have an ID, for example "1", and a material name, "A_286", or for example "2" and "17-4PH"
{"1", "A_286"}, {"2", "17-4PH"}
Another class is componentInfo, with a similar structure to the other class:
Public Property componentName As String
Public Property materialID As Integer
Public Sub New(ByVal component As String, ByVal identification As Integer)
materialID = identification
componentName = component
End Sub
Where you will have a name of the component in an assembly, for example "Pin" and its material ID "1", which corresponds to the material in the other class, A_286, and "Nut" and its material ID "2" corresponding to 17-4PH
{"Pin", "1"}, {"Nut", "2"}
I want to create a function that looks up and matches the material ID that exists in both classes, and saves the material name in a new array that will contain the following information:
{"Pin", "A_286"}, {"Nut", "17-4PH"}
I'm not sure how though, I thought the most rudimentary solution first which will be looking one by one in a If loop inside a for loop but it would be better to use a "Find" function.
The issue I have is I don't know how to use a find function, or findIndex, or Exists in VB.NET and in a list of a custom class.
Also the size of both lists can be different and the materials list be bigger, or shorter than the component list.
I would appreciate any input given.
Thanks.

Function MatchParts(materials As IEnumerable(Of materialsDefinition), components As IEnumerable(Of componentInfo) As IEnumerable(Of (String, String))
Return materials.Join(components,
Function(m) m.id,
Function(c) c.materialID ,
Function(m, c) (c.componentName, m.name) )
End Function
It's annoying to me we don't have .Join() overloads like this:
Function Join(Of TOuter, TInner, TResult)(outer As IEnumerable(Of TOuter), inner As IEnumerable(Of TInner), comparer As Predicate(Of TOuter, TInner), resultSelector As Func(Of TOuter, TInner, TResult)) As IEnumerable(Of TResult)
Function Join(Of TOuter, TInner)(outer As IEnumerable(Of TOuter), inner As IEnumerable(Of TInner), comparer As Predicate(Of TOuter, TInner)) As IEnumerable(Of (TOuter, TInner))
The latter option, especially, would allow also composing the former with a simple .Select() addition, and it's much easier to understand how to use. This question, for example, could be reduced to this:
Function MatchParts(materials As IEnumerable(Of materialsDefinition), components As IEnumerable(Of componentInfo) As IEnumerable(Of (String, String))
Return materials.Join(components, Function(m, c) m.id = c.materialId).
Select(Function(t) (t.Item2.componentName, t.Item1.name) )
End Function
And the benefit is not just in having shorter code, but also in making it easier to write and understand the first function call at all. Unfortunately, this is still a pipedream and we're stuck with the first option.

Related

extract list of string from list of custom class

i have a list(of custom class)
and i want to extract a list of all 'name' String, from it, through linq
I know how to do with a loop, but i need to get it with a linear, brief linq instruction.
i've checked this help
C# Extract list of fields from list of class
but i have problem in linq correct syntax
in particular because i would like to extract a New List(Of String)
Class Student
Sub New(ByVal NewName As String, ByVal NewAge As Integer)
Name = NewName
Age = NewAge
End Sub
Public Name As String
Public Age As Integer
End Class
Public Sub Main
Dim ClassRoom as New List(Of Student) From {New Student("Foo",33), New Student("Foo2",33), New Student("Foo3",22)}
Dim OneStudent as Student = ClassRoom(0)
Dim AllStudentsNames As New List(Of String) From {ClassRoom.Select(Function(x) x.Name <> OneStudent.Name).ToList}
End Sub
But something wrong...
Any help?
P.S. Since c# it's close to vb.Net, also c# helps are well welcome.
First, you don't need to create a new list From the one returned by the LINQ method. It's already in a new list at that point, so you can just set AllStudentsNames equal directly to what the ToList method returns.
Second, you are not selecting the name. You are selecting the result of the equality test to see if the names are different. In other words, when you say Select(Function(x) x.Name <> OneStudent.Name), that returns a list of booleans, where they true if the names are different and false if the names are the same. That's not what you want. You want the list of names, so you need to select the name.
Third, if you need to filter the list so that it only returns ones where the name is different, then you need to add a call to the Where method.
Dim AllStudentsNames As List(Of String) = ClassRoom.
Where(Function(x) x.Name <> OneStudent.Name).
Select(Function(x) x.Name).
ToList()

Lambda property value selector used in lambda expression

I sometimes find myself writing two versions of the same function that gets a count of members where one of several properties have a particular value. I've been looking at func and other examples to see if I could write a single function to do a count where a value matches one of several properties of an object. Feels like there should be a way...
Module Test
Private _students As New List(Of Student)
Sub Main()
_students.Add(New Student(1, "Stephen"))
_students.Add(New Student(2, "Jenny"))
' I'd like to replace the following lines...
Console.WriteLine(GetCountByID(1))
Console.WriteLine(GetCountByName("Stephen"))
' with a single function that could be used like below.
'Console.WriteLine(GetCountByType(1, Student.ID))
'Console.WriteLine(GetCountByType("Stephen", Student.Name))
Console.ReadLine()
End Sub
Public Function GetCountByID(ByVal id As Integer) As Integer
Return _students.Where(Function(s) s.ID = id).ToList.Count
End Function
Public Function GetCountByName(ByVal name As String) As Integer
Return _students.Where(Function(s) s.Name = name).ToList.Count
End Function
' I know this is wrong below but I'm just writing it like I'm thinking about it in my head
'Public Function GetCountByType(ByVal value As Object, selectorProperty As Func(Of Student)) As Integer
' Return _students.Where(Function(s) s.selectorProperty = value).ToList.Count
'End Function
Public Class Student
Public Property ID As Integer
Public Property Name As String
Public Sub New(ByVal id As Integer, ByVal name As String)
Me.ID = id
Me.Name = name
End Sub
End Class
End Module
You are along the right lines but your Func needed to return an object.
However it would be better to make it generic instead, the type needs to be of type IComparable so you can check for equality against the target value.
Public Function GetCountBy(Of T As IComparable)(selector As Func(Of Student, T), value As T) As Integer
Return _students.Where(Function(s) selector(s).CompareTo(value) = 0).Count()
End Function
Console.WriteLine(GetCountBy(Function(s) s.ID, 1))
Console.WriteLine(GetCountBy(Function(s) s.Name, "Stephen"))
p.s. your calls to ToList() are unnecessary
But once you have gone this far you might as well just pass in the complete predicate instead of a selector function and a value
Public Function CountWhere(predicate As Func(Of Student, Boolean))
Return _students.Where(predicate).Count()
End Function
Console.WriteLine(CountWhere(Function(s) s.ID = 1))
You can generalise this even further so it applies to any collection rather than just students, and make it an extension function if you wish
Public Function CountWhere(Of T)(coll As IEnumerable(Of T), predicate As Func(Of T, Boolean))
Return coll.Where(predicate).Count()
End Function
Console.WriteLine(CountWhere(_students, Function(s) s.ID = 1))

vb.net/Linq: how can I use a Linq using Generic classes?

I have the next structure:
public class mysample
public property status as integer
end class
Dim mylist=new List(Of mysample)
Dim item as new mysample
item.status=1
mylist.add(item)
Dim item1 as new mysample
item2.status=1
mylist.add(item1)
...
I have next function which it is calculating something:
Function test(Of T)(newstatus as integer, mylist as List(of T)) as integer
Dim res as integer = myList.Where(Function(x) x.status=newstatus).First.status
Return res
End function
The call is where I am interested to execute: test(Of mysample)(2, mylist)
I have mysample in different projects and they can not be in the same for this reason I decided to use generic list to do my Linq calcultion.
THE PROBLEM IS IN FUNCTION WHICH TELL ME STATUS IS NOT MEMBER OF T OBJECT.
How can I solve this issue? all clases has status but I have different classes and I pass the name as generic type.
Do the classes share a common base class or interface? If so you should place a filter on the generic type like this:
Function test(Of T as CommonBaseClassOrInterface)(newstatus as integer, mylist as List(of T)) as integer
That will allow you to access any members on CommonBaseClassOrInterface. If they currently don't share a base class or interface you should consider adding one, making sure that Status is a member.
If you can't give them a base class or interface for some reason, you can still do this using reflection, but I DO NOT recommend going that direction.
STATUS IS NOT MEMBER OF T OBJECT.
Yes, because you have not constraint T in any way. It would be perfectly legal to call
test(Of Integer)(2, new list(of Integer))
which would fail because Integer does not have a status property. You either need to constrain T to be of some type that has a status property (either a base class or a common interface), or don't make it generic:
Function test(newstatus as integer, mylist as List(of mystatus)) as integer
Dim res as integer = myList.Where(Function(x) x.status=newstatus).First.status
Return res
End function
I have mysample in different projects
You mean you have several classes names mystatus in several projects? Then they are not the same class.
all classes has status but I have different classes and I pass the name as generic type
The create at least an interface that has a Status property and use that to constrain the generic parameter in Test.

Passing Enums as parameters - as a dictionary(of string, enum) (VB 2012)

This might be impossible or a bit wrong headed, however in WinForms I've got combo boxes that need populating with specific options. The project uses about 10 different forms, all with similar but slightly different functionality: hence why I didn't use just one form and hide/show controls as appropriate.
Now I made a simple dictionary of options and fed the values with an Enum. Now I realise I've got duplicate code and would like to consolidate it. The option sets are date order and name order, but I've got one or two more to list.
This is what I've tried but cannot pass the dictionary into:
Public Sub BuildOrderOptions(nameorder As ComboBox, Options As Dictionary(Of String, [Enum]))
nameorder.Items.Clear()
For Each item In Options
nameorder.Items.Add(item)
Next
nameorder.DisplayMember = "Key"
nameorder.ValueMember = "Value"
End Sub
Property NameOrderOptions As New Dictionary(Of String, PersonList.orderOption) From {{"First Name", PersonList.orderOption.FirstName},
{"Last Name", PersonList.orderOption.LastName},
{"Room Number", PersonList.orderOption.RoomNumber}}
Property DateOrderOptions As New Dictionary(Of String, OrderDate) From {{"Newest First", OrderDate.NewestFirst}, {"Oldest First", OrderDate.OldestFirst}}
I've tried a few variations with Type and [enum].getnames etc but I can't pass the differing dictionary types in at all - I think I've overcomplicated the whole business by now but feel I'm missing an elegant solution. Shortly I'll either convert back to string matching alone, or just have functions per box type - evil duplication but I can move on.
Am I right in thinking there is a nicer way to do this? Unless I just define some kind of global resource for the options maybe-but globals are bad right?
Edit: Fixed thanks to Steven. In case anyone finds it useful OR better yet, anyone can critique and make nicer, here's the module code that all the forms can use to generate their options.
Public Sub BuildOrderOptions(nameorder As ComboBox, Options As IDictionary)
nameorder.Items.Clear()
For Each item In Options
nameorder.Items.Add(item)
Next
nameorder.DisplayMember = "Key"
nameorder.ValueMember = "Value"
End Sub
Property NameOrderOptions As New Dictionary(Of String, orderOption) From {{"First Name", orderOption.FirstName},
{"Last Name", orderOption.LastName},
{"Room Number", orderOption.RoomNumber}}
Property DateOrderOptions As New Dictionary(Of String, OrderDate) From {{"Newest First", OrderDate.NewestFirst}, {"Oldest First", OrderDate.OldestFirst}}
Property personStatusOptions As New Dictionary(Of String, personStatus) From {{"Active", personStatus.Active},
{"InActive", personStatus.InActive},
{"All", personStatus.All}}
Public Sub BuildComboBoxes(ListBoxes As Dictionary(Of ComboBox, IDictionary))
For Each pair In ListBoxes
BuildOrderOptions(pair.Key, pair.Value)
Next
End Sub
Public Enum OrderDate
OldestFirst
NewestFirst
End Enum
Public Enum personStatus
Active
InActive
All
End Enum
Public Enum orderOption
None
FirstName
LastName
RoomNumber
End Enum
And here's the way I've got one form using it - yes, I could have had a bunch of parameters or multiple function calls: I just like having a single object giving me a single parameter to pass on.
BuildComboBoxes( New Dictionary ( Of ComboBox , IDictionary ) From {{NameOrder, NameOrderOptions},
{DateOrder, DateOrderOptions},
{personStatus, PersonStatusOptions}})
You just need to change your method to accept any IDictionary object rather than a specific type of dictionary:
Public Sub BuildOrderOptions(nameorder As ComboBox, Options As IDictionary)
When you are using generics, such as Dictionary(Of TKey, TValue), the generic type is not really a type at all. You can think of it like a template for any number of specific types. So each time you use a generic type using different type parameters, they are entirely different and incompatible types. For instance:
' This works fine because both d1 and d2 are exactly the same type
Dim d1 As New Dictionary(Of String, String)()
Dim d2 As Dictionary(Of String, String) = d1
' This will not compile because d1 and d2 are completely different types
Dim d1 As New Dictionary(Of String, Integer)()
Dim d2 As Dictionary(Of String, Boolean) = d1
As you have found out, even if you try to use a base class as the generic type parameter, the two are still incompatible. So, even though Stream is the base class for MemoryStream, you still cannot do this:
' This will not compile because d1 and d2 are completely different types
Dim d1 As New Dictionary(Of String, MemoryStream)()
Dim d2 As Dictionary(Of String, Stream) = d1

LINQ Except using custom Comparer

I am trying to use the "Except" method on a LINQ result set using a custom implementation if IEqualityComparer to exclude certain results based on the value of a single field from the result set.
So, in simplified form I have...
'' Get collection of published sites...
Dim List1 = (From i In db.Sites _
Where (i.StatusID = published) _
Select i.SiteID, _
i.SiteName)
'' Find those with a pending site, but exclue all those whose SiteID is in List1...
Dim insComparer = New insCompare
Dim List2 = (From i In db.Sites _
Where (i.StatusID = pending) _
Select i.SiteID, _
i.SiteName).Except(List1, insComparer)
My Comparer is as follows...
Public Class insCompare
Implements System.Collections.Generic.IEqualityComparer(Of Object)
Public Function Equals1(ByVal x As Object, ByVal y As Object) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Object).Equals
Return IIf(x.SiteID = y.SiteID, True, False)
End Function
Public Function GetHashCode1(ByVal x As Object) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Object).GetHashCode
Return x.SiteID.ToString.ToLower.GetHashCode()
End Function
End Class
I get an invalid cast exception on the ".Except" line with the message "Unable to cast object of type '...insCompare' to type 'System.Collections.Generic.IEqualityComparer'"
Can anyone cast light on why this might be please.
Your problem here is that you implement IEqualityComparer(Of Object), but your lists are List(Of AT) where AT is an anonymous type, so you can't implement IEqualityComparer(Of AT).
I think your choices are:
Declare a class/struct to hold the SideID/SiteName, and select into an instance of that class, then implement IEqualityComparer(Of NewClass).
Use late-bound calls (ie. option explicit off, like it appears you are doing now), and put a .Cast(Of Object)() call on both lists before calling Except.
Use the following code.
from t in db.Sites
where
!
(from t0 in db.Sites2
select new {
t0.SomeID
}).Contains(new { t.SomeID })
select t
this is based in not in condition. I think this will help you. U are doing some complex thing.
It looks like it's asking that your comparer implement the non-generic interface IEqualityComparer, whereas yours implements IEqualityComparer (Of Object), which is a different interface.
It looks like you are using a database as the back end. You can't provide a custom comparer for this, as it can't be mapped to TSQL.
Have you tried Contains? i.e. where !List1.Contains(i.SiteID)?