Andrew
Andrew

Reputation: 760

Compare two Lists<object> returning different results

I have a class called TestResult which looks like this:

 public class TestResult : IEquatable<TestResult> {

        public TestResult(string labelName, List<object> correctValues) {
            this.LabelName = labelName;
            this.SelectedValues = correctValues;
        }

        public TestResult() {
        }

        public string LabelName { get; set; }
        public List<object> SelectedValues { get; set; }

        public override bool Equals(object obj) {
            if (ReferenceEquals(null, obj)) {
                return false;
            }
            if (ReferenceEquals(this, obj)) {
                return true;
            }

            return obj.GetType() == GetType() && Equals((TestResult)obj);
        }

        public override int GetHashCode() {
            unchecked {
                int hashCode = this.LabelName.GetHashCode();
                hashCode = (hashCode * 397) ^ this.SelectedValues.GetHashCode();
                return hashCode;
            }
        }

        public bool Equals(TestResult other) {
            if (ReferenceEquals(null, other)) {
                return false;
            }
            if (ReferenceEquals(this, other)) {
                return true;
            }

            bool areEqual = false;

            if (this.LabelName == other.LabelName) {
                areEqual = true;
            }

            if (this.SelectedValues?.Count != other.SelectedValues?.Count) {
                return false;
            }

            areEqual = this.SelectedValues.OrderBy(x => x).SequenceEqual(other.SelectedValues.OrderBy(x => x));

            return areEqual;
        }

        /// <summary>
        /// Override ==(you must ovverride this so if a developer called == it will return the same result as if they called Equals
        /// </summary>
        /// <param name="obj1"></param>
        /// <param name="obj2"></param>
        /// <returns></returns>
        public static bool operator ==(TestResult obj1, TestResult obj2) {
            if (ReferenceEquals(obj1, obj2)) {
                return true;
            }

            if (ReferenceEquals(obj1, null)) {
                return false;
            }
            if (ReferenceEquals(obj2, null)) {
                return false;
            }

            bool areEqual = false;

            if (obj1.LabelName == obj2.LabelName) {
                areEqual = true;
            }

            if (obj1.SelectedValues?.Count != obj2.SelectedValues?.Count) {
                return false;
            }

            areEqual = obj1.SelectedValues.OrderBy(x => x).SequenceEqual(obj2.SelectedValues.OrderBy(x => x));

            return areEqual;
        }

        /// <summary>
        /// No need to repeat myself, just return the opposite of the == function
        /// </summary>
        /// <param name="obj1"></param>
        /// <param name="obj2"></param>
        /// <returns></returns>
        public static bool operator !=(TestResult obj1, TestResult obj2) {
            return !(obj1 == obj2);
        }

As you can see I have overridden the equals methods so I can compare my objects when I create a List.

I then have a unit test which tests my equals methods and it looks like this:

   [TestMethod]
        public void ReturnIncorrectTestResults_IncorrectValuesSubmitted_3LabelsWillBeReturned() {
            List<string> failedLabelNames;

            var submittedResults = new List<Repository.TestManagement.Models.TestResult> {
                new Repository.TestManagement.Models.TestResult("Question1Label", new List<object> { true }),
                new Repository.TestManagement.Models.TestResult("Question2Label", new List<object> { true }), //Difference
                new Repository.TestManagement.Models.TestResult("Question3Label", new List<object> { 3, 4 }),
                new Repository.TestManagement.Models.TestResult("Question4Label", new List<object> { true }),
                new Repository.TestManagement.Models.TestResult("Question5Label", new List<object> { 1, 3 }), //Difference
                new Repository.TestManagement.Models.TestResult("Question6Label", new List<object> { 1, 2, 3, 4 }),
                new Repository.TestManagement.Models.TestResult("Question7Label", new List<object> { 1, 2, 3 }),
                new Repository.TestManagement.Models.TestResult("Question8Label", new List<object> { 2 }),
                new Repository.TestManagement.Models.TestResult("Question9Label", new List<object> { 3 }), //Difference
                new Repository.TestManagement.Models.TestResult("Question10Label", new List<object> { 1, 2, 3, 4, 5 })
            };

            var validResults = new List<Repository.TestManagement.Models.TestResult> {
                new Repository.TestManagement.Models.TestResult("Question1Label", new List<object> { false }),
                new Repository.TestManagement.Models.TestResult("Question2Label", new List<object> { true }),
                new Repository.TestManagement.Models.TestResult("Question3Label", new List<object> { 3, 4 }),
                new Repository.TestManagement.Models.TestResult("Question4Label", new List<object> { true }),
                new Repository.TestManagement.Models.TestResult("Question5Label", new List<object> { 5,6 }),
                new Repository.TestManagement.Models.TestResult("Question6Label", new List<object> { 1, 2, 3, 4 }),
                new Repository.TestManagement.Models.TestResult("Question7Label", new List<object> { 1, 2, 3 }),
                new Repository.TestManagement.Models.TestResult("Question8Label", new List<object> { 2 }),
                new Repository.TestManagement.Models.TestResult("Question9Label", new List<object> { 1 }),
                new Repository.TestManagement.Models.TestResult("Question10Label", new List<object> { 1, 2, 3, 4, 5 })
            };

            failedLabelNames = _iTestManager.ReturnIncorrectTestLabels(submittedResults, validResults);

            Assert.IsTrue(failedLabelNames.Count == 3);
        }

So I also have a method in my applications code which calls the same equals functions:

  public List<string> ReturnIncorrectTestLabels(List<TestResult> submittedResults, List<TestResult> acceptedResults) {
            if (submittedResults.Count != acceptedResults.Count)
                throw new ArgumentException($"The submitted results count is {submittedResults.Count} and the accepted results count is {acceptedResults.Count}. Amount of results must be equal.");

            /*Compare the valid results against the submitted results. We join on the label names and 
        compare the results. Please not that this works because I have overridden the equals in 
        the TestResult class*/

            var failedResultLabelNames = (from accepted in acceptedResults
                                          join submitted in submittedResults
                         on accepted.LabelName equals submitted.LabelName
                                          where accepted != submitted
                                          select accepted?.LabelName).ToList();

            return failedResultLabelNames;

        }

I use it to compare two lists of results and return any failed values.

What's strange is that my unit test passes, but when I test in my site it returns false and that the objects are not equals.

So for example if I submit two lists which look like this:

var list1 = new List<TestResult> {
                new TestResult("Question1Label", new List<object> { 1,2,3 }),
                new TestResult("Question2Label", new List<object> { 4,5,6 })
            };

            var list2 = new List<TestResult> {
                new TestResult("Question1Label", new List<object> { "1","2","3" }),
                new TestResult("Question2Label", new List<object> { "4","5","6" })
            };

And I call the ReturnIncorrectTestLabels method for my two lists, it returns both list items as "failed".

Why is this happening?

Upvotes: 0

Views: 65

Answers (2)

Andrew
Andrew

Reputation: 760

   public static bool operator ==(TestResult obj1, TestResult obj2) {
            if (ReferenceEquals(obj1, obj2)) {
                return true;
            }

            if (ReferenceEquals(obj1, null)) {
                return false;
            }
            if (ReferenceEquals(obj2, null)) {
                return false;
            }

            bool areEqual = false;

            if (obj1.LabelName == obj2.LabelName) {
                areEqual = true;
            }

            if (obj1.SelectedValues?.Count != obj2.SelectedValues?.Count) {
                return false;
            }

            //Order to make sure that they are in correct order to be compared
            obj1.SelectedValues = obj1.SelectedValues.OrderBy(x => x).ToList();
            obj2.SelectedValues = obj2.SelectedValues.OrderBy(x => x).ToList();

            for (int i = 0; i < obj1.SelectedValues.Count; i++) {
                var type = obj1.SelectedValues[i].GetType();
                //Use a dynamic so I can cast to the correct types at run time and compare
                dynamic castedObj1Val = Convert.ChangeType(obj1.SelectedValues[i], type);
                dynamic castedObj2Val = Convert.ChangeType(obj2.SelectedValues[i], type);
                if (castedObj1Val != castedObj2Val)
                    return false;
            }

            return areEqual;
        }

I was comparing two different types so I had to cast them to their correct types before comparing

Upvotes: 1

Vidmantas Blazevicius
Vidmantas Blazevicius

Reputation: 4802

It's because the != is performed like a cross join so everything is compared with everything hence when first item is compared with second you get the failedResult and when secondItem is compared with first - you also get a failedResult.

This is because you are joining on a LabelName which is the same for both items in list1 and list2. Try comparing when your label names are unique (as they are in your unit tests) and it should give desired and expected results.

        var list1 = new List<TestResult> {
            new TestResult("1", new List<object> { 1,2,3 }),
            new TestResult("2", new List<object> { 4,5,6 })
        };

        var list2 = new List<TestResult> {
            new TestResult("1", new List<object> { 1,2,3 }),
            new TestResult("2", new List<object> { 4,5,6 })
        };

        var test = ReturnIncorrectTestLabels(list1, list2);

Upvotes: 0

Related Questions