Representing the user code by delegates ( 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/01/22/generic-implementation-of-iterateandapply-query-design-continued.aspx 

 

Lets’ now consider the implementation of Apply function. In theory we could somehow interpret the string inside the Apply function, but having code in a form of string still causes problems. Consider the following example:

 

        Dim number_names As New List(Of Integer)

        number_names = IterateAndApply(numbers, _

                        "squares.Add(current_number.ToString())")

 

 

Take a look at the expression. While the code seems fine, our Apply function expects that the resulting type of the expression matches the type of the collection elements. This particular expression takes Integer and returns String.

 

We can add another type argument to Apply function to represent the return type of the expression:

 

Function Apply(Of T, S)(ByVal item As T, ByVal code As String) As S

 

However this just replaces one problem with another. Someone somewhere must specify S. Generic type argument T can be inferred from the function arguments (in particular from “item”). This is not the case for S. Since “code” is just a string, there is no way of knowing the type of the results of this expression. In fact there is no way to know at compile time that the expressions returns anything or even runs.

 

Consider this example

        Dim number_names As New List(Of Integer)

        number_names = IterateAndApply(numbers, "BLAH BLAH BLAH")

This is not going to run, but compiler has no idea.

 

Clearly a string is a very bad representation of user code. Besides the problems with interpreting it provides no information on what this code returns or whether this code will work.

 

Fortunately there is a way to write a piece of code and describe its arguments and return type. For that we need to put the code in a separate function and pass a reference to that function into the IterateAndApply as a delegate:

 

   Dim numbers As New List(Of Integer)

   numbers.Add(42)

   Dim number_names As New List(Of Integer)

 

   Dim MyCodeRef As Func(Of Integer, String) = AddressOf MyCode

   number_names = IterateAndApply(numbers, MyCodeRef)

   Function MyCode(ByVal current_number As Integer) As String

        Return current_number.ToString

   End Function

   ' =====================================

   ' implementation of IterateAndApply

   '======================================

   'delegate type to represent user expressions

   ' T is the type of argument

   ' S is the type of the result

   Delegate Function Func(Of T, S)(ByVal arg As T) As S

   ' source - the collection that is being iterated

   ' code - reference to user supplied code to apply to each element

   ' returns a collection of results when user's code is applied to

   ' all lements.

   Function IterateAndApply(Of T, S)(ByVal source As IEnumerable(Of T), _

                    ByVal code As Func(Of T, S) _
) As IEnumerable(Of S)

        Dim result As New List(Of S)

        'iterate the source

        For Each item In source

            'apply the code that user has provided to each element

            result.Add(Apply(item, code))

        Next

        Return result

   End Function

   'function that applies given code to the given object and returns
'the result

   Function Apply(Of T, S)(ByVal item As T, ByVal code As Func(Of T, S)) As S

        Return code.Invoke(item)

   End Function

 

 

The code above solves a lot of problems. First of all there is no need for “magic” to apply the user code. In fact Apply function becomes so trivial, there is no need to even have it. The inner loop of IterateAd Apply could simply look like this:

 

For Each item In source

            'apply the code that user has provided to each element

            result.Add(code.Invoke(item))

      Next

 

Secondly, because user code is contained in a regular function and expressed as regular code, existing checks in the compiler can verify the syntax, type safety and everything else that can be verified at compile time.