5

I have the following:

  var a = new List<OrderRule> {
    new OrderRule("name", OrderDirection.Ascending),
    new OrderRule("age", OrderDirection.Descending)
  };


  var b = new List<OrderRule> {
    new OrderRule("name", OrderDirection.Ascending),
    new OrderRule("age", OrderDirection.Descending)
  };

  var r = a.Equals(b);

The r variable is false even if the two lists include items which are equal to each other. The OrdeRule class implements IEquality. Note that two OrderRules are equal when both Direction and Property are equal.

public enum OrderDirection { ASC, DESC }

public class OrderRule : IEquatable<OrderRule> {

  public OrderDirection Direction { get; }
  public String Property { get; }

  public OrderRule(String property, OrderDirection direction) {
    Direction = direction;
    Property = property;
  }

  public Boolean Equals(OrderRule other) {

    if (other == null)
      return false;

    return Property.Equals(other.Property) && Direction.Equals(other.Direction);

  }

  public override Boolean Equals(Object obj) {

    if (ReferenceEquals(null, obj))
      return false;

    if (ReferenceEquals(this, obj))
      return true;

    if (obj.GetType() != GetType())
      return false;

    return Equals(obj as IncludeRule);

  }

  public override Int32 GetHashCode() {

    return HashCode.Of(Property).And(Direction);

  }

}

What am I missing?

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
Miguel Moura
  • 36,732
  • 85
  • 259
  • 481
  • 8
    `List` doesn't override `Equals`, basically - it's as simple as that. – Jon Skeet Jul 01 '16 at 18:10
  • 1
    `List` does not imeplement `IEquality` nor does it override `Equals` and `GetHashCode`, so that's doing a reference comparison. You might want to try `Enumerable.SequenceEqual` instead. – juharr Jul 01 '16 at 18:10
  • I am using this on a XUnit and it works fine when using Assert.Equal(orderRule1, orderRule2); but not when using Assert.Equal(orderRuleList1, orderRuleList2); ... I suppose I need to implement a comparer? Assert.Equal accepts a Comparer ... I was just trying to avoid this as I will need to replicate the code I already have in Equals. Any other option? – Miguel Moura Jul 01 '16 at 18:12
  • 1
    You should read this question then http://stackoverflow.com/questions/419659/xunit-assert-two-listt-are-equal – juharr Jul 01 '16 at 18:14
  • @juharr I have read that before. With Assert.Equal(a, b) returns false. Note that a and b are the collections of my example. – Miguel Moura Jul 01 '16 at 18:17
  • 1
    @MiguelMoura Maybe it's a version issue? I've never used XUnit, and I've always needed to use something like `Enumerable.SequenceEqual` or `CollectionAssert.AreEqual` – juharr Jul 01 '16 at 18:24
  • @MiguelMoura `a.Equals(b);` in your code does not hit your `OrderRule : IEquatable` implementation. The comparison essentially asks if the pointer-to-list `a` is equal to the pointer-to-list `b` they are not the *same exact list* so the result is false. See answers below on how to fix it. `a.SequenceEquals(b);` – Max Sorin Jul 01 '16 at 18:25

4 Answers4

9

Checking to see if two lists are equal doesn't mean checking to see if they both contain the same items.

If you do this:

var a = new List<OrderRule>();
var b = new List<OrderRule>();
var r = a.Equals(b); //false

r will always be false, even if the two lists have the same items in them. That's because you're not checking the contents of the lists. You're actually checking to see if both a and b refer to the same object. But a is one object and b is another.

If you did this, r would be true:

var a = new List<OrderRule>();
var b = a;
var r = a.Equals(b) //true

If you have two lists and you want to see if they contain the same items in the same order, you could do this:

var r = a.SequenceEquals(b);

In the example in your question r would be true because you're overriding Equals on OrderRule, so the items in the one list are considered "equal" to the items in the other.


Got it - your equality check in OrderRule is broken.

public override Boolean Equals(Object obj) {
if (ReferenceEquals(null, obj))
  return false;

if (ReferenceEquals(this, obj))
  return true;

if (obj.GetType() != GetType())
  return false;

return Equals(obj as IncludeRule);
}

I think instead of return Equals(obj as IncludeRule) you mean return Equals(obj as OrderRule).

Scott Hannen
  • 27,588
  • 3
  • 45
  • 62
  • You are right about "var r = a.SequenceEqual(b);" ... Now it is true. But why does Assert.Equal(a, b) returns false in XUnit. Assert.Equal supports collection comparison. – Miguel Moura Jul 01 '16 at 18:20
  • I've never used it, but I checked the docs and that's what it says. I'm assuming that equality check for `OrderRule` is working - what if it's not? Maybe you could write a test to check two identical instances for equality. – Scott Hannen Jul 01 '16 at 18:24
4

your code is testing to see if they are the same list. not if they have the same contents.

a.Equals(b); // false
var d = a;
d.Equals(a); // true

if you want to compare the contents then use linq's SequenceEqual

https://msdn.microsoft.com/en-us/library/bb348567(v=vs.100).aspx

pm100
  • 48,078
  • 23
  • 82
  • 145
1

First whenever one uses new keyword or creates a new object the reference or the address of the object is stored in the variable. Also equals compares the value of the variable.

Now you have overrided the equals function of the OrderRule. So if you do equals on two OrderRule you will get the result as you compare it inside the overrided equals function.

But now think about this What is List<OrderRule> its nothing but a generic class. So again the equals will check the value of the variable which contains the reference and since they are different you will not get true when you compare them. Also List implements the equals same as that of the object its not overrrided. Hence one way which i prefer is to create an extensions.

public static class Extensions
{
    public static bool Equal<T>(this List<T> x, List<T> y)
    {
        bool isEqual = true;
        if (x == null ^ y == null)
        {
            isEqual = false;
        }
        else if (x == null && y == null)
        {
            isEqual = true;
        }
        else if (x.Equals(y))
        {
            isEqual = true;
        }
        else if (x.Count != y.Count)
        {
            isEqual = false;
        }
        else
        {
            //This logic can be changed as per your need.
            //Here order of the list matters!
            //You can make one where order doesn't matter
            for (int i = 0; i < x.Count; i++)
            {
                if (!x[i].Equals(y[i]))
                {
                    break;
                }
            }

        }
        return isEqual;
    }
}
  • You don't need to write a extension to do this. [`Enumerable.SequenceEqual`](https://msdn.microsoft.com/en-us/library/bb348567%28v=vs.100%29.aspx) is a extension method built in to .NET that does this exact same logic already. – Scott Chamberlain Jul 01 '16 at 18:40
  • Yup true and i completely forgot about the `SequenceEqual`. But this way one has more control and can put his or her own logic as sometimes the sequence of data doesn't matter. But yeah thanks for reminding me! – Sukrat Kashyap Jul 01 '16 at 18:48
  • If you don't care about the order take a look at [this old answer of mine](http://stackoverflow.com/questions/33245613/whats-the-best-way-to-determine-whether-two-listt-objects-contain-the-same-se/33246455#33246455) where I converted `CollectionAssert.AreEquivalent` in to something that returned a bool instead of throwing a exception. – Scott Chamberlain Jul 01 '16 at 20:21
0

Doing so you are comparing references but not objects. So you get inequality returned.

easy
  • 320
  • 1
  • 10