【问题标题】:Compare two Lists<object> returning different results比较两个返回不同结果的 Lists<object>
【发布时间】:2018-09-02 16:43:49
【问题描述】:

我有一个名为 TestResult 的类,它看起来像这样:

 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);
        }

如您所见,我已经覆盖了 equals 方法,因此可以在创建 List 时比较我的对象。

然后我有一个单元测试来测试我的 equals 方法,它看起来像这样:

   [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);
        }

所以我的应用程序代码中也有一个方法,它调用相同的 equals 函数:

  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;

        }

我用它来比较两个结果列表并返回任何失败的值。

奇怪的是我的单元测试通过了,但是当我在我的站点中测试时它返回 false 并且对象不相等。

例如,如果我提交两个如下所示的列表:

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" })
            };

我为我的两个列表调用 ReturnIncorrectTestLabels 方法,它将两个列表项都返回为“失败”。

为什么会这样?

【问题讨论】:

  • list1 中的第一项是否与list2 中的第二项具有相同的LabelName?他们是平等的吗?
  • 确实如此,检查 TestResult 类
  • 这是因为 != 像交叉连接一样执行,因此所有内容都与所有内容进行比较,因此当第一项与第二项进行比较时,您会得到 failedResult,而当 secondItem 与第一项进行比较时 - 您还会得到结果失败。
  • 我现在没有时间发布答案,但如果我是你,我会更改所有类似于 List&lt;object&gt; correctValues 的代码以及你使用 object 的所有其他地方,并改用泛型。比较 List&lt;string&gt; correctValuesList&lt;int&gt; correctValues 是不同的。 List&lt;object&gt; 不是 List&lt;T&gt; 的好用法,虽然从技术上讲它是通用的,但在使用中它并不是那么通用,因为几乎所有东西都是 object

标签: c# compare equality iequatable


【解决方案1】:

这是因为!= 像交叉连接一样执行,因此所有内容都与所有内容进行比较,因此当第一项与第二项进行比较时,您会得到 failedResult,而当 secondItem 与第一项进行比较时 - 您也会得到 failedResult。

这是因为您正在加入 LabelName,这对于 list1list2 中的两个项目都是相同的。尝试比较您的标签名称何时是唯一的(就像它们在您的单元测试中一样),它应该会给出预期和预期的结果。

        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);

【讨论】:

  • 不,我不认为这是原因,因为当我调试我的重写 == 运算符时,它只比较一组列表并返回 false,即使两个列表具有相同的项目跨度>
  • @Andrew 绝对是,当两个列表中的所有项目的标签名称相同(交叉连接行为)时,您覆盖的操作符代码被调用 4 次,而当标签名称为不同(内部连接行为)。
  • 不,这绝对不是因为我刚刚弄清楚它是什么
  • @Andrew,好的,没问题,那是什么?如果我可能误解了,我很好奇。
  • 不,你是对的,技术上我写的问题是不正确的。
【解决方案2】:
   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;
        }

我在比较两种不同的类型,所以在比较之前我必须将它们转换为正确的类型

【讨论】:

  • 不错的一个。附带说明一下,考虑使用 Linq Except/Intersect 和自定义 IEqualityComparer&lt;TestResult&gt; - 这可能会改善代码的结构。
猜你喜欢
  • 1970-01-01
  • 2015-06-13
  • 2013-08-27
  • 2014-07-22
  • 1970-01-01
  • 1970-01-01
  • 2020-12-29
  • 1970-01-01
  • 2018-07-11
相关资源
最近更新 更多