Lambda property value selector used in lambda expression - vb.net

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))

Related

How to create a Boolean Function to list objects in a Generic Collection?

After creating a List(Of T), I want to create aBoolean function. First, we will ask data to add an object in the list. However, in case this new object has the same "DNI" (String attribute from the class Aspirante), then we cannot include this new Object in the list. Therefore, it should be True when we have an Object with the same attribute and False when we don´t, so we can add the new object.
Below is the code I did:
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(ByRef objAspirante As Aspirante) As Boolean
If datos.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
However it doesn´t work. I have no clue on how to do it. Sorry if I was not clear enough.
This doesn't answer your question directly but it involves a significant amount of code, so it won't work in a comment.
You probably shouldn't be using a List(Of T) in the first place. The HashSet(Of T) already includes functionality to prevent adding duplicate items, so that may be a better option. If you want to compare objects on a specific property value then you need to first create a comparer based on that:
Public Class Thing
Public Property Stuff As String
End Class
Public Class ThingComparer
Implements IEqualityComparer(Of Thing)
Public Overloads Function Equals(x As Thing, y As Thing) As Boolean Implements IEqualityComparer(Of Thing).Equals
Return x.Stuff.Equals(y.Stuff)
End Function
Public Overloads Function GetHashCode(obj As Thing) As Integer Implements IEqualityComparer(Of Thing).GetHashCode
Return obj.GetHashCode()
End Function
End Class
You then create a HashSet(Of T) that uses that comparer to determine equality:
Dim things As New HashSet(Of Thing)(New ThingComparer)
You can then just add items as you please by calling Add. That will either add the new item and return True or it will not not add the duplicate item and return False:
Dim variousStuff = {"One", "Two", "One"}
For Each stuff In variousStuff
Dim something As New Thing With {.Stuff = stuff}
If things.Add(something) Then
Console.WriteLine($"'{stuff}' was added successfully.")
Else
Console.WriteLine($"'{stuff}' is a duplicate and was not added.")
End If
Next
The potential drawback is that HasSet(Of T) does not implement IList(Of T), so you cannot access items by index. It does implement ICollection(OF T) though, so it does have a Count property and you can enumerate it with a For Each loop.
You can create a List(Of String) containing the just the DNI property of each object in datos. Then see if the DNI of objAspirante is contained in lst.
Public Class Oposicion
Private datos As New List(Of Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
Dim lst As New List(Of String)
For Each a As Aspirante In datos
lst.Add(a.DNI)
Next
If lst.Contains(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante)
Return False
End If
End Function
End Class
If you can change the type of datos, this might be easier.
Public Class Oposicion
Private datos As New Dictionary(Of String, Aspirante)()
Public Function Alta(objAspirante As Aspirante) As Boolean
If datos.ContainsKey(objAspirante.DNI) Then
Return True
Else
datos.Add(objAspirante.DNI, objAspirante)
Return False
End If
End Function
End Class
If you want to stick with your existing List(Of Aspirante), then simply use .Any() and pass it a Lambda to determine if one already exists. It'd look something like this:
Public Function Alta(ByVal objAspirante As Aspirante) As Boolean
If Not datos.Any(Function(x) x.DNI = objAspirante.DNI) Then
datos.Add(objAspirante)
Return True ' your description and code did not match for these!
End If
Return False ' your description and code did not match for these!
End Function
Note my comment on the Return lines...your code and description did not match here.

Call a generic function passed as parameter in recursive function

My desire is to run a given function by name through AddressOf with one input parameter, e.g. Function Foo(x as Integer) As Integer. The two inputs I need into the recursive function are the function name _name As String and an object of some type t _list As t (Integer, Double, List(Of Integer), etc). The goal is to process either an element or list of elements with the function name, as there are multiple times I need to process a list by a given function and I do not wish to replicate the list processing code in each location. The ways I've tried to call my best go at this type of function (below) that didn't crash completely resulted in this error:
Warning: List.Test operation failed. Overload resolution failed because no Public 'ProcessList' can be called with these arguments:
'Public Shared Function ProcessList(Of t)(_func As Func(Of Object,t), _list As System.Object) As IEnumerable(Of t)':
Type argument inference fails for argument matching parameter '_func'.
Iterator Function ProcessList(Of t)(_func As Func(Of Object, t), _list As Object) As IEnumerable(Of t)
If _list.GetType = GetType(List(Of t)) Then
Yield _list.SelectMany(Function(l) ProcessList(_func, l))
Else
Yield _func(_list)
End If
End Function
For reference, I found a snippet of Python code that effectively does what I need, but I'm a little rusty on translating in this direction (Python to VB.net), and I'm not as familiar with this type of programming in VB.net. The Python snippet is:
def ProcessList(_func, _list):
return map(lambda x: ProcessList(_func, x) if type(x)==list else _func(x), _list)
Any help as to how I need to call this function, or how to rework this function if my approach is flawed, would be greatly appreciated!
Update:
I re-examined how I was calling the function and a few other things based on #djv's info that my method is working. First, due to the nature of how I'm interfacing with these functions, I have to expose the above function with:
Public Shared Function Foo(ByVal _input As Object) As Object
Return Utilities.ProcessList(AddressOf Bar, _input)
End Function
I'm also now getting the error message:
Warning: List.Test operation failed.
Unable to cast object of type 'System.Int32' to type 'System.Collections.Generic.IList`1[System.Int32]'.
The issue at this point probably lies with the method in which I'm calling my ProcessList function, rather than the function itself as I thought. I'm interfacing with a GUI that is not happy with calling ProcessList on its own, so I need this intermediate "helper" function, which I am apparently not using correctly.
You will always get an IEnumerable(Of T) and T can either be a primitive (i.e. Integer) or list of primitive (i.e. List(Of Integer)). So when you try to call it with a List, you get a List(Of List(Of Integer)) for example.
We can see why by breaking ProcessList up into two methods. The difference between them is the type of the second argument which is either T or IEnumerable(Of T)
Sub Main()
Dim i As Integer = 1
Dim li As New List(Of Integer) From {1, 1, 1}
Dim ri As IEnumerable(Of Integer) = ProcessList(AddressOf foo, i).ToList()
Dim rli As IEnumerable(Of Integer) = ProcessList(AddressOf foo, li).ToList()
Dim d As Double = 1.0#
Dim ld As New List(Of Double) From {1.0#, 1.0#, 1.0#}
Dim rd As IEnumerable(Of Double) = ProcessList(AddressOf foo, d).ToList()
Dim rld As IEnumerable(Of Double) = ProcessList(AddressOf foo, ld).ToList()
Console.ReadLine()
End Sub
Function ProcessList(Of T)(f As Func(Of T, T), p As IEnumerable(Of T)) As IEnumerable(Of T)
Return p.Select(Function(i) ProcessList(f, i)).SelectMany(Function(i) i)
End Function
Iterator Function ProcessList(Of T)(f As Func(Of T, T), p As T) As IEnumerable(Of T)
Yield f(p)
End Function
Function foo(param As Integer) As Integer
Return param + 1
End Function
Function foo(param As Double) As Double
Return param + 1.0#
End Function
Previously, I could not even hit the line in your original code which did the SelectMany. Now, it is hit when the proper function is called. I also retooled that call to fit the new function signature.
The overloads are both called, based on the second argument passed them. However, you only need one foo method for each T (either a primitive or its IEnumerable).

How I can proceed each member of lambda expression with a Function that return value using delegate?

I use Vb.net in my work. I find a difficulty when trying to delegate a Function that returns value into each member of lambda expression.
I can do if for each member of lambda is delegated into a Sub:
Public class TransactionX
Public Number1 as Integer
Public Number2 as Integer
end class
Public Class TestX
Public Sub DoTest()
Dim trans as New List(Of TransactionX)
trans.Add(New TransactionX with{.Number1=0, .Number2=0})
trans.Add(New TransacrtionX with{.Number1=1, .Number2=1})
dim changeNumber = New Action(Of TransactionX)(AddressOf DoChange)
dim transSearch = trans.Where(Function(t) t.Number1>0).ForEach(changeNumber)
end Sub
Private Sub DoChange(ByVal tranX as TransactionX)
with tranx
.Number1 = 10
.Number2 = 10
end with
end Sub
end Class
above code will update each member of lambda expression's result.
What if I want to do Number1 * Number2 for each member that I catch from Lambda with a private Function and then will return into a collection?
I think it can be done with C# but I could not solve it using Vb.net.
Change you method to return calculated value and use it with Select extension method
Private Function DoChange(ByVal tranX As TransactionX) As Integer
Return tranX.Number1 * tranX.Number2
End Function
Dim transSearch As IEnumerable(Of Integer) = trans.Select(AddressOf DoChange)
Use AddressOf keyword for passing existed method as parameter

NHibernate QueryOver: Variable 'line' of type 'SomeTable' referenced from scope '', but it is not defined

I'm working with legacy data, which often brings me one information splited in multiple columns. I'm trying to reproduce the following SQL query...
SELECT * FROM SomeTable WHERE concat(DescriptionPart1,DescriptionPart2) LIKE 'TEST'
...using NHibernate QueryOver. So:
Dim myQuery = Me.Session.QueryOver(Of SomeTable).WhereRestrictionOn( _
Function(line As SomeTable) line.DescriptionPart1 & line.DescriptionPart2) _
.IsLike("TEST")
This own statement will run into the following exception:
Variable 'line' of type 'SomeTable' referenced from scope '', but it is not defined
Any directions? I'm trying to avoid magic strings, but I'm always giving up to it (as using HQL the concatenation expression + like function works like a charm).
a little bit verbose but it works
var results = session.QueryOver<SomeTable>()
.Where(Restrictions.Like(
Projections.SqlFunction("concat", NHibernateUtil.String, Projections.Property<SomeTable>(x => x.DescriptionPart1), Projections.Property<SomeTable>(x => x.DescriptionPart2)),
"TEST",
MatchMode.Anywhere))
.List();
For the record, I solved this issue using Linq.
The main point of my question (my fault, I haven't mentioned that) was the possibility to reuse code from a base class, so I wanted to extract the Description expression of a given table to use it for multiple purposes. The final idea is implemented as below:
Public MustInherit Class DefaultBusinessLogic(Of Poco)
Public Overridable ReadOnly Property DescriptionExpression as Expression(Of Func(Of Poco, String))
Get
Return Nothing
End Get
End Property
Public Function SearchByDescription(searchArgument as String) as IEnumerable(Of Poco)
Dim nhSession as ISession = SessionManager.GetSession()
Dim query = nhSession.Query(Of Poco)
If Not String.IsNullOrWhitespace(searchArgument) AndAlso
Me.DescriptionExpression IsNot Nothing Then
searchArgument = "%" & searchArgument & "%"
Dim isLikeMi = ReflectionHelper.GetMethod(Sub() LinqHelpers.IsLike(Nothing, Nothing)) '* See (1)
Dim isLikeExpression = Expression.Call(isLikeMi, Me.DescriptionExpression.Body, Expression.Constant(searchArgument))
Dim whereExpression = Expression.Lambda(Of Func(Of Poco, Boolean))(isLikeExpression, Me.DescriptionExpression.Parameters)
query = query.Where(whereExpression)
End If
Return query.ToList()
End Function
Public Function GetDescription(pocoRecord as Poco) as String
If Me.DescriptionExpression Is Nothing Then Return String.Empty
Return Me.DescriptionExpression.Compile().Invoke(pocoRecord)
End Function
End Class
Public Class SomeTableBusinessLogic
Inherits DefaultBusinessLogic(Of SomeTable)
Public Overrides ReadOnly Property DescriptionExpression as Expression(Of Func(Of Poco, String))
Get
Return Function (row as SomeTable) row.DescriptionPart1 & row.DescriptionPart2
End Get
End Property
End Class
Public Class SomeTable
Public Overridable Property Id as Integer
Public Overridable DescriptionPart1 as String
Public Overridable DescriptionPart2 as String
End Class
(1) The 'IsLike' method was implemented as a NHibernate Linq extension. Extracted from here.

Implementing iComparer for custom objects after converting a Dictionary to SortedDictionary

I'm having trouble implementing an IComparer method.
Essentially, I want to compare the properties of two custom objects (the properties are of type integer).
dE is a Dictionary(Of String, customObj)
prTabIndex is a property of customObj and is of type Integer
(these hold true for all examples)
After some more searching I found this thread which suggested 3 things: a List approach, utilizing LINQ, and using some C# 3.0 features. However, being in vb, not sure what they best approach is.
I've tried three different ways:
...rolling my own IComparer implementation:
Public m As Sub(ByRef d As Dictionary(of String, customObj))
Dim sortedD As New SortedDictionary(Of String, customObj)(d, myCompare)
End Sub
Public Class myCompare
Implements IComparer
Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare
If TryCast(x, customObj).prTabIndex < TryCast(y, customObj).prTabIndex Then
Return -1
Else
Return 1
End If
End Function
End Class
...Sorting a list (which, I think works - making this thread slightly academic).
Dim sortedL As List(Of KeyValuePair(Of String, customObj)) = dE.ToList
sortedL.Sort(Function(firstPair As KeyValuePair(Of String, customObj), nextPair As KeyValuePair(Of String, customObj)) firstPair.Value.prTabIndex.CompareTo(nextPair.Value.prTabIndex))
...or incorporating the lambda function directly in the conversion to the SortedDictionary:
Dim dESorted = From kvp As KeyValuePair(Of String, customObj) In dE.ToDictionary(Function(first As KeyValuePair(Of String, customObj), second As KeyValuePair(Of String, customObj)) first.Value.prTabIndex.CompareTo(nextPair.Value.prTabIndex))
Note that VS2008 has underlined 'dE.ToDictionary...' (to the end of the line) and giving me two messages depending on where I hover my mouse:
1) "Data type(s) of the type parameter(s) in extension method 'signature' As 'signature defined in 'System.Linq.Enumerable cannot be inferred from these arguments. Specifying the data types explicitly might correct this error. Seen while hovering over "ToDictionary".
2) Nested function does not have the same signature as delegate 'signature'. Seen while hovering over anything after "ToDictionary".
Admittedly, I'm new to lambda functions.
Q1) How far off am I in each of the implementations?
Q2) Which one is the computationally least expensive? Why?
Q3) Which one is the computationally most expensive? Why?
Warm Regards,
-sf
You can save your self casting if you implement the Generic IComparable(Of ...). I think you should also handle the possibility that the two objects are equal.
Public Class DemoClass
Implements IComparable(Of DemoClass)
Private mstrField1 As String
Public Property Field1() As String
Get
Return mstrField1
End Get
Set(ByVal value As String)
mstrField1 = value
End Set
End Property
Private mstrField2 As String
Public Property Field2() As String
Get
Return mstrField2
End Get
Set(ByVal value As String)
mstrField2 = value
End Set
End Property
Private mstrField3 As String
Public Property Field3() As String
Get
Return mstrField3
End Get
Set(ByVal value As String)
mstrField3 = value
End Set
End Property
''' <summary>
''' Default sort - 1 ASC, 2 ASC, 3 ASC
''' </summary>
''' <param name="other"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Function CompareTo(ByVal other As DemoClass) As Integer Implements System.IComparable(Of DemoClass).CompareTo
'-1 = less than other; 0 = same as other; +1 = greater than other'
Select Case Me.Field1
Case Is < other.Field1 : Return -1
Case Is > other.Field1 : Return 1
Case Else 'equal
Select Case Me.Field2
Case Is < other.Field2 : Return -1
Case Is > other.Field2 : Return 1
Case Else 'equal
Select Case Me.Field3
Case Is < other.Field3 : Return -1
Case Is > other.Field3 : Return 1
Case Else : Return 0 'equal
End Select
End Select
End Select
End Function
End Class