Почему именно массив.IndexOf не проверяет IEquatable, как это делает список?


Рассмотрим Этот код:

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;

}

Результаты:

0
-1
Почему результаты различаются между List<T> и Array? Я предполагаю, что это сделано специально, но почему?

Глядя на код List<T>.IndexOf заставляет меня задуматься еще больше, так как он портируется на Array.IndexOf.

3 7

3 ответа:

Реализация IndexOf в классе array вызывает метод:

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

Как вы видите, он использует object в качестве параметра value. В этом методе есть код:

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

Classs работает с объектами, поэтому он вызывает метод public virtual bool Equals(object obj), а не универсальный.

В List классе IndexOf используется универсальная реализация:

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

Таким образом, он использует универсальный компаратор качества:

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

UPD : я написал небольшой пост об этой проблеме: http://blog.rogatnev.net/2017/07/14/IndexOf-with-IEquatable.html

Поскольку универсальные коллекции объектов используют интерфейс IEquatable<T> при тестировании на равенство в таких методах, как Contains, IndexOf, LastIndexOf, и Remove.

Массив ничего не знает о <T>, поэтому он не может реализовать или использовать IEquatable интерфейс.

Массив вместо этого содержит объекты, которые не являются универсальными. Он вызовет Equals Для сравнения одного объекта с другим, поскольку все объекты имеют метод Equals, который вы можете переопределить.

List<T> может использовать интерфейс IEquatable<T>, так что один работает, как ожидалось.

Массив использует метод Equals из Object, и вы не переопределяете его, а просто реализуете IEquatable.

Попробуйте определить Equals Как:

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

, Что будет работать в обоих случаях одинаково.