比较两个.NET数组对象

25

我正在尝试比较两个 .NET 数组。这是一个比较字节数组的显而易见的实现:

bool AreEqual(byte[] a, byte[] b){
    if(a.Length != b.Length)
        return false;
    for(int i = 0; i < a.Length; i++)
        if(a[i] != b[i])
            return false;

    return true;
}

可以看到更精细的方法在这里(通过Google)。

  1. 使用更少的代码(但易读)比较两个.NET数组的最简单方法是什么?
  2. 比较两个.NET数组的最有效方法是什么?

2
如果它们是引用相等的,你也可以添加一个提前退出。 - justin.m.chase
4个回答

49
你可以使用SequenceEqual
string[] a = { "1", "2", "3" };
string[] b = { "1", "2", "3" };

bool areEqual = a.SequenceEqual(b); // true


string[] c = { "1", "2", "5" };
areEqual = a.SequenceEqual(c);      // false

3
这个页面提供了一些性能信息,并声称SequenceEqual比使用循环的自定义实现慢大约10倍。我想知道为什么。 - Roland Pihlakas
2
默认情况下,SequenceEqual 方法不可用,您必须使用 System.Linq。这是一个很好的知识点,以防您感到困惑(就像我一样)。 - Arne

18

我认为Kathy的方法不错。我个人会允许明确指定比较器:

bool AreEqual<T>(T[] a, T[] b)
{
    return AreEqual(a, b, EqualityComparer<T>.Default);
}

bool AreEqual<T>(T[] a, T[] b, IEqualityComparer<T> comparer)
{
    // Handle identity comparison, including comparing nulls
    if (a == b)
    {
        return true;
    }

    if (a == null || b == null)
    {
        return false;
    }

    if(a.Length != b.Length)
    {
        return false;
    }

    for(int i = 0; i < a.Length; i++)
    {
        if(!comparer.Equals(a[i], b[i]))
        {
            return false;
        }
    }
    return true;
}

如CMS所提到的那样,SequenceEqual很好,但由于它适用于IEnumerable<T>,我认为它不能在长度不相等时进行“提前退出”。(虽然可能会检查两个序列都实现IList,以直接检查Count。)您可以更加通用一些,使用IList<T>。

bool AreEqual<T>(IList<T> a, IList<T> b, IEqualityComparer<T> comparer)
{
    if(a.Count != b.Count)
    {
        return false;
    }
    for(int i = 0; i < a.Count; i++)
    {
        if(!comparer.Equals(a[i], b[i]))
        {
            return false;
        }
    }
    return true;
}

直接使用数组版本可能是最有效的 - 添加通用性和抽象通常会影响性能,尽管是否显著取决于您的应用程序。


4
以上代码片段应该有 if (!comparer.Equals(a[i], b[i])) return false 吗?我是不是发现了我的宇宙中的一个异常 :) - Gishu
如果您想检查计数,应该使用ICollection<T>而不是IList<T>,因为它定义了所有.NET集合应该实现的内容(数组、列表、集合、队列等)。虽然我不同意null == null的比较。对于EqualityComparer,我给出+1。 - Shelakel
@Shelakel:我也在使用 IList<T> 的索引器。你为什么不同意 null == null?这是大多数 C# 相等运算符的做法...在 .NET 的上下文中,使 null 不等于自身会显得很奇怪,我的看法是这样的。 - Jon Skeet
@JonSkeet:使用索引器应该会更快,不幸的是,没有共享的ICollection接口支持索引器 - 猜测最好的方法是为数组和列表以及ICollection和IEnumerable实现这个接口作为回退。在长度检查之前,最好先执行ReferenceEquals方法。我之所以说null != null的主要原因是a和b之间没有比较(没有引用、哈希代码、成员、行为),如果相等性需要可衡量的值,那么null!=null。 - Shelakel
@Shelakel:是的,如果您需要为ICollection<T>实现此功能,则有其他替代方案,但这些替代方案并不是此问题所必需的。至于空值性,如果ab都为空,则这些正在比较的引用...这就是所有可用的信息。在.NET中,null == null几乎无处不在。如果相等性“需要可衡量的值”,那么您可能不应该首先使用它与null一起使用。 - Jon Skeet
显示剩余7条评论

9

随着.NET 4的推出,你可以使用.NET数组显式实现接口IStructuralEquatable提供的Equals()方法。然后代码可能看起来像这样(我改写了CMS的示例):

string[] a = { "1", "2", "3" };
string[] b = { "1", "2", "3" };
bool result = ((IStructuralEquatable)a).Equals(b, StructuralComparisons.StructuralEqualityComparer);
// result evaluates to true.
< p >(IStructuralEquatable 也被实现在元组中(这也是 .NET 4 中的新功能)。)< /p >

4
或者使用 StructuralComparisons.StructuralEqualityComparer.Compare(a,b) 进行比较。 - Chris Bednarski
1
谢谢Chris!你的解决方案比我的重要好处是:它是空值感知的。 - Nico

0
也许像这样吗?
static bool AreEqual<T>(T[] a, T[] b) 
{
    bool areEqual = false ;
    T[] result = a.Intersect(b.AsEnumerable()).ToArray();
    areEqual = (result.Length == a.Length) && (result.Length == b.Length);
    return areEqual;
}

我不确定这个会对性能造成什么影响。

编辑

修订版本已考虑到Jon的建议:

    static bool AreEqual<T>(T[] a, T[] b) 
    {
        return a.SequenceEqual(b);
    }

如果您可以使用LINQ,并且不介意与直接使用数组相比的性能损失(并且提前退出以处理不同的长度),那么SequenceEqual是前进的方式。 - Jon Skeet
1
这个解决方案在存在重复项时会失败:{1,1,1} = {1,1,1} 返回false。它也不考虑顺序:{1,2}!= {2,1} 返回true。 - Jon Skeet
"{1, 2} != {2, 1}" 返回 true,这意味着顺序被考虑了,对吗? - Agnel Kurian
我想说的是,排序应该被考虑在内,但实际上并没有。这两个数组应该被视为不同,但是在这个答案的原始版本中,AreEqual返回true。对于造成的困惑,我感到很抱歉。 - Jon Skeet
如果您想检查两个集合是否具有相同的唯一值,则可以使用以下代码:return (new HashSet<T>(a)).SetEquals(b);(此方法忽略顺序,不关心同一值是否存在多次)。 - NiKiZe

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