Query pattern, Composability ( Query Design continued ):

  ****


This post is a part of a series of posts about query design. For the previous post see: https://blogs.msdn.com/vladimirsadov/archive/2007/02/19/polymorphic-selector-and-extension-methods-query-design-continued.aspx

 

 

Here I want to summarize what exactly the minimum requirements are for a type to be considered queryable with some explanations why we have such rules.

 

Quaryable type requirements:

1. There must be a method called Select that can be called with the collection instance. Note that this can be either an instance method or an extension method. As long as you can make a call like col_instance.Select(..) it is acceptable as a select operator.

Why: by having a proper Select method a type indicates that it can be used in query expressions. It is also the method that represents “Select” in the query syntax.

Dim result = From el in col_inst Select el.ToString
translates into:
Dim result = col_inst.Select(...)

2. Select must take exactly one argument. The argument must be of a function delegate type that takes one argument.

Why: The argument must be a delegate because compiler will generate a function that takes one argument and will pass this function to the Select method in form of a delegate.

3. While the selector delegate can be (and often is) a generic type, the delegate’s argument type (let’s call it T) must be completely inferable from the collection type.

Why:T must be known because it indicates the type of collection elements. Since the collection is completely in charge of iterating itself we need some way to know what type its elements have. In the above example T would be the type of el. Knowing the type of iteration variable compiler can verify that projection expression (el.ToString in the above example) makes sense. T is also used to provide intellisense when you type “el.” .

 

Example of very simple queryable type:

 

Public Delegate Function SelectorDelegate(ByVal arg As Integer) As Integer

Class MyQueryable

    Public elements() As Integer

    Public Function [Select](ByVal projection As SelectorDelegate) As MyQueryable

        Dim new_col As New MyQueryable

        new_col.elements = elements.Clone

        For I As Integer = 0 To elements.Length - 1

            new_col.elements(I) = projection(elements(I))

        Next

        Return new_col

    End Function

End Class

You can test this queryable type with code like this:

Module Module1

    Sub Main()

        Dim arr As Integer() = New Integer() {1, 2, 3}

        Dim col As New MyQueryable()

        col.elements = arr

        Dim q = From I In col Select square = I * I

        For Each t In q.elements

            Console.WriteLine(t)

        Next

    End Sub

End Module

 

Output:

                1,4,9

 

 

Note that result of Select in our example is MyCollection that is a queryable type. This is not a requirement of queryable pattern. Select could return any type it wants. However returning a queryable type is very convenient as you can “compose” query by chaining Select operators:

 

This is a valid expression in VB. The second Select will be applied to the result of the first select

 

Dim q1 = From I In col Select square = I * I Select sq_2 = square * 2

 

If you replace the query in the previous example with the new query, you will get output:

                2,8,18