如何为类似于空条件运算符的集合创建空条件运算符?

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

C# 6.0 引入了空条件运算符,大获全胜。

现在我想要一个与它行为相似的运算符,但用于空集合。

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

现在,如果 Where returns 一个空的 IEnumerable,这就会爆炸,因为如果集合为空,MinBy(来自 MoreLinq)会抛出异常。

在 C# 6.0 之前,这可能会通过添加另一个 扩展方法 MinByOrDefault.

来解决

我想重新写成这样:.Where(...)?.MinBy(...)。但这不起作用,因为 .Where returns 一个 空集合 而不是 null.

现在可以通过为 IEnumerable 引入 .NullIfEmpty() 扩展方法来解决这个问题。到达 .Where(...).NullIfEmpty()?.MinBy().

最终这看起来很尴尬,因为返回 空集合 总是比返回 null.

更可取

还有其他更优雅的方法吗?

恕我直言,"most elegent" 解决方案是 re-write MinBy 使其进入 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;
    }
}

我认为不需要专门的操作员。

只需使用 DefaultIfEmtpy 定义一个默认项,如果它为空则放入序列中:

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

当然,如果您想提供自己的默认值以在类型的默认值不是您想要的情况下使用,您当然可以使用接受第二个参数的重载。