C#中两个结构体的相等性

9
我正在寻找两个此结构体实例之间的相等性。
public struct Serie<T>
{
    T[]         X; 
    double[]    Y;

    public Serie(T[] x, double[] y)
    {
        X = x;
        Y = y;
    }

    public override bool Equals(object obj)
    {
        return obj is Serie<T> && this == (Serie<T>)obj;
    } 
    public static bool operator ==(Serie<T> s1, Serie<T> s2)
    {
        return s1.X == s2.X && s1.Y == s2.Y;
    }
    public static bool operator !=(Serie<T> s1, Serie<T> s2)
    {
        return !(s1 == s2);
    }

这不起作用。我错过了什么吗?

        double[] xa = { 2, 3 };
        double[] ya = { 1, 2 };
        double[] xb = { 2, 3 };
        double[] yb = { 1, 2 };
        Serie<double> A = new Serie<double>(xa, ya);
        Serie<double> B = new Serie<double>(xb, yb);
        Assert.AreEqual(A, B);

6个回答

19
您正在比较数组的引用而不是它们的内容。yayb指向不同的数组。如果您想检查数组的内容,则必须明确地这样做。
我想在框架中没有内置的方法可以为您完成这项任务。虽然如此,但类似下面的代码应该可以正常工作:
public static bool ArraysEqual<T>(T[] first, T[] second)
{
    if (object.ReferenceEquals(first, second))
    {
        return true;
    }
    if (first == null || second == null)
    {
        return false;
    }
    if (first.Length != second.Length)
    {
        return false;
    }
    IEqualityComparer comparer = EqualityComparer<T>.Default;
    for (int i = 0; i < first.Length; i++)
    {
        if (!comparer.Equals(first[i], second[i]))
        {
            return false;
        }
    }
    return true;
}

作为一个旁注,你的结构体在某种程度上是可变的,因为在创建结构体之后可以更改数组内容。你真的需要这个作为结构体吗?
编辑:如Nick在评论中提到的那样,你确实应该重写GetHashCode方法。同样,你需要从数组中获取内容(如果数组在之后被更改,这将会导致问题)。以下是类似的实用方法:
public static int GetHashCode<T>(T[] array)
{
    if (array == null)
    {
        return 0;
    }
    IEqualityComparer comparer = EqualityComparer<T>.Default;
    int hash = 17;
    foreach (T item in array)
    {
        hash = hash * 31 + comparer.GetHashCode(item);
    }
    return hash;
}

12
可以使用LINQ的SequenceEqual()扩展方法来比较数组内容。 - LBushkin
2
可能吧,但我倾向于选择清晰而不是性能,除非有明显的性能问题。出于好奇,你认为SequenceEquals会低效的原因是什么? - LBushkin
5
+1 我会补充说,当重写 Equals 方法时需要重写 GetHashCode 方法,因此您需要对数组进行散列(或者使用一个非常糟糕的散列码,例如始终返回一个恒定值)。 - Nick Guerrera
2
@LBushkin:除非它确实在某个地方*针对数组进行了优化,否则它将无法使用最有效的路径。 数组可以进行相当大量的优化。 我同意通常更喜欢简单,但这是一个有用的实用方法,可以编写一次,然后在许多地方使用。 @Nick:关于GetHashCode的好观点; 将其添加到答案中。 - Jon Skeet
2
哦,我也要加入我的声音,这个真的不应该是一个结构体。 - ShuggyCoUk
显示剩余3条评论

6

很抱歉,框架中没有内置此功能。

在4.0版本中,有此功能:

StructuralComparisons.StructuralEqualityComparer.Equals(firstArray, secondArray);

这是一个很棒的答案,但如果它能够独立存在,不引用其他答案的话会更好。 - Ryan Leach

5

2
调用==会对数组执行引用相等性 - 它们不比较元素的内容。这基本上意味着,只有在完全相同的实例时,a1 == a2才会返回true,这不是你想要的,我想..。
你需要修改你的operator ==来比较x数组的内容,而不是它的引用值。
如果你正在使用.NET 3.5(带链接),你可以这样做:
public static bool operator ==(Serie<T> s1, Serie<T> s2)
{
    return ((s1.X == null && s2.X == null) || s1.X.SequenceEquals( s2.X )) 
           && s1.Y == s2.Y;
}

如果您需要进行深度比较(超出引用范围),可以为类型T提供自定义的IEqualityComparer,并将其提供给SequenceEquals。

您可能还应该考虑为您的结构实现IEquatable<T>接口。这将有助于您的代码与LINQ和.NET框架的其他执行对象比较的部分更好地配合工作。


2
这段话的意思是:

s1.Y == s2.Y 这部分代码测试的是它们是否是指向同一个数组实例的两个引用,而不是它们的内容是否相等。因此,尽管标题是这样的,但这个问题实际上是关于数组(引用)之间的相等性。

另外一些建议:由于你正在重载 Serie<>,并且由于嵌入了数组,因此应该将其设计为不可变的类,而不是结构体。


1
您可以为您的结构体创建一个私有访问器并使用CollectionAssert:
[TestMethod()]
public void SerieConstructorTest()
{
    double[] xa = { 2, 3 };
    double[] ya = { 1, 2 };
    double[] xb = { 2, 3 };
    double[] yb = { 1, 2 };
    var A = new Serie_Accessor<double>(xa, ya);
    var B = new Serie_Accessor<double>(xb, yb);
    CollectionAssert.AreEqual(A.X, B.X);
    CollectionAssert.AreEqual(A.Y, B.Y);
}

这段代码运行正常。

参考资料:


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