带有默认值的 C# LINQ SelectMany

C# LINQ SelectMany with Default

我正在寻找一种优雅的解决方案,以将集合中的子集合聚合成一个大集合。我的问题是某些子集合可能为空。

EG:

var aggregatedChildCollection = parentCollection.SelectMany(x=> x.ChildCollection);

如果任何子集合对象为空,这将引发异常。一些备选方案是:

// option 1
var aggregatedChildCollection = parentCollection
    .Where(x=>x.ChildCollection != null)
    .SelectMany(x => x.ChildCollection);

// option 2
var aggregatedChildCollection = parentCollection
    .SelectMany(x => x.ChildCollection ?? new TypeOfChildCollection[0]);

两者都可以,但我正在对父级的相当多的子集合执行特定操作,而且它变得有点笨拙。

我想要的是创建一个扩展方法来检查集合是否为空,如果是则执行选项 2 的操作 - 添加一个空数组。但是我对 Func 的理解还没有达到知道如何编写此扩展方法的程度。我知道我想要的语法是这样的:

var aggregatedChildCollection = parentCollection.SelectManyIgnoringNull(x => x.ChildCollection);

是否有一种简单的扩展方法可以实现此目的?

您可以使用自定义扩展方法:

public static IEnumerable<TResult> SelectManyIgnoringNull<TSource, TResult>(
    this IEnumerable<TSource> source, 
    Func<TSource, IEnumerable<TResult>> selector)
{
    return source.Select(selector)
        .Where(e => e != null)
        .SelectMany(e => e);
}

并像这样使用:

var aggregatedChildCollection = parentCollection
    .SelectManyIgnoringNull(x => x.ChildCollection);

如果 ParentCollection 是您自己的 class,您还应该能够为 class 添加默认构造函数,例如:

public ParentCollection{
    public ParentCollection() {
        ChildCollection = new List<ChildCollection>();
    }
}

这应该可以防止 null ref 异常,如果其中没有任何内容,则会为您提供一个空列表。至少这适用于 EF 模型。

你的 "option 2" 是我会做的,稍作调整:使用 Enumerable.Empty() 而不是创建空数组来减少你正在创建的新对象的数量。

我使用了一个简单的扩展方法 Touch() —— 以 *nix 实用程序命名 —— 来保持 LINQ 语法流并减少输入:

public static IEnumerable<T> Touch<T>(this IEnumerable<T> items) =>
    items ?? Enumerable.Empty<T>();

并将其用作:

var aggregatedChildCollection = parentCollection
    .SelectMany(x => x.ChildCollection.Touch());

您可以使用 SelectMany Reference Source

避免 LINQ 扩展的开销
public static IEnumerable<TResult> SelectManyNotNull<TSource, TResult>(
         this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector) 
{
    foreach (TSource element in source)
    {
        var subElements = selector(element);
        if (subElements != null)
            foreach (TResult subElement in subElements )
                yield return subElement;
    }
}