Functional Collection Parameters: Handling the null collection
One of the interesting cases where I've noticed we tend to avoid functional collection parameters in our code base is when there's the possibility of the collection being null.
The code is on the boundary of our application's interaction with another service so it is actually a valid scenario that we could receive a null collection.
When using extension methods, although we wouldn't get a null pointer exception by calling one on a null collection, we would get a 'source is null' exception when the expression is evaluated so we need to protect ourself against this.
As a result of defending against this scenario we have quite a lot of code that looks like this:
public IEnumerable<Foo> MapFooMessages(IEnumerable<FooMessage> fooMessages) { var result = new List<Foo>(); if(fooMessagaes != null) { foreach(var fooMessage in fooMessages) { result.Add(new Foo(fooMessage)); } } return result; }
The method that we want to apply here is 'Select' and even though we can't just apply that directly to the collection we can still make use of it.
private IEnumerable<Foo> MapFooMessages(IEnumerable<FooMessage> fooMessages) { if(fooMessages == null) return new List<Foo>(); return fooMessages.Select(eachFooMessage => new Foo(eachFooMessage)); }
There's still duplication doing it this way though so I pulled it up into a 'SafeSelect' extension method:
public static class ICollectionExtensions { public static IEnumerable<TResult> SafeSelect<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector) { return source == null ? new List<TResult>() : source.Select(selector) ; } }
We can then make use of this extension method like so:
private IEnumerable<Foo> MapFooMessages(IEnumerable<FooMessage> fooMessages) { return fooMessages.SafeSelect(eachFooMessage => new Foo(fooMessage)); }
The extension method is a bit different to the original way that we did this as I'm not explicitly converting the result into a list at the end which means that it will only be evaluated when the data is actually needed.
In this particular case I don't think that decision will have a big impact but it's something interesting to keep in mind.
Another way around this is an extension method to IEnumerable that will pass through non-null source and return Enumerable.Empty() for a null source:
IEnumerable EmptyIfNull(this IEnumerable source)
{
if (source == null)
return Enumerable.Empty();
return source;
}
Then your function can look like this:
private IEnumerable MapFooMessages(IEnumerable fooMessages)
{
return
fooMessages
.EmptyIfNull()
.Select(eachFooMessage => new Foo(fooMessage));
}
Chris Ammerman
17 Jun 09 at 1:45 am
Arg, all my generics got dropped by the sanitizer….
I also forgot to note that the EmptyIfNull approach is really nice because then all of the non-aggregatory IEnumerable operations will automatically work because they all know how to handle an empty collection! So, just one function to enable all operations, instead of one for each.
Chris Ammerman
17 Jun 09 at 1:48 am
That's a pretty neat idea, hadn't thought of that. I guess that also has the benefit of making it more obvious what's going on
Mark Needham
17 Jun 09 at 7:33 am
I can't remember if you've written about the null-coalescing operator '??', but I've been using it a lot to remove all those annoying 'if (x == null)'s
return (source ?? new List() ).Select(selector) ;
Iain B
17 Jun 09 at 8:04 pm
[...] written previously about the ways I've been making use of functional collection parameters in my code but what I hadn't really considered was that the way of thinking [...]
Functional Collection Parameters: A different way of thinking about collections at Mark Needham
19 Jun 09 at 11:29 pm