Deva Raja
Deva Raja

Reputation: 93

Regarding Contains and Equals in C#

Newbie here. I read the following code at Microsoft docs. You can see the webpage here: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.find?view=net-6.0

public class Part : IEquatable<Part>
{
    public string PartName { get; set; }
    public int PartId { get; set; }

    
    //other code

    public override bool Equals(object obj)
    {
        if (obj == null) return false;
        Part objAsPart = obj as Part;
        if (objAsPart == null) return false;
        else return Equals(objAsPart);
    }
    

    // other code

    public bool Equals(Part other)
    {
        if (other == null) return false;
        return (this.PartId.Equals(other.PartId));
    }
    
}

public class Example
{
    public static void Main()
    {
        // Create a list of parts.
        List<Part> parts = new List<Part>();

        // Add parts to the list.
        parts.Add(new Part() { PartName = "crank arm", PartId = 1234 });
        parts.Add(new Part() { PartName = "chain ring", PartId = 1334 });
        parts.Add(new Part() { PartName = "regular seat", PartId = 1434 });
        parts.Add(new Part() { PartName = "banana seat", PartId = 1444 });
        parts.Add(new Part() { PartName = "cassette", PartId = 1534 });
        parts.Add(new Part() { PartName = "shift lever", PartId = 1634 }); ;

        //other code

        // Check the list for part #1734. This calls the IEquatable.Equals method
        // of the Part class, which checks the PartId for equality.
        Console.WriteLine("\nContains: Part with Id=1734: {0}",
            parts.Contains(new Part { PartId = 1734, PartName = "" }));

     }
}

Now, my question is about parts.Contains(new Part { PartId = 1734, PartName = "" }) line. This is the ListT.Contains(T) method.

As you can see from the Microsoft reference source (here: https://referencesource.microsoft.com/#mscorlib/system/collections/generic/list.cs,521b9f7129105e15), the code for Contains method is the following:

public bool Contains(T item) {
            if ((Object) item == null) {
                for(int i=0; i<_size; i++)
                    if ((Object) _items[i] == null)
                        return true;
                return false;
            }
            else {
                EqualityComparer<T> c = EqualityComparer<T>.Default;
                for(int i=0; i<_size; i++) {
                    if (c.Equals(_items[i], item)) return true;
                }
                return false;
            }
        }

So as you can see Contains method uses Equals method, but which Equals of all? c.Equals(_items[i], item) means that the method we are calling belongs to EqualityComparer<T>.Default. Indeed Default is a property of EqualityComparer<T> class and it returns an object of the same class. So the Equals we see here should belong to the EqualityComparer<T> class.

Question #1: How can we have an object of EqualityComparer<T> class since this class is abstract?

Question #2: How can we call c.Equals(_items[i], item) since this method is also abstract? (as you can see here: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1.equals?view=net-6.0

But the most important of all, Question #3: How do we go from c.Equals(_items[i], item) method of class EqualityComparer<T>, to the IEquatable.Equals method which is implemented in the Part class. Does the first method invoke the second?

Here: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.equalitycomparer-1?view=net-6.0 it says the following:

"The Default property checks whether type T implements the System.IEquatable<T> generic interface and, if so, returns an EqualityComparer<T> that invokes the implementation of the IEquatableT.Equals method. Otherwise, it returns an EqualityComparer<T>, as provided by T."

I don't understand how the second method, IEquatable.Equals, is invoked.

I am sorry for the long post. Thanks in advance!

Upvotes: 5

Views: 221

Answers (1)

Guru Stron
Guru Stron

Reputation: 142943

How can we have an object of EqualityComparer<T> class since this class is abstract?

You don't, you have an instance of some inheritor of EqualityComparer<T> created in the EqualityComparer<T>.CreateComparer method.

Question #2: How can we call c.Equals(_items[i], item) since this method is also abstract?

Because concrete inheritor of EqualityComparer<T> should and implements this method (read the doc about abstract keyword). Slightly modified example from the doc may clarify some things:

// create an instance of Square and save to variable of Square type:
Square square = new Square(12); 
// assign Square to variable of Shape type, works because Square is Shape:
Shape shape = square; 
// access the abstract method declared on Shape and implemented by Square:
Console.WriteLine($"Area of the square = {shape.GetArea()}");

abstract class Shape
{
    public abstract int GetArea();
}

class Square : Shape
{
    private int _side;

    public Square(int n) => _side = n;

    // GetArea method is required to avoid a compile-time error.
    public override int GetArea() => _side * _side;
}

Question #3: How do we go from c.Equals(_items[i], item) method of class EqualityComparer<T>, to the IEquatable.Equals method which is implemented in the Part class. Does the first method invoke the second?

Because already mentioned CreateComparer method checks if generic type T implements this interface and if it does - uses the implementation in the Default instance:

// If T implements IEquatable<T> return a GenericEqualityComparer<T>
if (typeof(IEquatable<T>).IsAssignableFrom(t)) {
     return (EqualityComparer<T>)RuntimeTypeHandle.CreateInstanceForAnotherGenericParameter((RuntimeType)typeof(GenericEqualityComparer<int>), t);
}

Few extra notes:

  • To better understand how this works you might want to read about generics: here, here and some important info here
  • referencesource.microsoft.com is for the older .NET Framework sources, if you are working with modern .NET Core (including .NET 5, .NET 6 and upcoming .NET 7) you can use source.dot.net

Upvotes: 5

Related Questions