There is code what we type over and over again without realizing how many mistakes can be made in a few lines. Let’s take the example where we want to remove items from a collection. Intuitively, the code should be something like:
var orders = new List<Order> {
new Order { Total = 100 },
new Order { Total = 150 },
new Order { Total = 200 }};
foreach (var order in orders)
{
if (order.Total > 100)
{
orders.Remove(order);
}
}
However, this will fail miserably with an “InvalidOperationException: Collection was modified; enumeration operation may not execute.”
In fact, it doesn’t fail on the Remove() call, it fails on the MoveNext() as we can’t enumerate a modified collection or can’t modify a collection while enumerating. In order to work correctly, we learned that we need to first keep track of the items to remove and then enumerate through those and remove them from the collection:
var orders = new List<Order> {
new Order { Total = 100 },
new Order { Total = 150 },
new Order { Total = 200 }};
var ordersToRemove = new List<Order>();
foreach (var order in orders)
{
if (order.Total > 100)
{
ordersToRemove.Add(order);
}
}
foreach (var order in ordersToRemove)
{
orders.Remove(order);
}
Wow! That is a huge amount of code. At least it can be refactored in a generic extension method:
public static class CollectionExtensions
{
public static void Remove<T>(
this ICollection<T> collection,
Func<T, bool> predicate)
{
var itemsToRemove = new List<T>();
foreach (var item in collection)
{
if (predicate(item))
{
itemsToRemove.Add(item);
}
}
foreach (var item in itemsToRemove)
{
collection.Remove(item);
}
}
}
And then be used as:
orders.Remove(o => o.Total > 100);
In Umbrella, we go the extra mile and “simplify” the implementation of the Remove() extension method and dog food our own extensions:
public static void Remove<T>(
this ICollection<T> collection,
Func<T, bool> predicate)
{
collection
.Where(predicate)
.ToList()
.ForEach(item => collection.Remove(item));
}
What basically happens here is that we:
- Filter the original collection to keep only the items to remove (Sounds familiar?)
- Copy those items to a temporary list (Remember?)
- For each of those items, call Remove() on the original collection (Rings a bell?)
() => “Francois”