3

I'm trying to use IEqualityComparer to compare 2 fields from 2 collections field by field. IEqualityComparer is comparing only 1 field "name". I want to compare "mark" as well.

In Java, we have comparator interface to compare more than one fields in Equals method.

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

Actual Result: Names in 2 collections are equal Expected Result: Names in 2 collections are equal Marks in 2 collections are not equal

Kunal Mukherjee
  • 5,775
  • 3
  • 25
  • 53
  • Use reflection and compare each property of these two objects to know that each value of the property is equal or not.. – Niranjan Singh May 23 '19 at 06:14
  • Why not just add a comparison for marks aswell in the equals method? And how can you expect 2 outputs from one console writeline? – Mono May 23 '19 at 06:33
  • Console.WriteLine("Marks in 2 collections are {0}", isEqual?"equal":"not equal"); I will add this line. Also I will add comparison for marks also. But I'm not sure how to write the mark comparison in Equals method. How the linq query will differentiate the difference between name and mark comparison. – HEMAMBUJAVALLY VIRAPPANE May 23 '19 at 06:44
  • if (x.name == y.name && x.mark == y.mark) return true; //This way also I can achieve comparison of 2 fields. Is there any other way differentiate the linq query. bool isNameEqual = studentList1.SequenceEqual(studentList2, new StudentComparer()); //using this I will print Name is equal. bool isMarkEqual = studentList1.SequenceEqual(studentList2, new StudentComparer()); //using this I will print marks are not equal. – HEMAMBUJAVALLY VIRAPPANE May 23 '19 at 06:53

4 Answers4

3

Your issue here is that Object.GetHashCode() is necessarily unique between objects. This means that when SequenceEqual(…) calls comparer.GetHashCode(…), it will get different values for different instances of objects, despite the fact that you have declared them equal in StudentComparer.Equals(…).

What all of this means is that, you should return hash codes which are unique in only the situations where instances of Student are unique according to the IEqualityComparer. For example, you could change your implementation of StudentComparer.GetHashCode(Student obj) to: return obj.name.GetHashCode()

corranrogue9
  • 206
  • 1
  • 3
1

Need to correctly implement the equality comparer like this (this code has been generated by 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 Why is '397' used for ReSharper GetHashCode override?.

vladimir
  • 13,428
  • 2
  • 44
  • 70
0

Basically you need to check whether are the names and marks equal in the Equals method and get the hash-code of the name property individually as well as mark property for IEqualityComparer to work correctly.

Modify your code to this:

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

Outputting:

 Contents in 2 collections are not equal
Kunal Mukherjee
  • 5,775
  • 3
  • 25
  • 53
  • if (x.name == y.name && x.mark == y.mark) return true; //This way also I can achieve comparison of 2 fields. Is there any other way differentiate the linq query. bool isNameEqual = studentList1.SequenceEqual(studentList2, new StudentComparer()); //using this I will print Name is equal. bool isMarkEqual = studentList1.SequenceEqual(studentList2, new StudentComparer()); //using this I will print marks are not equal. – HEMAMBUJAVALLY VIRAPPANE May 23 '19 at 06:52
  • 1
    @HEMAMBUJAVALLYVIRAPPANE basically you cant use `SequenceEqual` to check for individual properties as you're expecting – Kunal Mukherjee May 23 '19 at 06:55
-2

I would suggest you add the IEquatable Interface to your Student class, as it is less code and more clearly what you want to achive:

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

You can leave out the additional StudentComparer implementation.

NicKno
  • 19
  • 6