0

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?

Andrew
  • 720
  • 3
  • 9
  • 34
  • Does the first item in `list1` have the same `LabelName` as the second item in `list2`? Are they equal? – mjwills Mar 23 '18 at 22:55
  • it does, check the TestResult class – Andrew Mar 23 '18 at 22:56
  • 1
    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. – Vidmantas Blazevicius Mar 23 '18 at 23:03
  • I dont have time right now to post an answer, but if I were you I would change all code which is like `List correctValues` and everywhere else you are using `object` and use generics instead. Comparing `List correctValues` to `List correctValues` is different. A `List` is not a good use of `List`, though technically it is generic but in usage it is not that generic since almost everything is an `object` – CodingYoshi Mar 23 '18 at 23:42

2 Answers2

1
   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

Andrew
  • 720
  • 3
  • 9
  • 34
  • 1
    Nice one. On a side note, consider using Linq `Except`/`Intersect` with a custom `IEqualityComparer` - that might improve the structure of your code. – Vidmantas Blazevicius Mar 23 '18 at 23:40
0

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);
Vidmantas Blazevicius
  • 4,652
  • 2
  • 11
  • 30
  • No, I don't think this is the reason because when I debug into my overridden == operator it only compares one set of the list and returns false even when both lists have the same items – Andrew Mar 23 '18 at 23:08
  • 1
    @Andrew It is definitely that, your overriden operator code is called 4 times when the label names are the same for all items in both lists (cross join behaviour) and only 2 times when the label names are different (inner join behaviour). – Vidmantas Blazevicius Mar 23 '18 at 23:14
  • No it definitely isnt that because i've just figured out what it is – Andrew Mar 23 '18 at 23:15
  • @Andrew, ok no problems, what was it then? I am quite curious if I perhaps misunderstood. – Vidmantas Blazevicius Mar 23 '18 at 23:16
  • I was comparing a list of objects that were ints to a list of objects that were strings. I am in the process of casting them to their correct types and comparing – Andrew Mar 23 '18 at 23:23
  • @Andrew Oh, nice one, that makes sense. Not sure if it was replicable from your code example and the same label names kinda produced similar issue. Good luck anyway. – Vidmantas Blazevicius Mar 23 '18 at 23:26
  • 1
    No you're right, technically the question I wrote is incorrect. – Andrew Mar 23 '18 at 23:28