C#中使用LINQ的SelectMany和默认值

5

我希望找到一种优雅的解决方案,将集合中的子集合聚合成一个大集合。我的问题在于某些子集合可能为空。

例如:

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

有没有简单的扩展方法可以完成这个任务?
4个回答

4
你可以使用自定义扩展方法:
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);

一个很好的解决方案 - 在我的情况下,我将其与Cory Nelson的答案相结合,使用Enumerable.Empty<T>()而不是剪切空集合,这样我至少会有一个空集合。 - DenverCoder9
那也可以,但请注意Cory链接的代码附带了许可证,因此您需要提供归属。 - DavidG

1
如果ParentCollection是您自己的类,您还应该能够向类添加一个默认构造函数,例如:
public ParentCollection{
    public ParentCollection() {
        ChildCollection = new List<ChildCollection>();
    }
}

这样可以防止空引用异常,如果列表为空,则会得到一个空列表。至少在EF模型中是有效的。

2
很遗憾,我不能保证任何子集合都会被填充。如果一个子集合为空并不意味着该类无效,我只是想将所有子集合分组到一个集合中,并通过将它们视为空集合来忽略任何空值。 - DenverCoder9

1
你的 "选项 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());

1
你可以通过使用 SelectMany 来避免 LINQ 扩展的开销。参考源代码: Reference Source
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;
    }
}

谢谢你提供的好资源,很有趣看到他们是如何做到的。 - DenverCoder9

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接