Lambdas, Generics and Delegates. Oh My!!!

By Anthony Jackson. Filed in C#  |  
TOP del.icio.us digg

Lambdas, Generics and Delegates have become some of my favorite programming tools (along with reflection).  It’s unfortunate that their use seems to violate one of the underlying principles I have based my career on; a colleague summed it up nicely with “the belief that a solution reaches a better place when it is as simple as possible”.

So the challenge becomes how to bring these techniques out of the domain of the senior programmer and have them be accessible to a junior audience.  I’ll start by saying that I don’t believe they are that complicated of topics, it’s just that much of what is written about them revolves around the theoretical rather than the practical implementation view point.  So I’m going to attempt to bring them back down to earth.

Generics
Microsoft states it as “Generics allow you to define type-safe classes without compromising type safety, performance, or productivity.”  I know this makes it as clear as mud.

Let’s take a different approach at defining Generics. Let’s say you wanted to create a class that could be applied to multiple “kinds” of data. You don’t care whether the programmer instantiating your class wants to use it with integers, strings or another class you’ve never heard of. The old way would be to simply use the object type, but a few problems would arise:

  1. Type Safety – no guarantee all objects would be the same type
  2. Performance – Each object would need to be “boxed/unboxed” when referenced
  3. Productivity – In order to solve 1 & 2, you would create type specific versions of the class, impacting productivity and creating maintenance issues.

Generics make this unnecessary. Here is the outline for one of my favorite classes that I’ve written using generics:


public class SQLMapperBase<T>
{
protected T Map(IDataRecord record)

public ObservableCollection<T> MapAll(IDataReader reader)

}

I’m sure that taking a look at the definition for this class is very confusing at first.  I’ve left out the actual implementation code as it would just confuse things further and is the subject of a future article I’m working on.  The point of this class is simply to take any object and map its properties to the fields in a record from a database.  Forgetting about how the class actually accomplishes this mapping (reflection), the beauty of this is that I as the developer of this class do not need to know anything about the classes it will be used with.  The person using the class simply needs to make sure that the properties of the class “passed in” as T when the class is instantiated match the fields in the DataReader to be used.

Still not quite following?  It should be easier to follow when we look at the actual usage of an instance of this class:


SQLMapperBase<Customer> customerMapper = new SQLMapperBase<Customer>();
SqlDataReader customerReader = GetCustomersByCriteria("LastPurchaseDate > 1/1/2012");
ObservableCollection<Customer> customers = customerMapper.MapAll(customerReader);

So we’re instantiating a SQLMapperBase object and telling it that it’s going to work with objects of class Customer.  We then get the customer records out of the database and finally ask the instantiated customerMapper to map each record to a Customer object and return all of these Customer objects as an ObservableCollection.

Keep in mind that the real power of this is that SQLMapperBase doesn’t actually know anything about class Customer, nor did SQLMapperBase’s developer need to know anything about it.

Now we can do the exact same thing for the other data we need to extract from the database and turn into a collection of type safe objects.  You see we know for a fact that all objects within the collection are in fact a given type that we specified.  If we specify the class as Customer, all our objects will be of type Customer.  If we were to follow the same pattern of code and specify Product as our class type then in fact all objects within our collection will be of type Product.

While this may not seem important at first glance, it effects how the compiler handles not only this code but the code that comes after.  It allows it to produce more efficient IL code than it would be able to do if these were simply of type object.  Not too mention the fact that while the SQLMapperBase class may seem difficult to follow at first, the code which uses it is relatively easy to follow, once you get used to the syntax and see the <T> as just a different kind of parameter that defines the type of object the class is working with.

Lambdas

Lambdas are simply a short hand way of writing functions.  This fits in very nicely with the viewpoint of the traditional C based programmer.  If you’re not aware their are/were competitions to see who could write the most functionality into a single line of code.  I’m sure based on my opening statement on creating code that is easy to maintain you can see my issue with this viewpoint.

So the question would be why do I have such an interest in Lambdas, if I prefer code that is easy to maintain and readily apparent as to its purpose?  It comes down to another guiding principle that says the less lines of code you have, the less their is to maintain and therefore a lower chance of errors.  Now it should be obvious that these are competing principles and neither are 100% correct in every case; they are more like guidelines.  The other main reason is their interaction with Delegates and Generics which we’ll delve further into shortly.

You’ll often see a simple example given to explain Lambdas:


x => x + 1;
int AddOne(int x)
{
return x++;
}

While line 1 is functionally equivalent to lines 2 through 5; it’s typical usage inside of other expressions is what tends to cause developers to freeze up quickly.  Which leads us to the topic of delegates.

Delegates

We’ll refer back to Microsoft again for a definition: “A delegate is a type that defines a method signature. When you instantiate a delegate, you can associate its instance with any method with a compatible signature. You can invoke (or call) the method through the delegate instance.”

Taking our example from above and modifying slightly:


int AddOne(int x)
{
return x++;
}

delegate int del(int x);

bool AreTheyEquivalent(int x)
{
int y = AddOne(x);

del delegateAddOne = a => a + 1;
int z = delegateAddOne(x);

return y==z;
}

And the answer would be yes they are functionally equivalent.  Outside of our function we create a delegate that can accept an integer and returns an integer.  Within our function we actually create an implementation that does something and then call that implementation to do the work.

This is fairly easy to follow, but doesn’t demonstrate the true beauty of Lambdas and Delegates.  It’s when you combine them with Generics that they really shine.

Let’s think back to creating a class that doesn’t actually know anything about the objects its working with.  We saw how to do this using Generics, but what if you needed to provide functionality that could find a given instance within the collection of objects without knowing anything about the objects.  You could try to create multiple searches that used the first integer property or the first string property for this purpose, but it wouldn’t really be useable in every possible situation.

What if the developer instantiating your class could pass you a delegate that did the comparison work?  After all, they know at that moment what the type of the object is and how to find the one they are looking for.

Hopefully I’ve inspired you to look into these topics further and see how they can help to improve your code and allow more opportunities for code reuse.  I also hope that they don’t seem quite as daunting as they may have when you first started reading it.

About Anthony Jackson

Anthony Jackson has written 10 posts in this blog.

Anthony has been developing software professionally since 1996; working primarily with Microsoft technologies such as C#, VB.NET, ASP.NET and MS SQL Server. He has a varied background working in a number of industries, on teams of different sizes including Enterprise Application Development. He often takes on varied roles for projects as well, quickly adapting to what is needed for a given project and is involved in the full life-cycle of software development. This blog and it's articles are entirely the opinion of the author Anthony Jackson and do not reflect the opinions of his employers or clients past, present or future. He currently works for Avanade as a Senior System Analyst and loves working their, but everything written is still just his opinion and does not necessary reflect on any of his employers or clients past, present or future.

Leave a Reply

You must be logged in to post a comment.