【问题标题】:Union collections only if second TSource is not Null仅当第二个 TSource 不为 Null 时才联合集合
【发布时间】:2014-12-16 19:45:34
【问题描述】:

我有以下 linq 语句:

List<Type> allTypes = group.GetTypes().Union(group2.GetTypes()).ToList();

当 group2 为 null 时可能会抛出 NullReferenceException

解决此问题的一种方法是在之前执行空检查,例如:

if (group2 != null)
{
    List<Type> allTypes = group.GetTypes().Union(group2.GetTypes()).ToList();
}
else 
{
   List<Type> allTypes = group.GetTypes();
}

但问题是我对不同类型有许多类似的分配,并且不想以这种方式为它们中的每一个执行 if 语句,但我宁愿将 null 检查放在一行中,例如:

 List<Type> allTypes = group.GetTypes().Union((if group2 != null)group2.GetTypes()).ToList();

但不确定如何使用 linq。

【问题讨论】:

  • 正是这些问题让我对即将到来的?.运营商充满期待。
  • 您可能需要考虑更改您的设计,以便 group2 永远不会为空。

标签: c# .net linq ienumerable


【解决方案1】:

您在这里遇到的问题不是 TSource 为空;您要从中获取源的对象为 null (group2)。

您可以随时使用Enumerable.Empty 来保存您的神奇一号线。

List<Type> allTypes = group.GetTypes().Union(group2 != null ? group2.GetTypes() : Enumerable.Empty<Type>()).ToList();

或者您可以使用可重复使用的Union 重载:

public static IEnumerable<T> Union<T>(this IEnumerable<IEnumerable<T>> source)
{
    var set = new HashSet<T>();
    foreach (var s in source)
    {
       foreach (var item in s)
       {
           if (set.Add(item))
               yield return item;
       }
    }
}

然后你的代码变成:

var allTypes = new [] { group, group2 }.Where(x => x != null).Select(x => x.GetTypes()).Union().ToList();

这种方法的优点是可以有两个以上的序列形成一个联合。

【讨论】:

  • 请注意,通过创建HashSet 并将所有序列与它联合起来,您拥有的Union 方法会明显更有​​效。事实上,你不断地构建然后丢弃一个哈希集来表示中间数据集。
  • 虽然您对性能的看法是正确的,但HashSet 会产生令人讨厌的副作用,即重新排列序列。 Enumerable.Union 首先从第一个序列(按顺序)返回元素,然后从第二个序列返回。
  • 这种行为很容易被模仿。本质上,您的代码看起来与Union 的源代码完全一样,除了不是为每组执行一次代码,而是foreach 在两组上。代码的核心(在错误检查和集合创建之外)最终变成:foreach(var sequence in source) if(sequence != null) foreach(var item in sequence) if(set.Add(item)) yield return item;
  • source.SelectMany(x =&gt; x).Distinct() 应该等同于您的联合方法。示例代码将简单地变为.Where(x =&gt; x != null).SelectMany(x =&gt; x.GetTypes()).Distinct()
  • Union 方法的 null 处理很奇怪。如果所有元素都是null,它将返回一个空序列。如果它同时包含 null 和非 null 元素,则会出现 null 引用异常。
【解决方案2】:

这里你只需要一个方法来获取可以支持空参数的组的类型,因为你的不支持。这是一个非常简单的编写方法:

public static IEnumerable<Type> MyGetTypes(Group group)
{
    if(group == null)
        return Enumerable.Empty<Type>();
    else
        return group.GetTypes();
}

(如果你愿意,你可以把它变成一个扩展方法)

您现在可以将原始代码编写为:

var allTypes = MyGetTypes(group).Union(MyGetTypes(group2)).ToList();

如果我们愿意,我们也可以概括这一点,而不是让这个方法如此具体。

public static TResult Use<TSource, TResult>(TSource source,
    Func<TSource, TResult> selector,
    TResult defaultValue = default(TResult))
{
    if (source == null)
        return defaultValue;
    else
        return selector(source);
}

我们可以这样写:

var allTypes = group.GetTypes()
    .Union(group2.Use(g => g.GetTypes(), Enumerable.Empty<Type>()))
    .ToList();

当 C# 6.0 发布并且我们可以访问 ?. 运算符时,您还可以像这样编写代码:

var allTypes = group.GetTypes()
    .Union(group2?.GetTypes() ?? Enumerable.Empty<Type>())
    .ToList();

这允许空组传播到空类型集合,而不是抛出,然后允许用空集合替换空值,Union 将支持。这个运算符或多或少是我们 Use 方法的内置版本,但它允许我们避免使用 lambda,使其明显更加简洁。

【讨论】:

    【解决方案3】:

    我发现在一行中放置空检查的最佳方法是ternary operator

    List<Type> allTypes = group2 == null ? 
      group.GetTypes() 
      : group.GetTypes().Union(group2.GetTypes()).ToList();
    

    【讨论】:

    • 这是一个很好的解决方案,但希望我能做类似@usr 建议的事情。
    • @Stanislav 一旦 C# 6.0 出现,您就可以使用,正如我在回答中所展示的那样。目前没有办法。
    【解决方案4】:

    对于这种情况,我通常会创建一个新的扩展方法。例如:

    public static IEnumerable<T> SafeUnion<T>(
        this IEnumerable<T> source1, IEnumerable<T> source2)
    {
        return source1 != null ?
            (source2 != null ? source1.Union(source2) : source1) : source2;
    }
    

    或在您的情况下最有意义的任何特定逻辑(上面将允许可枚举为空...例如,您可能只想允许第二个)。

    一些脾气暴躁的人可能会觉得 OP 无法根据自己的需要调整这个想法。我认为他可能不会有任何问题,而且如果没有变量声明,我就无法准确显示它的样子。但它会是这样的bit

    public static List<Type> SafeUnion(this Group group1, Group group2)
    {
        return (group2 != null ?
                group1.GetTypes().Union(group2.GetTypes()) : group1.GetTypes();
    }
    

    当然,Group 类型需要替换为这些变量的实际类型。此示例也不允许 group1 为空。如果需要的话,大概读者可以自己解决这个变化。

    【讨论】:

    • 这完全有道理。 LINQ 已经是扩展方法,所以只需添加更多。
    • 但这并没有解决问题。问题不在于序列为空 - group2 对象为空。此外,您的 SafeUnion 可能返回 null,这不是很 LINQish。
    • 正如我所写,该示例可以根据 OP 的需要进行调整。不幸的是,OP 没有提供完整的代码示例,如果没有正确声明 groupgroup2 变量,就不可能编写完全匹配的代码示例。至于返回null,嗯……首先在扩展方法中允许空值不是“LINQ-ish”,那又如何?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-12-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多