Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extending LINQ to accept nullable enumerables

Tags:

c#

.net

linq

While working with Linq extensions it's normal to see code like this:

IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;
if (enumerable != null)
{
    sum = enumerable.Sum();
}

In order to enhance the code quality, I wrote the following extension method that checks for nullable enumerables and breaks the linq execution.

public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null) yield break;
    foreach (var item in enumerable)
    {
        yield return item;
    }
}

So, I can refactor the code to be like this:

var sum = GetEnumerable().IgnoreIfEmpty().Sum();

My questions now:

  1. What penalties are associated with my extension method at runtime?
  2. Is it's a good practice to extend linq that way?

Update: My target framework is: 3.5

like image 870
Mahmoud Samy Avatar asked Feb 24 '16 08:02

Mahmoud Samy


3 Answers

What penalties are associated with my extension method at runtime?

Your extension method is transformed into a state-machine, so there's the minimal overhead of that, but that shouldn't be noticeable.

Is it's a good practice to extend linq that way?

In your question you state:

While working with Linq extensions it's normal to see code like this (insert enumerable null check here)

And I beg to differ. The common practice says don't return null where an IEnumerable<T> is expected. Most cases should return an empty collection (or IEnumerable), leaving null to the exceptional, because null is not empty. This would make your method entirely redundant. Use Enumerable.Empty<T> where needed.

like image 72
Yuval Itzchakov Avatar answered Nov 11 '22 17:11

Yuval Itzchakov


  1. You're going to have a method call overhead, it will be negligible unless you are running it in a tight loop or a performance criticial scenario. It's but a shadow in comparison to something like a database call or writing to a file system. Note that the method is probably not going to be inlined, since it's an enumerator.
  2. It's all about readability / maintainability. What do I expect to happen when I see GetEnumerable().IgnoreIfEmpty().Sum();? In this case, it makes sense.

Note that with C# 6 we can use the following syntax: GetEnumerable()?.Sum() which returns an int?. You could write GetEnumerable()?.Sum() ?? 0 or GetEnumerable()?.Sum().GetValueOrDefault() to get a non-null integer that will default to zero.

If you are truly concerned with performance, you could also slightly refactor your method so that it's not an enumerator. This may increase the chance of inlining, although I have no idea of the 'arcane' logic of the JIT compiler:

public static IEnumerable<T> IgnoreIfEmpty<T>(this IEnumerable<T> enumerable)
{
    if (enumerable == null) return Enumerable.Empty<T>();
    return enumerable;
}

More generally about extending Linq, I think it is perfectly fine as long as the code makes sense. MSDN even has an article about it. If you look at the standard Where, Select methods in Linq, and forget about the performance optimizations they have in there, the methods are all mostly one-liner methods.

like image 43
Bas Avatar answered Nov 11 '22 16:11

Bas


You can skip the additional extension method and use null coalescing operator - this is what it's for, and a one-time check for nullability should be a lot more efficient than another state machine:

IEnumerable<int> enumerable = GetEnumerable();
int sum = 0;

sum = (enumerable ?? Enumerable.Empty<int>()).Sum();
like image 14
w.b Avatar answered Nov 11 '22 16:11

w.b