Adventures in Creating Fluent Interfaces V2

I looked at the code and it looked at the code from my last Adventures in Creating Fluent Interfaces and it looked convoluted and clunky. I also realized that I was creating a copy of most of the objects and more objects with every dot in my syntax. There had to be another way.  I realized that if I could just pass the method I wanted to evaluate (the innerText method in this case) then I could just pass down the original list to the next object in the chain then perform the selection function on it (like the “Contains” function) and if it returned true then I could include that in the next collection. I created lamdas and passed them down in the constructor for the object with the original list and boy does that look better.

To simplify the example I used the canonical Person class that just holds one name. We will add more later.

Public Class Person
    Public Name As String

    Sub New(ByVal _Name As String)
        Name = _Name
    End Sub

End Class

From there we want to get to this code:

People = New PersonList
People.Add(New Person("Bentley"))
People.Add(New Person("Joe"))
People.Add(New Person("Jimmy"))
People.Add(New Person("Karla"))
People.Add(New Person("Cameron"))

Debug.Print People.Name.StartsWith("J").count
'Prints 2 (for Joe and Jimmy)

Debug.Print People.Name.StartsWith("J").Name.EndsWith("y").count
'Prints 1 (for Jimmy)

That looks great! Now how do we get there. First, we want a collection of people.

Public Class PersonList
    Inherits List(Of Person)

    Sub New()
        MyBase.new()
    End Sub

    Sub New(ByVal List As List(Of Person))
        MyBase.New(List)
    End Sub

    Public Function Name() As StringWhere(Of PersonList, Person)
        Return New StringWhere(Of PersonList, Person)(Me, Function(s) s.Name)
    End Function

End Class

So when we call ‘People.Name’ we receive a StringWhere object. That is a generic class that we don’t have to write each time. Here is the beginning of it to give you an idea

Public Class StringWhere(Of ListType As {IList, New}, ItemType)
    Public InternalList As ListType
    Public InternalFunction As Func(Of ItemType, String)

    Sub New(ByRef list As ListType, ByVal _Func As Func(Of ItemType, String))
        InternalList = list
        InternalFunction = _Func
    End Sub

    Public Function GetNewList(ByRef _Func As Func(Of ItemType, Boolean))
        GetNewList = New ListType
        For Each item In InternalList
            If _Func(item) Then GetNewList.Add(item)
        Next
    End Function

    Public Function StartsWith(ByVal value As String) As ListType
        Return GetNewList(Function(item) InternalFunction(item).StartsWith(value))
    End Function

    Public Function Contains(ByVal value As String) As ListType
        Return GetNewList(Function(item) InternalFunction(item).Contains(value))
    End Function
etc...

Now we can iterate over it as many times as we want using StartsWith or EndsWith or Contains and filter what we need. We can add other text fields like LastName or StreetAddress and do the same with them by just also adding it to the collection like we did with the Name field. Now you can easily find the objects you want and look good doing it. Let me know if you have any other ideas or see any disadvantages in this approach or even the whole concept. Please don’t frustrate, educate!