Delayed Execution ( 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/06/07/query-pattern-composability-query-design-continued.aspx

 

Here is another important example of a queryable type. There are two differences from the previous examples:

 

It uses System.Func delegate type that is defined in System.Core.dll . There is nothing special in using this type. VB compiler will accept any delegate type that matches the queryable pattern. However using predefined delegate type reduce the amount of code to type.

 

Another very important difference is that the Select does not create another collection with projected elements. Instead it creates an object that “knows” how to produce projected elements when asked for them. There are many advantages in doing that. First of all we may not need all the elements so it makes sense to delay computation until the elements are needed. Secondly for a big source collection, the resulting collection will take much less space. This is especially important if you compose a query with multiple projections applied in sequence.

 

Basically instead of creating another collection for every Select in a composed query we will build a pipeline of enumerable types that represent projection delegates. When another element is requested from the query, by delegating the request through the chain of these enumerable types, we will get the next element from the source collection and every projection in the pipeline will be applied to the element.

 

This concept is commonly known as a delayed execution. It does complicate the debugging somewhat as there seem to be too many levels of indirection. However the benefits far outweigh that.

 

Here is the sample
(it is not as big as it seems as most parts are just required to implement IEnumerable pattern):

Class EnumerableQueryable(Of T)

    Implements IEnumerable(Of T)

    Private elements As IEnumerable(Of T)

    Sub New(ByVal elements As IEnumerable(Of T))

        Me.elements = elements

    End Sub

    Public Function [Select](Of S)(ByVal selector As Func(Of T, S)) As EnumerableQueryable(Of S)

        Dim new_enum = New Enumerable4Select(Of T, S)(Me, selector)

        Dim new_col As New EnumerableQueryable(Of S)(new_enum)

        Return new_col

    End Function

    Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of T) Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator

        Return elements.GetEnumerator

    End Function

    Public Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

        Return GetEnumerator()

    End Function

End Class

Class Enumerable4Select(Of T, S)

    Implements IEnumerable(Of S)

    Private collection As IEnumerable(Of T)

    Private selector As Func(Of T, S)

    Public Sub New(ByVal coll As IEnumerable(Of T), ByVal func As Func(Of T, S))

        collection = coll

        selector = func

    End Sub

    Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of S) Implements System.Collections.Generic.IEnumerable(Of S).GetEnumerator

        Return New Enumerator4Select(collection.GetEnumerator(), selector)

    End Function

    Public Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator

        Return GetEnumerator()

    End Function

    Class Enumerator4Select

        Implements IEnumerator(Of S)

        Private collection As IEnumerator(Of T)

        Private selector As Func(Of T, S)

        Public Sub New(ByVal coll As IEnumerator(Of T), ByVal func As Func(Of T, S))

            collection = coll

            selector = func

        End Sub

        Public ReadOnly Property Current() As S Implements System.Collections.Generic.IEnumerator(Of S).Current

            Get

                Return selector(collection.Current)

            End Get

        End Property

        Public ReadOnly Property Current1() As Object Implements System.Collections.IEnumerator.Current

            Get

                Return Current

            End Get

        End Property

        Public Function MoveNext() As Boolean Implements System.Collections.IEnumerator.MoveNext

            Return collection.MoveNext()

        End Function

        Public Sub Reset() Implements System.Collections.IEnumerator.Reset

            collection.Reset()

        End Sub

        Private disposedValue As Boolean = False ' To detect redundant calls

        ' IDisposable

        Protected Overridable Sub Dispose(ByVal disposing As Boolean)

            If Not Me.disposedValue Then

                If disposing Then

                    ' TODO: free managed resources when explicitly called

                End If

                ' TODO: free shared unmanaged resources

            End If

            Me.disposedValue = True

        End Sub

#Region " IDisposable Support "

        ' This code added by Visual Basic to correctly implement the disposable pattern.

        Public Sub Dispose() Implements IDisposable.Dispose

            ' Do not change this code. Put cleanup code in Dispose(ByVal disposing As Boolean) above.

            Dispose(True)

            GC.SuppressFinalize(Me)

        End Sub

#End Region

    End Class

End Class

Here is some code to test the above sample:

Module Module1

    Sub Main()

        Dim col As New EnumerableQueryable(Of Integer)(New Integer() {1, 2, 3})

        Dim q = From i In col Select i_sqr = i * i Select i_sqr + 1

        For Each i In q

            Console.WriteLine(i)

        Next

    End Sub

End Module