Lambda Expressions

One of my least fond memories of university was my class on Scheme. Firstly, I never saw the value in functional programming, I felt it was confusing and writing an entire application that was just one massive series of brackets seemed ludicrous. To add to it all, despite the power and performance of functional programming I just never saw it being applied to mainstream programming.

Fast forward 5 years since that class, and while I still have no particular love for Scheme or LISP and still heavily doubt that mainstream developed applications written entirely in functional programming will ever exist, I'm at least starting to find a use for lambda expressions and small chunks of embedded functional programming inside my .Net applications. Which is making wish I actually paid attention in that class now.

As stated in one of my last post, I've been working on ways of reducing the amount of code I need to rewrite by writing more generic implementations of classes. In some cases it was easy, for instance, one object was a query object used to get information from a database. Every query has the same fundamental elements, I reduced mine to simply a query and a string of parameters to sub into the query. Then the server takes that objects, applies the parameter list to the query and knows how to return the results. This was a huge success for me because I can re-use this class without ever touching it again for any query I want. It may not be the most elegant implementation, but it has already saved me hours of coding and this is one I wrote just a few days ago.

So, then there was another object which I had created to filter the result set. My old school object oriented programming gave me 2 problems. Firstly, for every field I wanted to filter I would need to add code in both the class executing the filter and code to deal with it inside the filter, and my second problem is that for each query I may need to do filtering on a different schema, so I would also need a new class for every object I wanted to create filters for.

The solution to problem 2 really has nothing to do with lambda expressions, and you might think it should have been the first problem on my list, but in this scenario you would be wrong. The answer to 2 is to associate my filter with a strongly typed class (forgive my poor use of terminology, I was educated in Java, not .Net). So instead of my class definition looking like:
public class MyFilter {} it would look like public class MyFilter {}.
So back to why this is the solution to my second problem and not the other way around. Because I need to interpret how to apply what the class using MyFilter is passing into the filter class, I still need implementation specific code inside the MyFilter class, so while it will now accept the type at RunTime, I have gained nothing.

Segue time... I have been using some very simple lambda expressions in my code to bypass using delegates and I had seen some other examples of Lambda expressions solving the types of problems I had wanted to resolve, so last night I did some digging around and found a solution. Segue over.

For moment, assume we haven't already solved problem 2, because it detracts from looking at the solution in the small iterative steps I went through :) so we will also assume that our filter object is called PersonFilter and the first class we are using to apply the filter will be called PersonFilterMap.

My original example had a constructor that would have looked something like this basic example:
PersonFilterMap(string FieldName, PersonFilter Filter) and inside the class using the filter and the map I would have something like this PersonFilterMap pfm = new PersonFilterMap("LastName", myFilter);

Then inside the PersonFilter class, when something happens and I may need to update my filter object and apply a value. It would be ugly; something like switch(FieldName) { case "LastName" : do something ... break;}. It is this chunk of code here which made specifying the type at run time impossible, and also the reason why every field I want to add or change a filter on would require both a change in the class using it and inside this class itself. And what if I wanted to have 2 different types of filters on LastName? I would need to start hacking in ugly FieldName values and the code would quickly become unmanageable. But this is ultimately what the code we have wants me to do today.

So, after digging around last night, the solution I came up with was to use a lambda expression for that. In my little test project I changed my constructor to the following: PersonFilterMap(Func mappingFunction, PersonFilter Filter);

So, I've replaced my FieldName variable with a Function that expects to take in a PersonFilter and an object as parameters and then return an object. Not the most elegant (working on a solution) because I don't really need the return value. Now, in PersonFilterMap, I can replace my potentially huge and confusing case statement with 1 line of code, namely: mappingFunction.Invoke(Filter, myValue);

Inside the class using this method I now call: PersonFilterMap pfm = new PersonFilterMap( (x, y) => { do something...; return null; }, myFilter);

See where the case statement went? That code is now embedded in my constructor. This line of code grows a little, but the case statement itself actually disappears completely! Flexibility and reduction in the complexity of the mapping class.

Now, when I want to add a new filter type to my query, I only need to add code in one class, I create another mapper as above. The implementation specific part is now pulled out of the PersonFilterMap class and life is better.

Now that I don't have any implementation specific code in that class, I'm no longer in a position where I can't make the implementation even more generic. So, we will now go and add the back to our map class and change the name to reflect that it is now more generic and the quick and dirty version looks like this:
public class FilterMap 
{
     public FilterMap( Func mappingFunction, T filter) {}
}


Now inside my code calling this it looks like:
FilterMap personFilter = new FilterMap( (x, y) => {do something...; return null;}, myFilter);

So, long post, but what has all of this accomplished? Now I can build a filter for any field, and for any class (really there is nothing in there that even restricts me to just filtering, but not the point here) using that one mapping class. Once I implement this at work where I will eventually have hundreds of such mappings to do, this will have saved me days of redundant code. The only pieces I need to add are the pieces that truly need to change for each Filter type and Mapping.

Now, we'll get to the one voice I heard the most on the forums and websites while I was looking for solutions. "The code is less readable!". I can only assume that this is a result of using a Lambda expression in place of the more "traditional" C# code. From my perspective, especially in this application, using a Lambda Expression is more readable than the alternative. I create a new mapping, but without the lambda expression where I'm creating the mapping I have no idea how it is going to be used. I have to dig one or more levels deeper just to find out what the mapping is actually doing. Here I'm creating a new mapping, and at the time I create it, I have full control and visibility into what that mapping function is doing. My opinion... if you think this is less readable it is simply because you find Lambda Expressions in general less readable, which is the fault of the reader and not the code.

Having said that, so far this is the best solution for me in this one scenario, but if there were multiple functions I needed to initialize to make the mapper work this could quickly become just as difficult as the alternative. The point here was to illustrate a practical example of how Lambda Expressions can be used for the better. I spent hours last night trying to find a practical C# tutorial that didn't use a delegate and did something other than add 2 numbers together, and in the end... I simply didn't find one. So hopefully, someone else will find this one here and find it useful.

Comments

Popular Posts