使用LINQ SequenceEqual扩展方法时,如何处理可能为空的属性?

3

我正在编写一个简单的控制台应用程序,用于比较两个自定义类对象的实例。对于每个属性,我将True或False写入控制台窗口,以显示每个对象的属性是否匹配。

有些属性,如ProductLines(列表属性),在一个或两个对象中可能为空...或者都不为空。这对使用SequenceEqual来比较它们带来了一些问题,因为它不接受空值。 有比我编写的代码更好的方法来比较两个序列属性吗?

// test if either collection property is null.
if (commsA.Last().ProductLines == null || commsB.Last().ProductLines == null) 
{
    // if both null, return true.
    if (commsA.Last().ProductLines == null && commsB.Last().ProductLines == null)
    {
        Console.WriteLine("Property Match:{0}", true);
    }
    // else return false.
    else
    {
        Console.WriteLine("Property Match:{0}", false);
    }
}
// neither property is null. compare values and return boolean.
else
{
    Console.WriteLine("Property Match:{0}", 
          commsA.Last().ProductLines.SequenceEqual(commsB.Last().ProductLines));
}

附注 - 每次写 commsA.Last()commsB.Last() 时,都会执行查询。即使使用 Linq to Objects,这也涉及创建枚举器并执行 Last()。 - Sergey Berezovskiy
为两个序列缓存Last()的结果。否则,您有每次迭代它的风险。至少这会浪费资源,而您可以将其花费为0。 - abatishchev
2
SequenceEqual 确实 接受空值... 你为什么认为它不接受呢?(我刚刚用字符串数组测试过了,没问题。) - Jon Skeet
@SergeyBerezovskiy:如果序列是IList<T>,则不涉及任何迭代。无论如何,避免仍然是值得的... - Jon Skeet
1
@JonSkeet SequenceEqual会在其中一个参数为null时抛出异常 - 这里有检查if (first == null) throw Error.ArgumentNull("first")。你是对的,如果源是IList<T>,就不会使用Last()进行枚举(但仍然存在一些开销..)。 - Sergey Berezovskiy
2
@SergeyBerezovskiy:啊,我明白了。是的,我错过了用法。它可以处理序列中的空值...但不能处理空源。 - Jon Skeet
2个回答

8

我可能会添加一个NullRespectingSequenceEqual扩展方法:

public static class MoreEnumerable
{
    public static bool NullRespectingSequenceEqual<T>(
        this IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null && second == null)
        {
            return true;
        }
        if (first == null || second == null)
        {
            return false;
        }
        return first.SequenceEqual(second);
    }
}

或者使用堆叠的条件运算符:

public static class MoreEnumerable
{
    public static bool NullRespectingSequenceEqual<T>(
        this IEnumerable<T> first, IEnumerable<T> second)
    {
        return first == null && second == null ? true
             : first == null || second == null ? false
             : first.SequenceEqual(second);
    }
}

然后你只需要使用:

Console.WriteLine("Property Match: {0}",
     x.ProductLines.NullRespectingSequenceEqual(y.ProductLines));

关于是否应该调用Last的问题稍微有些不同。

您可以在任何地方重复使用该扩展方法,就像它是LINQ to Objects的正常部分一样。(当然,它不适用于LINQ to SQL等。)


很棒的回答,Jon。我认为你的扩展方法最适合我的应用程序。 - David Alan Condit

5
您的属性匹配结果显示肯定存在重复。请只显示一次结果。将计算移至单独的方法中:
Console.WriteLine("Property Match:{0}", 
  IsMatch(commsA.Last().ProductLines, commsB.Last().ProductLines));

就像这样:

public bool IsMatch<T>(IEnumerable<T> a, IEnumerable<T> b)
{
    if (a != null && b != null)
       return a.SequenceEqual(b);

    return (a == null && b == null);
}

1
@DavidAlanCondit 当然,你可以将这个方法作为扩展方法,特别是如果你经常需要使用它。 - Sergey Berezovskiy

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