Lessons Learned: Make your ListItemCollection Linq Queryable

I love Linq! In my opinion it is one of the best features that have ever been added to C#, making developers extremely more productive. Really miss it when coding Java ;) . Beside the different Linq providers (LinqToSQL, LinqToEntities, LinqToXml, ...), one of the powerful possibilities is to query collections in memory. I mean, just think how often you have to traverse lists, searching for some specific type, some object with a specific property and so on.

This is cumbersome because there is nothing especially complex in doing those tasks, but rather it reduces to silly typing work. Linq is great, because it gives you the possibility to  naturally express a query and more importantly it allows you to abstract. If you take a look at one of your Linq queries, what you do is to express what should be done but not how. That's up to the specific Linq provider implementation (like making use of deferred execution etc..).

Problem

Often you may encounter the situation where you don't see the famous Where, First, FirstOrDefault etc extension methods appear in your Intellisense popup.  That's mainly because Linq extends on IEnumerable<T> and IQueryable<T> types.
Take for instance ASP.net and the CheckBoxList server control. Suppose you'd like to get all of the checked items by the user. In such a case you would like to query the CheckBoxList.Items collection. That's however not an IEnumerable<T> type but a ListItemCollection which just implements IEnumerable and won't therefore allow to be queried using Linq. So you would end up writing it the old style:
List<ListItem> selectedItems = new List<ListItem>();
foreach(ListItem item : checkBoxList.Items)
{
if(item.Selected == true)
{
selectedItems.Add(item);
}
}

...
Annoying to write and more difficult to read.

Candiate Solution

Instead, writing as a Linq query you could do it like
IEnumerable<ListItem> selectedItems = from item in checkBoxList.Items.AsEnumerable()
where item.Selected == true
select item;
Nice, isn't it, or the even more concise Lambda version
IEnumerable<ListItem> selectedItems = checkBoxList.Items.AsEnumerable()
.Where(x => x.Selected == true);

This is a lot more readable! As mentioned however, ListItemCollection is not of type IEnumerable<T> and there doesn't exist a AsEnumerable function out of the box. The code above used a very simple extension method I've written, that adds the missing functionality:

public static IEnumerable<ListItem> AsEnumerable(this ListItemCollection listItems)
{
foreach (ListItem item in listItems)
{
yield return item;
}
}

An according unit test verifies the correct functioning of the above.

[TestMethod]
public void TestListItemCollection_AsEnumerable_Filtering()
{
//setup
ListItemCollection collection = new ListItemCollection();
collection.Add(new ListItem("text1", "value", false));
collection.Add(new ListItem("text2", "value2", true));
collection.Add(new ListItem("text3", "value3", false));

//execute
IEnumerable<ListItem> filteredItems = collection.AsEnumerable().Where(x => x.Enabled == true);

//verify
Assert.AreEqual(1, filteredItems.Count(), "There should be 1 item filtered");
}
It works, just as expected. But then...once I had the AsEnumerable extension coded, a ReSharper info message icon popped up telling me that my foreach loop could be converted into a Linq expression. Interesting I though and approved the conversion and see there, that was the result:
public static IEnumerable<ListItem> AsEnumerable(this ListItemCollection listItems)
{
return listItems.Cast<ListItem>();
}

Cool :)

Conclusion

Once more, ReSharper taught me new syntax! Basically my AsEnumerable extension isn't needed at all, but instead one can use the Cast() extension method directly to achieve the same effect. The only reason for leaving the AsEnumerable() extension method, is to give your devs an already familiar experience as with some other native collections which already expose an AsEnumerable()method. On the other side, "the less code you write, the less you have to maintain" ;) .
Kindle

Comments

0

Your ad here?