Updateable Fluent Collection

In my previous posts about my Adventures in Creating Fluent Interfaces I created a way to fluently filter objects in a collection by the methods of the object. That’s all nice and dandy but what is it worth if you can’t manipulate the objects in the same way. I want to be able to change the object also. My first goal is to just manipulate the properties on the object and not yet children objects. When I began I just started with an ‘Update’ method on the FluentComparableList object which was the object I returned when I selected PeopleList.Name which gave me all the filtering methods like greater and contains.

The Design Problem – invalid options

While writing out my tests (Not quite TDD but I did them really soon) I realized that this created issues. If I had a read only property I didn’t want the Set method to appear in the list because It would not work. I it’s a write only parameter then I didn’t want all the filtering methods available because there is no data to filter on. I noticed this when I wrote People.Name.IndexOf(“e”)… the ‘Set’ option came up. What would that be setting. I can’t set the index of the letter ‘e’! Am I setting the name before it? It’s unclear syntax. I decided I needed a separate object for updating than filtering.

First Solution – differentiate by additional options

This posed another issue because there are many cases where a method can both be updated and filtered. If it can be written to and read from I should be able to Update and Filter it. You can’t inherit from two objects (not in VB at least) so I had a quandary. I built out an example where I chose which to do in the syntax. For example: PeopleList.Name.Filter.Contains(“e”) but that lengthened the syntax. No, there had to be a better way.

Second Solution – Composition objects

My current solution is to create one object for update and another object for filtering and a third object that composes these together. It is a lot of complicated code but, once again, this code is in a library and should not change often. That is where the complexity can be managed. There is probably something better. I look forward to your suggestions.

When I type PersonList.Name I will receive a special string collection I call FluentStringCollection that has all the update and filter methods. When I type Personlist.Name.indexof(‘e’) I don’t have to return a collection that has all the update methods. i can just return a collection with just the filter methods. Separating it out also makes the code easier to follow and helps get closer to the single responsibility principal.

The Code

To get this done we will need more classes. I have the FluentComparableList from the last post but I renamed it to FluentCollectionFilter. I’m constantly trying to have the name make the most sense until I come to a release point. It remains mostly unchanged. Here it is so you can see where the other classes fit in.

Public Class FluentCollectionFilter(Of CollectionType As {ICollection, New}, ItemType, ParamaterType As IComparable)
    Public InternalList As CollectionType
    Public InternalFunction As Func(Of ItemType, ParamaterType)
    Public useNot As Boolean

    Sub New(ByRef list As CollectionType, ByVal _Func As Func(Of ItemType, ParamaterType), Optional ByVal _Not As Boolean = False)
        InternalList = list
        InternalFunction = _Func
        useNot = _Not
    End Sub

    Public Function [Not]() As FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, Function(s) InternalFunction(s), True)
    End Function

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

    Public Function Greater(ByVal value As ParamaterType) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).CompareTo(value) > 0)
    End Function

    Public Function Lesser(ByVal value As ParamaterType) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).CompareTo(value) < 0)
    End Function

    Public Function LesserOrEqual(ByVal value As ParamaterType) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).CompareTo(value) <= 0)
    End Function

    Public Function GreaterOrEqual(ByVal value As ParamaterType) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).CompareTo(value) >= 0)
    End Function

    Public Function Between(ByVal Min As ParamaterType, ByVal Max As ParamaterType) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).CompareTo(Min) >= 0 And InternalFunction(item).CompareTo(Min) <= 0)
    End Function

    Public Shadows Function Equals(ByVal value As ParamaterType) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).Equals(value))
    End Function

End Class

To that we add our FluentCollectionSetter. You guessed it, this is the class that has all the methods for updating the objects in a collection. Right now there is only one which is Set. I’m still playing with the names. ‘Set’ is shorter than ‘Update’ but it is a reserved word. You will notice that there is also a new function InternalSetter. This is where we pass in the a function to update the parameter. A function to read a parameter is significantly different from a function to write so I needed to pass in a pointer to what function updates the proper parameter. More on that later.

Public Delegate Sub Setter(Of ItemType, ParamaterType)(ByRef item As ItemType, ByVal value As ParamaterType)

Public Class FluentCollectionSet(Of CollectionType As {ICollection, New}, ItemType, ParamaterType As IComparable)
    Public InternalList As CollectionType
    Public InternalFunction As Func(Of ItemType, ParamaterType)
    Public InternalSetter As Setter(Of ItemType, ParamaterType)


    Sub New(ByRef list As CollectionType, ByVal _Func As Func(Of ItemType, ParamaterType), ByRef _Setter As Setter(Of ItemType, ParamaterType))
        InternalList = list
        InternalFunction = _Func
        InternalSetter = _Setter
    End Sub

    Function [Set](ByVal Value As ParamaterType) As CollectionType
        For Each item In InternalList
            InternalSetter(item, Value)
        Next
        Return InternalList
    End Function
End Class

Now we need a third class that combines the FluentCollectionFilter and the FluentCollectionSetter. I could not inherit from two objects so I composed them together by setting up every method and wiring up the calls. Usually you could just inherit the methods and your class is much more concise. Here is the combined class:

Public Class FluentCollection(Of CollectionType As {ICollection, New}, ItemType, ParamaterType As IComparable)
    Public InternalList As CollectionType
    Public InternalFunction As Func(Of ItemType, ParamaterType)
    Public useNot As Boolean
    Public InternalSetter As Setter(Of ItemType, ParamaterType)

    Sub New(ByRef list As CollectionType, ByVal _Func As Func(Of ItemType, ParamaterType), ByRef _Setter As Setter(Of ItemType, ParamaterType), Optional ByVal _Not As Boolean = False)
        InternalList = list
        InternalFunction = _Func
        InternalSetter = _Setter
        useNot = _Not
    End Sub

    Public Function GetNewList(ByRef _Func As Func(Of ItemType, Boolean))
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        GetNewList(_Func)
    End Function


    Public Function [Set](ByVal value As ParamaterType) As CollectionType
        Return New FluentCollectionSet(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, InternalSetter). _
        Set(value)
    End Function

    Public Function Greater(ByVal value As ParamaterType) As CollectionType
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        Greater(value)
    End Function

    Public Function Lesser(ByVal value As ParamaterType) As CollectionType
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        Lesser(value)
    End Function

    Public Function LesserOrEqual(ByVal value As ParamaterType) As CollectionType
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        LesserOrEqual(value)
    End Function

    Public Function GreaterOrEqual(ByVal value As ParamaterType) As CollectionType
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        GreaterOrEqual(value)
    End Function

    Public Function Between(ByVal Min As ParamaterType, ByVal Max As ParamaterType) As CollectionType
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        Between(Min, Max)
    End Function

    Public Shadows Function Equals(ByVal value As ParamaterType) As CollectionType
        Return New FluentCollectionFilter(Of CollectionType, ItemType, ParamaterType)(InternalList, InternalFunction, useNot). _
        Equals(value)
    End Function
End Class

One More class to inherit from the FluentCollection above to add on the special String only filter and updates. I just inherited as it works for what I am doing now and is significantly concise.

Public Class FluentCollectionString(Of CollectionType As {ICollection, New}, ItemType)
    Inherits FluentCollection(Of CollectionType, ItemType, String)

    Sub New(ByRef list As CollectionType, ByVal _Func As Func(Of ItemType, String), ByRef _Setter As Setter(Of ItemType, String), Optional ByVal _Not As Boolean = False)
        MyBase.New(list, _Func, _Setter, _Not)
    End Sub

    Public Shadows Function [Not]() As FluentCollectionString(Of CollectionType, ItemType)
        Return New FluentCollectionString(Of CollectionType, ItemType) _
        (InternalList, Function(s) InternalFunction(s), InternalSetter, True)
    End Function

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

    Public Function EndsWith(ByVal value As String) As CollectionType
        Return GetNewList(Function(item) InternalFunction(item).EndsWith(value))
    End Function

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

    Public Function IndexOf(ByVal value As String) As FluentCollectionFilter(Of CollectionType, ItemType, Integer)
        Return New FluentCollectionFilter(Of CollectionType, ItemType, Integer) _
        (InternalList, Function(s) InternalFunction(s).IndexOf(value))
    End Function

    Public Function Replace(ByVal oldValue As String, ByVal newValue As String) As CollectionType
        For Each item In InternalList
            Me.InternalSetter(item, InternalFunction(item).Replace(oldValue, newValue))
        Next
        Return InternalList
    End Function

As it turns out I could not figure out how to update items inside a function statement or a lambda. I tried several options including setting everything to ByRef but there is no ByRef on the function itself. The MSDN documentation implies that both of these are always read only. To be able to modify an item in a function I create a delegate and set it to the address of a function that sets the parameter. This adds several lines of code to each Parameter which you will see below. You will see that in the code below for the implementation of the new PersonList Class. This code is different from above in that it is not in the Fluent library. This is what your code would look like when using the library. It could be generated though as it is mostly just hooking things up.

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 FluentCollectionString(Of PersonList, Person)
        Return New FluentCollectionString(Of PersonList, Person)(Me, Function(s) s.Name, AddressOf NameSetter)
    End Function

    Private Sub NameSetter(ByRef si As Person, ByVal value As String)
        si.Name = value
    End Sub

    Public Function BirthDate() As FluentCollection(Of PersonList, Person, Date)
        Return New FluentCollection(Of PersonList, Person, Date) _
        (Me, Function(s) s.BirthDate, AddressOf BirthDateSetter)
    End Function

    Public Function BirthDate(ByVal value As Date) As PersonList
        Return New FluentCollection(Of PersonList, Person, Date) _
        (Me, Function(s) s.BirthDate, AddressOf BirthDateSetter). _
        Set(value)
    End Function

    Private Sub BirthDateSetter(ByRef si As Person, ByVal value As String)
        si.BirthDate = value
    End Sub
End Class

It’s starting to get quite complicated but if it can be code generated then it won’t be too bad. If it could be generated then you could just build your Person class and generate the collection to use the library and voila! lots of power with little work. The FluentCollectionString might also be generated and not be in the library. I’ll have to think about that more.

Now we can update our collected objects in the same line we filter them. We could change all the letter ‘e’ to the letter ‘a’ in everyone’s name borne after 1980 with just:

People.BirthDate.Greater(“1/1/1980”).Name.Replace(‘e’,’a’)

Now that’s power! Tell me what you think.