Search code examples
c#collectionsienumerablec#-6.0null-conditional-operator

How to create empty-conditional operator for collections similar to null-conditional operator?


C# 6.0 introduced null-conditional operator, a big win.

Now I would like to have an operator which behaves similarly to it, but for empty collections.

Region smallestFittingFreeRegion = FreeRegions
            .Where(region => region.Rect.W >= width && region.Rect.H >= height)                
            .MinBy(region => (region.Rect.W - width) * (region.Rect.H - height));

Now this blows up if Where returns an empty IEnumerable, because MinBy (from MoreLinq) throws an exception if collection is empty.

Before C# 6.0 this would probably be solved by adding another extension method MinByOrDefault.

I would like to re-write it like this: .Where(...)?.MinBy(...). But this doesn't work because .Where returns an empty collection instead of null.

Now this could be solved by introducing a .NullIfEmpty() extension method for IEnumerable. Arriving at .Where(...).NullIfEmpty()?.MinBy().

Ultimately this seems awkward because returning empty collection has always been preferable to returning a null.

Is there an other more-elegant way to do this?


Solution

  • IMHO, the "most elegent" solution is to re-write MinBy to make it in to a MinByOrDefault

    public static TSource MinByOrDefault<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> selector)
    {
        return source.MinByOrDefault(selector, Comparer<TKey>.Default);
    }
    
    public static TSource MinByOrDefault<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> selector, IComparer<TKey> comparer)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (selector == null) throw new ArgumentNullException("selector");
        if (comparer == null) throw new ArgumentNullException("comparer");
        using (var sourceIterator = source.GetEnumerator())
        {
            if (!sourceIterator.MoveNext())
            {
                return default(TSource); //This is the only line changed.
            }
            var min = sourceIterator.Current;
            var minKey = selector(min);
            while (sourceIterator.MoveNext())
            {
                var candidate = sourceIterator.Current;
                var candidateProjected = selector(candidate);
                if (comparer.Compare(candidateProjected, minKey) < 0)
                {
                    min = candidate;
                    minKey = candidateProjected;
                }
            }
            return min;
        }
    }
    

    I don't see much need for a special operator.