C# LINQ SelectMany with Default

本文关键字:Default with SelectMany LINQ | 更新日期: 2023-09-27 18:05:42

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

,

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);

是否有一个简单的扩展方法来完成这一点?

C# LINQ SelectMany with Default

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

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是您自己的类,您还应该能够为该类设置默认构造函数,例如:

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

这应该防止null ref异常,并给你一个空列表,如果它没有任何东西在它。

你的"选项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参考源

来避免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;
    }
}