2
\$\begingroup\$

Brief Description:

A generic extension method that performs the same function as IEnumerable.Any(), but does not throw an error when passed a parameter with a null value.

Explanation:

Often times I will come across code that performs two steps on a nullable collection to make sure it contains elements. The first step checks to make sure the collection is not null, then the second step looks to see that there are greater than zero elements.

For example, to make sure a List<T> meets these qualifications before working through each element, code might look like this:

List<int> intList = new List<int>();

if (
    intList != null &&
    intList.Any()
    )
{
    foreach (int intElement in intList)
    {
        // logic here
. . .

To reiterate, just using .Any() above would be insufficient for the situation where intList hasn't yet been initialized.

Because this is a common occurrence in the code I maintain, I've come up with a generic extension method that performs the same function as the above, in one method:

static class CLR
{
    public static bool Populated<TSource>(this IEnumerable<TSource> source)
    {
        return source != null && source.Any();
    }
. . .

... which renders the first block a little simpler:

if (intList.Populated())
{
    foreach (int intElement in intList)
    {
        // logic here
. . .

Are there suggestions for why this should not be implemented, or why it should be implemented in a different form?

\$\endgroup\$
  • 2
    \$\begingroup\$ In C# 6 you can just do collection?.Any() ;-) \$\endgroup\$ – t3chb0t Aug 12 '16 at 18:11
  • \$\begingroup\$ @t3chb0t nice tip! unfortuantely .NET 3.5. \$\endgroup\$ – Aaron Thomas Aug 12 '16 at 18:14
  • 4
    \$\begingroup\$ Any IEnumerable reference that is null means bad code. Best practice since .net 1.1 has been to yield/return an empty collection when there are no items, not a null reference. This extension method is working around a severe problem with the code, rather than solving it. I'd much rather hunt down instances of code returning null and change them to return an empty enumerable instead; that way you fix what's wrong instead of introducing a way to make the wrong seem right... just my two cents ;-) \$\endgroup\$ – Mathieu Guindon Aug 12 '16 at 18:32
  • \$\begingroup\$ @Mat'sMug wise words... unfortunatelly you can't count on it when working with 3rd party libs that hasn't been reviewed here ;-) \$\endgroup\$ – t3chb0t Aug 12 '16 at 18:38
  • 1
    \$\begingroup\$ Null is not Empty by Eric Lippert. And Framework Design Guidelines 2nd edition, p.256, and this Stack Overflow question ;-) \$\endgroup\$ – Mathieu Guindon Aug 12 '16 at 20:57
1
\$\begingroup\$

With C# 6 you could simply do collection?.Any()

but in older versions I use a more generic solution for the same thing because there a more situations where something could be null.

This is my helper:

static class Helpers
{
    public static TResult If<TArg, TResult>
    (
        this TArg arg,
        Func<TArg, bool> predicate,
        Func<TArg, TResult> trueFunc,
        Func<TArg, TResult> falseFunc = null
    )
    {
        return 
            predicate(arg) 
            ? trueFunc(arg) 
            : (falseFunc == null ? default(TResult) : falseFunc(arg));
    }

    public static TResult IfNotNull<TArg, TResult>(
        this TArg arg, 
        Func<TArg, TResult> trueFunc
    ) where TArg : class
    {
        return arg.If(x => x != null, x => trueFunc(x));
    }
}

Examples:

var foo = (List<int>)null;
var hasAny1 = foo.If(x => x != null, x => x.Any()); // false
var hasAny2 = foo.IfNotNull(x => x.Any()); // false
\$\endgroup\$
3
\$\begingroup\$

My point of view is different depending on whether the the code that's returning null enumerables is a 3rd-party API or your own code.


3rd-Party

If the code that's returning null enumerables is a 3rd-party API, then I only have a few minor points to mention:

  • CLR is a very bad and misleading name for a class. In the .NET world CLR stands for Common Language Runtime, the part that's responsible for JIT-compiling CIL code (which all .net languages compile to), a bit like the JVM does for byte code / compiled Java.
  • I would have named Populated with a little Is prefix to read like IsPopulated, because I like Boolean functions/properties to read that way. But that's personal preference.

The implementation itself is nice & clean, nothing to say about it (other than the C# 6 trick in t3chb0t's answer); I like that you named the this parameter source, it lines up with other framework-provided IEnumerable<T> extension methods, e.g. LINQ. TSource seems a bit verbose when T would have been sufficient, but I like descriptive generic type parameters so I'd leave it as is.

In the sample code, you have this:

if (
    intList != null &&
    intList.Any()
    )

That looks terrible; an if condition should fit one single line:

if (intList != null && intList.Any())

Very long conditions that actually need to be multiple lines, should be extracted out of the if statement and into their own function with a meaningful name that captures the meaning of all these conditions:

if (IsValid(intList))

You get the idea; that's usually more commonly seen with LINQ queries and predicates though, but the concept is the same: if it's too much clutter to be readable, move it elsewhere and abstract it behind a meaningful identifier.


Your own

On the other hand, if the code that's returning null enumerables is under your control, there's a bigger issue.

Per Framework Design Guidelines 2nd Edition, p.256:

DO NOT return null values from collection properties or from methods returning collections. Return an empty collection or an empty array instead.

See this SO Q&A for more information about whether it's better to return null or an empty collection.

Instead of introducing an extension method to work around a serious design issue in your code base, you should be hunting down instances of IEnumerable<T>-returning methods and properties in that code base, and fixing them so that they return an empty enumerable instead of a null value:

return Enumerable.Empty<Foo>();

Then all you need to do is get rid of redundant but inoffensive null-checks everywhere in the code base, and new code wouldn't need any such null-checks.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.