我们是否可以使用 IEqualityComparer 接口使用 LINQ 扩展方法 SequenceEqual逐字段比较



我正在尝试使用IEqualityComparer逐字段比较 2 个集合中的 2 个字段。IEqualityComparer仅比较 1 个字段"名称"。我也想比较"标记"。

在 Java 中,我们有comparator接口来比较Equals方法中的多个字段。

using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
IList<Student> studentList1 = new List<Student>()
{
new Student(){ name="aaaaa", mark = 95, },
new Student(){ name="bbbb", mark = 25, },
new Student(){ name="ccc",  mark = 80 }
};
IList<Student> studentList2 = new List<Student>()
{
new Student(){ name="aaaaa", mark = 95, },
new Student(){ name="bbbb", mark = 5, },
new Student(){ name="ccc",  mark = 80 }
};
bool isEqual = studentList1.SequenceEqual(studentList2, new StudentComparer());
Console.WriteLine("Names in 2 collections are {0}", isEqual?"equal":"not equal");   
}
}
public class Student
{
public string name { get; set; }
public int mark { get; set; }
}
public class StudentComparer : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
if (x.name == y.name)
return true;
return false;
}
public int GetHashCode(Student obj)
{
return obj.GetHashCode();
}
}

实际结果: 2 个集合中的名称相等 预期成果: 2 个集合中的名称相等 2 个集合中的分数不相等

您在这里的问题是Object.GetHashCode()在对象之间必然是唯一的。这意味着当SequenceEqual(…)调用comparer.GetHashCode(…)时,它将为不同的对象实例获得不同的值,尽管您已经在StudentComparer.Equals(…)中声明了它们相等。

所有这些意味着,您应该返回哈希代码,这些哈希代码仅在根据IEqualityComparer的学生实例是唯一的情况下是唯一的。例如,您可以将StudentComparer.GetHashCode(Student obj)的实现更改为:return obj.name.GetHashCode()

需要像这样正确实现相等比较器(此代码由 R# 生成):

public sealed class StudentComparer  : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return string.Equals(x.name, y.name) && x.mark == y.mark;
}
public int GetHashCode(Student obj)
{
unchecked
{
return ((obj.name != null ? obj.name.GetHashCode() : 0) * 397) ^ obj.mark;
}
}
}

PS 为什么"397"用于ReSharper GetHashCode覆盖?

基本上,您需要检查Equals方法中的名称和标记是否相等,并单独获取name属性的哈希代码以及mark属性,以便IEqualityComparer正常工作。

将您的代码修改为:

public class Program
{
public static void Main()
{
IList<Student> studentList1 = new List<Student>()
{
new Student { name = "aaaaa", mark = 95, },
new Student { name = "bbbb", mark = 25, },
new Student { name = "ccc",  mark = 80 }
};
IList<Student> studentList2 = new List<Student>()
{
new Student { name = "aaaaa", mark = 95, },
new Student { name = "bbbb", mark = 5, },
new Student { name = "ccc",  mark = 80 }
};
bool isEqual = studentList1.SequenceEqual(studentList2, new StudentComparer());
Console.WriteLine("Contents in 2 collections are {0}", isEqual ? "equal" : "not equal");
}
}
public class Student
{
public string name { get; set; }
public int mark { get; set; }
}
public class StudentComparer : IEqualityComparer<Student>
{
public bool Equals(Student x, Student y)
{
//Check whether the compared objects reference the same data. 
if (object.ReferenceEquals(x, y))
return true;
//Check whether any of the compared objects is null. 
if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
return false;
return string.Equals(x.name, y.name, StringComparison.OrdinalIgnoreCase) && x.mark == y.mark;
}
public int GetHashCode(Student student)
{
//Check whether the object is null 
if (object.ReferenceEquals(student, null))
return 0;
//Get hash code for the name field if it is not null
int nameHashCode = !string.IsNullOrEmpty(student.name) ? 0 : student.name.GetHashCode();
// Get hash code for marks also if its not 0
int marksHashCode = student.mark == 0 ? 0 : student.mark.GetHashCode();
return nameHashCode ^ marksHashCode;
}
}

输出:

Contents in 2 collections are not equal

我建议你将IEquatable接口添加到你的学生类中,因为它的代码更少,而且你想要实现的目标更清楚:

public class Student : IEquatable<Student>
{
public string name { get; set; }
public int mark { get; set; }
public bool Equals(Student other)
{
if (string.Equals(name, other.name) && mark == other.mark)
{
return true;
}
return false;
}
}

您可以省略额外的 StudentComparer 实现。

相关内容

最新更新