Welcome to part 3 in the Powerful Extension Methods series. This article takes a look at a common class in ASP.NET, the System.Web.UI.WebControls.ListItemCollection. The ListItemCollection is used in many places: the ListBox, DropDownList, CheckBoxList, and RadioButtonList all store their items in this type of collection.
ListItemCollection implements IEnumerable, but not the strongly typed IEnumerable<ListItem>. Many collections throughout the base class libraries are that way. One of the disadvantages of implementing only IEnumerable is that when you want to process the collection using LINQ, you must first cast the collection to the desired type. Something like this:
items.Cast<ListItem>()
The Cast<TResult>() function gets called implicitly in LINQ expressions such as this:
var selectedItems = from ListItem item in items
where item.Selected
select item;
The query feels slightly less elegant when written using the LINQ methods directly.
var selectedItems = items.Cast<TResult>().Where(item => item.Selected);
Adding a quick extension method like the one below allows calls to the Where<TResult>() method without including the inelegant call to Cast<TResult>().
/// <summary>
/// Filters a <c>ListItemCollection</c> based on a predicate.
/// </summary>
/// <param name="items">The <c>ListItemCollection</c> to filter.</param>
/// <param name="predicate">A function to test each element for a condition.</param>
/// <returns>An enumerable set of list items filtered by the predicate.</returns>
public static IEnumerable<ListItem> Where(this ListItemCollection items, Func<ListItem, bool>
predicate)
{
return items.Cast<ListItem>().Where(predicate);
}
This allows the simpler call:
var selectedItems = items.Where(item => item.Selected);
The ASP.NET ListItemCollection does not ship with a SelectedItems or SelectedIndices property. We can elegantly compensate for the missing property with two short extension methods.
/// <summary>
/// Filters a <c>ListItemCollection</c> based on whether the item is selected.
/// </summary>
/// <param name="items">The <c>ListItemCollection</c> to filter.</param>
/// <returns>An enumerable set of list items filtered by whether the item is selected.</returns>
public static IEnumerable<ListItem> WhereSelected(this ListItemCollection items)
{
return items.Cast<ListItem>().Where(i => i.Selected);
}
/// <summary>
/// Filters a <c>ListItemCollection</c> based on whether the item is not selected.
/// </summary>
/// <param name="items">The <c>ListItemCollection</c> to filter.</param>
/// <returns>An enumerable set of list items filtered by whether the item is not selected.</returns>
public static IEnumerable<ListItem> WhereNotSelected(this ListItemCollection items)
{
return items.Cast<ListItem>().Where(i => !i.Selected);
}
Often times, it is not the list items themselves that we are interested in, but only their values. A few brief extension methods make for an elegant way to retrieve SelectedValues.
/// <summary>
/// Filters a <c>ListItemCollection</c> based on whether the item is selected, and retrieves each
value.
/// </summary>
/// <param name="items">The <c>ListItemCollection</c> to filter.</param>
/// <returns>An enumerable set of list items filtered by whether the item is selected.</returns>
public static IEnumerable<string> ValuesWhereSelected(this ListItemCollection items)
{
return items.Cast<ListItem>().Where(i => i.Selected).Select(i => i.Value);
}
/// <summary>
/// Filters a <c>ListItemCollection</c> based on whether the item is not selected, and retrieves
each value.
/// </summary>
/// <param name="items">The <c>ListItemCollection</c> to filter.</param>
/// <returns>An enumerable set of list items filtered by whether the item is not selected.</returns>
public static IEnumerable<string> ValuesWhereNotSelected(this ListItemCollection items)
{
return items.Cast<ListItem>().Where(i => !i.Selected).Select(i => i.Value);
}
Collecting selected values is helpful, but setting selected values is also helpful. The next two extension methods enable selecting multiple items provided a list of values or a Lambda expression predicate.
/// <summary>
/// Sets each element in the collection as selected based on whether the item is contained in the
set of values.
/// </summary>
/// <param name="items">The items to update.</param>
/// <param name="values">The values to select. All other items will be deselected.</param>
public static void SetAllSelectedByValue(this ListItemCollection items, IEnumerable<string> values)
{
var hashValues = new HashSet<string>(values);
if (values == null)
{
items.SetAllSelectedIf(i => false);
}
else
{
items.SetAllSelectedIf(i => hashValues.Contains(i.Value));
}
}
/// <summary>
/// Sets each element in the collection as selected based on a predicate.
/// </summary>
/// <param name="items">The items to update.</param>
/// <param name="predicate">A function to test each element for a condition, and update the selected
status of the element to the test result.</param>
public static void SetAllSelectedIf(this ListItemCollection items, Func<ListItem, bool> predicate)
{
foreach (ListItem item in items)
{
item.Selected = predicate(item);
}
}
These few extension methods don't perform any earth-shaking operations, but they do provide a clean, elegant way to operate on ListItemCollection objects.
Happy Coding!