仅当第二个 S 不为 Null 时才联合集合

本文关键字:集合 Null 第二个 不为 | 更新日期: 2023-09-27 18:34:27

我有以下 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 check 放在一行中,如下所示:

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

但不确定如何使用 LINQ 执行此操作。

仅当第二个 S 不为 Null 时才联合集合

你在这里遇到的问题不是 S null;而是你想要从中获取源代码的对象是 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();

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

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

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();
这允许 null

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

我发现将空检查放在一行中的最佳方法是三元运算符。

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

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

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

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

一些诅咒者可能会觉得OP无法使这个想法适应自己的需求。我认为他可能不会有麻烦,如果没有变量的声明,我无法准确地显示它会是什么样子。但它会有点像这样:

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

当然,Group类型需要替换为这些变量的实际类型。此示例也不允许group1为 null。如果需要,大概读者可以自己弄清楚这种变化。