Lessons Learned: Make your ListItemCollection Linq Queryable
3 min read
3 min read
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..).Where
, First
, FirstOrDefault
etc extension methods appear in your Intellisense popup. That's mainly because Linq extends on IEnumerable<T>
and IQueryable<T>
types.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>();Annoying to write and more difficult to read.
foreach(ListItem item : checkBoxList.Items)
{
if(item.Selected == true)
{
selectedItems.Add(item);
}
}
...
IEnumerable<ListItem> selectedItems = from item in checkBoxList.Items.AsEnumerable()Nice, isn't it, or the even more concise Lambda version
where item.Selected == true
select item;
IEnumerable<ListItem> selectedItems = checkBoxList.Items.AsEnumerable()
.Where(x => x.Selected == true);
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;
}
}
[TestMethod]It works, just as expected. But then...once I had the
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");
}
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>();
}
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" ;) .