Jonas Stawski

Everything .NET and More

Traveling Through Time: from Delegates to Anonymous Methods to Lambda Expressions

We are very close to the release (February 2008) of Visual Studio 2008 and .NET 3.5 which will include both C# 3.0 and VB 9.0. Now we can look back in time and see where the .NET framework has taken us.

Delegates

In .NET 1.x we were introduced to delegates. simply put a delegate is a pointer to a function (thanks Matt Winkler for that one.) So basically you have a function that can be assigned to another function or object. Let's look at the following code:

public delegate bool StringCondition(string s);
static void Main(string[] args)
{
  string[] names = { "Jonas", "John", "Dave", "Joe", "Alexandra" };
  Console.WriteLine("Starts With A:");

  PrintNames(names, new StringCondition(StartsWithA));
  Console.WriteLine();
  Console.WriteLine("Starts With J:");
  PrintNames(names, new StringCondition(StartsWithJ));
  Console.WriteLine();
  Console.WriteLine("Is Less than 5 Chars:");
  PrintNames(names, new StringCondition(IsLessThan5Chars));
  Console.ReadLine();
}

private static void PrintNames(string[] names, StringCondition condition)
{
  for (int i = 0; i < names.Length; i++)
  {
    if (condition(names[ i ]))
      Console.WriteLine(names[ i ]);
  }
}

private static bool StartsWithA(string s)
{
  return s.StartsWith("A");
}

private static bool StartsWithJ(string s)
{
  return s.StartsWith("J");
}

private static bool IsLessThan5Chars(string s)
{
  return (s.Length < 5);
}

The output of this code is simply:
Starts With A:
Alexandra

Starts With J:
Jonas
John
Joe

Is Less than 5 Chars:
John
Dave
Joe

From the code below we can see the power that delegates give us. We can simply pass in different delegates to the PrintNames functions based on what we want to display and it will use that delegate to decide whether it should display it or not. Without delegates we would have had to write the same code with if or switch statements.

Anonymous Methods

Then came .NET 2.0 or C# 2.0 and introduced a new concept called Anonymous Methods. Anonymous Methods are very similar to delegates, but instead of declaring the method and a delegate you can have the delegate inline. For those of you that are JavaScript lovers it's like an inline function. So the same code above can be rewriten like this:

public delegate bool StringCondition(string s);
static void Main(string[] args)
{
  string[] names = { "Jonas", "John", "Dave", "Joe", "Alexandra" };
  Console.WriteLine("Starts With A:");

  PrintNames(names, delegate(string s) { return s.StartsWith("A"); });
  Console.WriteLine();
  Console.WriteLine("Starts With J:");
  PrintNames(names, delegate(string s) { return s.StartsWith("J"); });
  Console.WriteLine();
  Console.WriteLine("Is Less than 5 Chars:");
  PrintNames(names, delegate(string s) { return s.Length < 5; });
  Console.ReadLine();
}

You can see here that there's no need to write a function, the delegate is passed in inline. So we accomplished the same thing in more compacted code. The compiler actually creates a function with a random name and passes a new delegate with that function. The reason why they are called Anonymous Methods is because they have no name and therefore they are anonymous to us, not to the compiler. Another feature of Anonymous Methods is that you can use any variable of the parent function within the anonymous method/function.

Lambda Expressions

Now we are in the edge of the .NET 3.5/C# 3.0 era and we have a new feature in place: Lambda Expressions. If you paid attention to the title of this post then you can have a small idea of what Lambda Expressions are capable of doing. Yep, you guessed right. Lambda Expressions are similar to Anonymous Methods, but can be written in a even more compacted way:

public delegate bool StringCondition(string s);
static void Main(string[] args)
{
  string[] names = { "Jonas", "John", "Dave", "Joe", "Alexandra" };
  Console.WriteLine("Starts With A:");
  PrintNames(names, (name => name.StartsWith("A")));
  Console.WriteLine();
  Console.WriteLine("Starts With J:");
  PrintNames(names, (name => name.StartsWith("J")));
  Console.WriteLine();
  Console.WriteLine("Is Less than 5 Chars:");
  PrintNames(names, (name => name.Length < 5));
  Console.ReadLine();
}

So the code above is similar to the previous one, but with different syntax. The syntax of a Lambda Expressions is in the form of

Parameters => Expression

So our examples (name => name.StarsWith("A")) is saying "test whether the input name starts with 'A'". "What is 'name'?" you might ask. Just the name of the parameter. The value is the one passed from the PrintNames function: condition(names[ i ]).
As a matter of fact we can get rid of the delegate altogether and change the signature of the PrintNames function to be like this:

private static void PrintNames(string[] names, Func<string, bool> condition)

Func is a delegate type built in the System namespace and it has a return type specified as the last generic parameter and it allows up to 4 parameters to be supplied as the input parameters. In this case I only need one.

There is something else we gain with Lambda Expressions: they can be used as Expression Trees. Consider the following lines:

Expression<Func<string, bool>> condition1 = (name => name.Length < 5);
Func<string, bool> condition2 = (name => name.Length < 5);

Console.WriteLine(condition2("Jonas"));

This will print false, but what on earth is the condition1 and why does it exist? Condition1 is a Lambda Expression that will be represented at runtime as an Expression tree. Why do we need Expression Trees? Because of LINQ. I will not get into that now, but i'm going to leave you with some nice things about this Expression type. You can do the following:

BinaryExpression body = (BinaryExpression)condition1.Body;
MemberExpression left = (MemberExpression)body.Left;
ConstantExpression right = (ConstantExpression)body.Right;

Console.WriteLine("{0} {1} {2}",
left.Member, body.NodeType, right.Value);

This will output: "Int32 Length LessThan 5"

It is nice when you reach a point when you can look back and see the evolution of things. In this case we see how in each version of the framework we get new features that lets the developer pick and choose how to accomplish the same task. Whether you like to have your code neat, write less code or have more efficient code it's your decision, the outcome will be the same.

Happy Programming!

Comments (1) -

It's great to see the language features put in their evolutionary context, especially for those of us who  haven't used delegates and anon methods to any great degree. You finally get a sense of the motivation for such features.

Reply

Add comment

biuquote
Loading