Archive

Archive for the ‘Lambda Expressions’ Category

A Good, practical LINQ example

January 17, 2008 14 comments

You may recall that one of my New Year’s Resolutions was to find and participate in a .NET Forum. Well, I have joined several, and have even posted a few times. While perusing the C# Corner forums today, I found this post about not being able to break out of a loop. The code was using one of my favorite techniques for Windows Forms, the ErrorProvider. I like ErrorProvider and use it quite a bit, and I have done what the poster was trying to do many times: loop through the Controls on a Form and find out whether any of them have an active error message. If they do, then I use that information to warn the user or set some Form state so that it is apparent that an error exists.

My answer does not specifically address the issue the poster brought to the table, but the post got me to thinking: we should be able to use LINQ to solve this problem. Here is a typical example of the loop construct:

bool hasErrors = false;
foreach (Control c in this.Controls)
{
    if (!"".Equals(errorProvider1.GetError(c)))
    {
        hasErrors = true;
        break;
    }
}

In looking at this, it appears we have a good case for using LINQ: we have a Collection and we want to query that Collection for information about itself. Seems it should be pretty straightforward, and ultimately it is, but in this particular case it took a little trial and error to hack it out. Since it wasn’t as simple as I originally thought it would be, let’s walk through it in stages.

Build a Queryable Object

The first step is to try and build a LINQ query of all the Controls on the Form:

var q = from c in this.Controls
        select c;

When I tried to compile this, I got an error I did not expect:

Could not find an implementation of the query pattern for source type ‘System.Windows.Forms.Control.ControlCollection’. ‘Select’ not found. Consider explicitly specifying the type of the range variable ‘c’.

Naturally, I reexamined the code and couldn’t find anything odd about it, but what did seem odd was that I knew this.Controls was a Collection, and LINQ is supposed to work on Collections, right? Wrong!

Getting a Sequence from a Collection

If you read my recent LINQ to Objects post you may recall that LINQ does not work on Collections, but rather Sequences. It soon dawned on me that ControlCollection does not qualify as a Sequence because it does not implement IEnumerable<T>, but rather IEnumerable. This led me to uncharted territory: how do I get an IEnumerable<T> from an IEnumerable? Well, there was definitely some trial and error, and a lot of IntelliSense browsing, but I finally figured out an elegant solution.

Microsoft was gracious enough to provide an IEnumerable extension method (my favorite new feature) that will cast an IEnumerable to an IEnumerable<T>. Fittingly, this method is named Cast<T>(). Here is the code that will transform our ControlCollection into a usable Sequence:

var q = from c in this.Controls.Cast<Control>()
        select c;

Now we have a LINQ query object representing all the Controls on our Form.

Querying the Queryable Object

Now that we have a Sequence instead of a Collection to work with, this could come in quite handy. In fact, this would be a good candidate for a ControlCollection Extension Method in its own right. In the meantime, now that we have a queryable object, we have several options for how to complete our task. One way is to query the entire Sequence when we need it. I’m going to use our friend the Lambda Expression to find out if there are any Controls with Errors in the errorProvider:

// Query as needed
MessageBox.Show(q.Count(c => !"".Equals(errorProvider1.GetError(c))) > 0 ? "There are errors!" : "No errors!");

Again we are using a supplied Extension Method called Count<>() and passing it a Func<> defined by our Lambda Expression. If the count is greater than 0, then we have errors, else we do not.

This approach is especially handy if you may need to query the list in multiple ways. In our case, however, we know that this criteria is the only one we will need, so we have the option to embed our Lambda in the LINQ statement itself. To do so, we will use yet another Extension Method called Where:

var q = from c in this.Controls.Cast<Control>()
        .Where(c => !"".Equals(errorProvider1.GetError(c)))
        select c;

You’ll notice that this is the same logic just implemented as part of the LINQ statement. Now our consuming code is a bit simpler because our list is defined as only the items with messages in the ErrorProvider:

// Now we don't need the Lambda
MessageBox.Show(q.Count() > 0 ? "There are errors!" : "No errors!");

Here is the complete code block:

var q = from c in this.Controls.Cast<Control>()
        .Where(c => !"".Equals(errorProvider1.GetError(c)))
        select c;
MessageBox.Show(q.Count() > 0 ? "There are errors!" : "No errors!");

Hopefully, as you experiment with these new features, you will come to appreciate how they all complement each other. This example turns out to be fairly simple, even though it took a little effort to work through. In it, we have taken advantage of Extension Methods, LINQ, and Lambda Expressions because they all work together. While these are all great additions on their own, the cumulative power they represent is outstanding. I guess .NET really is worth more than the sum of its parts.