Friday, August 3, 2012

.NET LINQ and Lambdas Step by Step

LINQ is powerful, really powerful, can make code more readable, and can help you write code faster. However, LINQ has a syntax hurdle, lambda expressions. Lambdas are one of those constructs that trips up developers new to .NET. Luckily, there's a large return on the investment of learning them. So, let's learn how they work step by step.

Consider it without any of the LINQ/lambda mumbo jumbo:
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    foreach (int x in numbers)
    {
        if (x < 0)
        {
            result = true;
            break;
        }
    }
    return result;
}
Now, let's factor out a method and call it IsNegative:
bool IsNegative(int x)
{
    return x < 0;
}
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    foreach (int x in numbers)
    {
        if (IsNegative(x))
        {
            result = true;
            break;
        }
    }
    return result;
}
The code above works great. However, there's a lot of it, thus more opportunity for bugs. Not only that, but it is a common algorithm to determine if any element in an array satisfies a certain condition (in this case, whether any element is negative). Surely there's a way to make the code simpler and more readable. There is. This is where LINQ shines! Let's use LINQ, but no Lambdas yet:
bool IsNegative(int x)
{
    return x < 0;
}
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    result = numbers.Any(IsNegative);
    return result
}
We are calling a LINQ method called Any. The Any method takes a single argument which is yet another method (in our case, the IsNegative method). That method must take a single int argument (because the array contains only integers) and return a bool. The functionality of Any is to return true if any element in the array matches the specified condition (in our case, if the number is negative).

Ok great, we simplified the code and reduced the chance for bugs to sneak in. However, the IsNegative method may not need to be used by anyone other than the IsAnythingNegative method. In this case, it is just overhead to have to define a separate IsNegative method, and it fragments the code. So, let's accomplish the same thing with an anonymous method defined in-line.
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    result = numbers.Any
    (
        (int x) =>        // anonymous method takes a single argument
        {
            return x < 0; // method body is a single statement
        }
    );
    return result
}
Don't freak out yet, this is just syntax. We are defining an anonymous method which takes a single int argument named x. The method body consists of a single statement: return x < 0;. And thus, the method returns bool. This anonymous method is just like IsNegative. Guess what, you just wrote a lambda expression!

Ok, x must be int since numbers only contains int, so let's remove that redundancy:
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    result = numbers.Any
    (
        (x) =>            // int can go away
        {
            return x < 0;
        }
    );
    return result
}
.NET provides a shortcut for lambdas which take a single argument and return a single result:
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    result = numbers.Any
    (
        x =>              // () can go away
                          // {} can go away
            x < 0         // return can go away and ; can go away
    );
    return result
}
Whitespaces removed:
bool IsAnythingNegative(int[] numbers)
{
    bool result = false;
    result = numbers.Any(x => x < 0);
    return result
}
And now, we finally arrived at the all-too-confusing lambda expression => < ( > % #(*^!^$%$%#@**($ syntax.

Here are some other fun methods to play with:
bool areAllEven = numbers.All(x => x % 2 == 0);
int[] justTheNegatives = numbers.Where(x => x < 0).ToArray();
Hopefully this helps ease you into the new syntax.

1 comment:

  1. I am glad I had a few months to pick Linq up when it was released with the 3.0 framework, it seems like for ever ago. Its very powerful, especially when you look at plinq and the parallel task library.

    ReplyDelete