Можем ли мы сравнить два сложных набора полей по полю, используя интерфейс IEqualityComparer, используя метод расширения LINQ SequenceEqual - PullRequest
3 голосов
/ 23 мая 2019

Я пытаюсь использовать IEqualityComparer для сравнения 2 полей из 2 коллекций поле за полем.IEqualityComparer сравнивает только 1 поле "имя".Я также хочу сравнить "mark".

В 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 коллекциях не равны

Ответы [ 4 ]

3 голосов
/ 23 мая 2019

Ваша проблема здесь в том, что Object.GetHashCode() обязательно уникален между объектами.Это означает, что когда SequenceEqual(…) вызывает comparer.GetHashCode(…), он получит разные значения для разных экземпляров объектов, несмотря на то, что вы объявили их равными в StudentComparer.Equals(…).

Все это означает, что вы должны возвращать хеш-коды, которые являются уникальными только в тех случаях, когда экземпляры Student являются уникальными в соответствии с IEqualityComparer.Например, вы можете изменить реализацию StudentComparer.GetHashCode(Student obj) на: return obj.name.GetHashCode()

1 голос
/ 23 мая 2019

Необходимо правильно реализовать средство сравнения равенства следующим образом (этот код был сгенерирован с помощью 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? .

0 голосов
/ 23 мая 2019

Я бы посоветовал вам добавить интерфейс IEquatable в ваш класс Student, так как он содержит меньше кода и более ясно, чего вы хотите достичь:

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.

0 голосов
/ 23 мая 2019

В основном вам необходимо проверить, равны ли имена и метки в методе 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
...