2017-07-09 55 views
7

Rozważmy this kod:Dlaczego funkcja Array.IndexOf nie sprawdza, czy IEquatable podobna jest do listy <T>?

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; 

} 

Wyniki:

0 
-1 

Dlaczego są różne wyniki między List<T> i Array? Zgaduję, że jest to zgodne z projektem, ale dlaczego?

Patrząc na kod List<T>.IndexOf zastanawiam się jeszcze bardziej, ponieważ jest to portowanie do Array.IndexOf.

+1

Napisałem mały post na temat tego pytania: http://blog.rogatnev.net/2017/07/1 4/IndexOf-with-IEquatable.html – Backs

Odpowiedz

4

Realizacja IndexOf w metodzie połączeń klasy Array:

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

Jak widać, używa object jako parametr wartości. W tej metodzie nie ma kod:

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

Classs współpracuje z obiektów, dlatego wzywa public virtual bool Equals(object obj) sposób ogólny, a nie jeden.

W List klasy IndexOf używa rodzajowy realizacji:

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

Więc używa rodzajowy jakości porównywarka:

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

UPD: Napisałem mały słupek o tym problemie: http://blog.rogatnev.net/2017/07/14/IndexOf-with-IEquatable.html

+1

Dlatego właśnie * zawsze * powinno się nadpisywać' Equals (obiekt) '(i' GetHashCode' choć z różnych powodów) podczas implementacji 'IEquatable '. Bardzo prosta poprawka: 'public override bool Equals (object other) => Equals (inne jako Item); publiczne nadpisanie int GetHashCode() => Id; ' –

4

Ponieważ ogólne zbiory obiektu przy użyciu interfejsu IEquatable<T> podczas testowania równości w taki sposób, aby Contains, IndexOf, LastIndexOf i Remove.

Tablica nic nie wie o <T>, więc nie może zaimplementować ani używać interfejsu IEquatable.

Zamiast tego Array przechowuje obiekty, które nie są ogólne. Będzie wywoływać Equals, aby porównać jeden obiekt z drugim, ponieważ wszystkie obiekty mają metodę Equals, którą możesz przesłonić.

+0

Czy "T []" nie jest generyczne? – Shimmy

+0

Tak, dokładnie. 'T' jest ogólne, ale Array nie jest. Tablica zawiera obiekty, które nie są wpisane. Dlatego użyje object.Equals do porównań. –

2

List<T> może korzystać z interfejsu IEquatable<T>, aby działał zgodnie z oczekiwaniami.

Tablica korzysta z metody Equals z Object i nie zastępujesz tego, ale po prostu wdrażasz IEquatable.

Spróbuj definiowania Equals jak:

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

To będzie pracować dla obu przypadków w taki sam sposób.

+0

Możesz uniknąć powielania kodu, pisząc 'public override bool Equals (object other) => Equals (inne jako Item);', a to robi * not * throw 'NullReferenceException', jeśli' other' jest niezerowym obiektem, który jest nie "przedmiot". –

+0

Albo jeszcze '=> inny jest Przedmiot? item.Id == Id: false; ' – Shimmy