Why is Array.IndexOf not checking for IEquatable like List<T> does?
Asked Answered
J

3

8

Consider this code:

public static void Main()
{
    var item = new Item { Id = 1 };

    IList list = new List<Item> { item };
    IList array = new[] { item };

    var newItem = new Item { Id = 1 };

    var lIndex = list.IndexOf(newItem);
    var aIndex = array.IndexOf(newItem);

    Console.WriteLine(lIndex);
    Console.WriteLine(aIndex);
}

public class Item : IEquatable<Item>
{
    public int Id { get; set; }

    public bool Equals(Item other) => other != null && other.Id == Id;

}

Results:

0
-1

Why are the results different between List<T> and Array? I guess this is by design, but why?

Looking at the code of List<T>.IndexOf makes me wonder even more, since it's porting to Array.IndexOf.

Jeanettajeanette answered 9/7, 2017 at 2:21 Comment(1)
I wrote little post about this question: blog.rogatnev.net/2017/07/14/IndexOf-with-IEquatable.htmlRoyce
R
5

Implementation of IndexOf in array class calls method:

public static int IndexOf(Array array, object value, int startIndex, int count)

As you see, it uses object as value parameter. In this method there is code:

object obj = objArray[index];
if (obj != null && obj.Equals(value))
    return index;

Classs works with objects, so it calls public virtual bool Equals(object obj) method, not generic one.

In List class IndexOf uses generic implementation:

public static int IndexOf<T>(T[] array, T value, int startIndex, int count)

So, it uses generic quality comparer:

EqualityComparer<T>.Default.IndexOf(array, value, startIndex, count);

UPD: I wrote a little post about this problem: http://blog.rogatnev.net/2017/07/14/IndexOf-with-IEquatable.html

Royce answered 9/7, 2017 at 2:38 Comment(1)
This is why you always should override Equals(object) (and GetHashCode though for different reasons) when implementing IEquatable<T>. Very simple fix: public override bool Equals(object other) => Equals(other as Item); public override int GetHashCode() => Id;Maidamaidan
G
4

Because generic object collections use the IEquatable<T> interface when testing for equality in such methods as Contains, IndexOf, LastIndexOf, and Remove.

An Array knows nothing about <T> so it can't implement or use the IEquatable interface.

An Array instead holds objects that are not generic. It will call Equals to compare one object to another as all objects have an Equals method which you are free to override.

Garrard answered 9/7, 2017 at 2:33 Comment(2)
Isn't T[] generic?Jeanettajeanette
Yes exactly. T is generic but Array is not. Array contains objects that are not typed. Therefore it will use object.Equals for comparisons.Garrard
S
3

List<T> can make use of the IEquatable<T> interface, so that one works as expected.

The array is using the Equals method from Object and you are not overriding that one, but rather just implementing IEquatable.

Try defining Equals like:

public override bool Equals(Object other) => other != null && (other as Item).Id == Id;

That would work for both cases in the same way.

Scrooge answered 9/7, 2017 at 2:35 Comment(2)
You can avoid code duplication by writing public override bool Equals(object other) => Equals(other as Item);, and this does not throw NullReferenceException if other is a non-null object that is not an Item.Maidamaidan
Or yet => other is Item item ? item.Id == Id : false;Jeanettajeanette

© 2022 - 2024 — McMap. All rights reserved.