Extension methods have been with us since the arrival of LINQ back in 2007.
They were primarily introduced to support LINQ, but extension methods are quite awesome in their own right.
When used thoughtfully, they can really improve your code.
Today, I want to highlight some interesting extension method ideas to simplify processing an IEnumerable record set.
Filtering ‘Price Bands’ data
The scenario for this example is lifted from my current job whereby we match ‘Price bands’ from our database to find the correct Term for a particular loan application.
I won’t go into the actual business logic (FirstPass etc) and we will remove any error handling logic as well to keep it simple.
The ‘Traditional’ Approach
The class below shows the Traditional approach where if else
logic is used to determine the next steps.
public IEnumerable<PriceBand> Filter(IEnumerable<PriceBand> priceBands, decimal allocatedWallet, decimal walletLimit, decimal purchaseAmount)
{
var results = FirstPass(priceBands, allocatedWallet, purchaseAmount);
if (!results.Any()) {
results = SecondPass(priceBands, allocatedWallet);
}
if (!results.Any()) {
results = SecondPass(priceBands, allocatedWallet);
}
if (!results.Any()) {
results = ThirdPass(priceBands, walletLimit);
}
foreach (var match in results) {
LogResult(match);
}
return results;
}
The ‘Extension Method’ Approach
Using extension methods we chain together logic making it much simpler to reason about…
public IEnumerable<PriceBand> Filter(IEnumerable<PriceBand> priceBands, decimal allocatedWallet, decimal walletLimit, decimal purchaseAmount)
{
return FirstPass(priceBands, allocatedWallet, purchaseAmount)
.Else(() => SecondPass(priceBands, allocatedWallet))
.Else(() => ThirdPass(priceBands, walletLimit))
.Then(LogResult);
}
The Else
method only runs if the earlier method returns nothing.
At the end of the chain we add our LogResult
method which runs last over the resulting set of Price Bands.
NOTE: The Filter
method won’t actually do anything until the resulting IEnumerable is executed.
Either by a call to .ToArray() or ToList()
Cool - Now show me the code
public static IEnumerable<T> Then<T>(this IEnumerable<T> self, Action<T> some)
{
if (self.NotNullOrEmpty() && some.NotNullOrDefault())
self.Each(some);
return self;
}
public static IEnumerable<T> Else<T>(this IEnumerable<T> self, Func<IEnumerable<T>> some)
{
return self.IsNullOrEmpty() && some.NotNullOrDefault() ? some.Invoke() : self;
}
public static void Each<T>(this IEnumerable<T> items, Action<T> action)
{
foreach (var item in items)
action(item);
}
public static bool IsNullOrEmpty<T>(this IEnumerable<T> items)
{
return items == null || !items.Any();
}
public static bool IsNullOrDefault<T>(this T item)
{
return EqualityComparer<T>.Default.Equals(item, default(T));
}
public static bool NotNullOrDefault<T>(this T item)
{
return !IsNullOrDefault(item);
}
Final thoughts
Extension methods are a great addition to anyone’s tool-belt. But they should be used thoughtfully.
Avoid the temptation to put lots of business logic inside them. Keep them simple and also consider performance costs if using them in a hot path in your code.