Closures in C#: Lambdas and Predicates

In the last post, we had a look at the Func family of delegates in C#. We saw how a function can be created dynamically within the program and passed as a parameter to another function.

A common requirement in any program that deals with searching a list is that we compare an element of the list with some reference value and return ‘true’ if there is a match or ‘false’ otherwise. A function that performs an operation and returns a boolean value is known in logic as a predicate. C# provides the Predicate delegate type which allows a predicate function to be defined and passed as a parameter to another function.

A Predicate is just a special case of a Func, in that it takes only a single parameter and always returns a bool. Thus Predicate<T> is equivalent to Func<T, bool>. Note, though, that there is only one Predicate delegate, which takes only a single generic type, unlike Func, of which there are 17 varieties for various numbers of input parameters.

As a simple example of the use of a Predicate, we’ll have a look at the built-in List generic type, which is part of the System.Collections.Generic namespace in .NET. Consider the following code.

  class TestClosure
  {
    Predicate<int> intPred;
    private void makePredWithDelegate()
    {
      int compare = 10;
      intPred = delegate(int number)
      {
        return number > compare;
      };
    }

    static void Main(string[] args)
    {
      List<int> intList = new List<int> { 5, -39, 123, 8, 89, 63, 102, -934 };

      TestClosure testClosure = new TestClosure();
      testClosure.makePredWithDelegate();
      List<int> largeNums = intList.FindAll(testClosure.intPred);
      Console.WriteLine("makePredWithDelegate: ");
      foreach (int number in largeNums)
        Console.Write(number + " ");
      Console.WriteLine();
    }
  }

On line 15, we create a List of ints and initialize it with a few numbers. Then we create a TestClosure object on line 17 and call its makePredWithDelegate method. On line 3, we declare a Predicate delegate which points to a function that takes an int as its one argument. The makePredWithDelegate method constructs the Predicate in the same way as we built the Func in the previous post. The Predicate thus tests if number is greater than compare. There are a couple of interesting things about the way we’ve done this.

First, note that we’ve made intPred an instance variable within the TestClosure class, rather than returning it from makePredWithDelegate as we did in discussing Func earlier. Second, we’ve introduced what looks like a pointless local int variable, compare, inside the method, and then used this variable in building the Predicate.

If you recall your scoping rules for local variables, this setup should set off a few alarm bells. We have created intPred inside a method, and we’ve used a local method variable in doing this. Surely when the method call ends, compare will fall out of scope and intPred will therefore contain an invalid reference.

In fact, this is not what happens. When a Predicate (or, more generally, a Func) is created in this way, it captures or encloses the environment that was current when it is created. This means that the compare variable is captured as part of intPred‘s definition and, even though we can’t refer to compare explicitly after the makePredWithDelegate method ends, compare lives on within the Predicate and it will not be garbage-collected until intPred itself falls out of scope. This is what is meant when we say that a closure captures or closes over its environment: all data at the time of the Predicate’s creation are frozen and preserved for the life of the Predicate.

Once we’ve created the Predicate, we can pass it to the FindAll() method on line 19. This method expects a Predicate argument whose data type is the same as the type contained within the List, in this case, an int. The Predicate is used as the test condition for finding objects in the List, and the FindAll() method returns a new list containing all elements in the original list that satisfy the condition. We then just print these out.

Admittedly this is an artificial example, since it’s unlikely we would ever write code like this, but it illustrates the important point that dynamic function creation in C# performs this capturing or closing activity.

While we’re at it, we might as well mention a language feature that was introduced with C# version 3: the lambda. The name ‘lambda’ is taken from ‘lambda calculus’ in logic theory, but basically it’s another name for a closure. In C#, a new operator is introduced to allow concise definitions of predicates, which we’ll illustrate with a brief example.

Insert the following lines of code in the Main() function above, after line 23:

      largeNums = intList.FindAll(num => num > 10);
      Console.WriteLine("Lambda: ");
      foreach (int number in largeNums)
        Console.Write(number + " ");
      Console.WriteLine();

This code actually does the same thing to the intList that the earlier code did, but it’s obviously a lot shorter.  In fact, we’ve replaced the explicit creation of a Predicate object by the single bit of code: num => num > 10. This introduces the lambda operator => which should be read as goes to. You’ll note that the variable num isn’t explicitly declared anywhere; it is in fact declared implicitly by being on the left of the => operator. Its data type is inferred from its location within the FindAll() method: since the method is being called on a List containing ints, this variable is assumed to be an int. In other situations where it isn’t obvious (either to the compiler or the human) what data type is intended, you can state it explicitly as in (int num) => num > 10.

This input parameter is then used in the expression num > 10, which evaluates to a bool value, and is taken as the return value of the function.

What is happening here is that an anonymous function has been created and a Predicate pointing to that function has been overlaid onto it, all in that one concise bit of code.

It is possible to write lambda statements as opposed to the lambda expression we have just written by enclosing the statement(s) within braces in the usual way. A more verbose way of writing the above lambda expression is

      largeNums = intList.FindAll((int num) => { bool result = num > 10; return result; });

It’s important to keep in mind that the => operator returns a delegate, not a function. The delegate points to the function whose code has been written to the right of the => operator. To make this clear, here is yet another way of writing the above code. We can replace the method makePredWithDelegate with another method called makePredWithLambda, which looks like this:

    private void makePredWithLambda()
    {
      int compare = 10;
      intPred = number => number > compare;
    }

This makes an explicit assignment of the result of the => operator to intPred, demonstrating that it has produced a Predicate.

Although in principle we could write an anonymous function of any length as the right operand of the lambda operator, to keep code readable, it’s a good idea to use it only for relatively short functions.

The lambda operator can also be used to create any delegate of the Func type, with any number of arguments up to 16. For example, if we declared a Func that takes two arguments and returns an int:

    Func intFunc;

then we could use a lambda expression to provide a delegate of this type, as in:

      intFunc = (int num, string str) => str.Length + num;

The data type of the result of the expression must match the data type of the Func, of course. We could also use a lambda statement here, provided it returned the correct data type (an int in this case).

Finally, it’s worth knowing that there is parallel set of 17 delegates called Action which mirror the Func delegates except that they point to void functions. These too can be initialized with a lambda expression or statement. For example we can declare a delegate for a void function that takes an int and a string as arguments:

    Action intAction;

We can use a lambda operation to initialize this delegate, provided that the result of the operation is void, as in

      intAction = (int num, string str) => Console.WriteLine("Sum = " + (str.Length + num));

Code for this post available here.

Advertisements
Post a comment or leave a trackback: Trackback URL.

Trackbacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: