Почему KeyValuePair не переопределяет Equals() и GetHashCode()?
Я собирался использовать KeyValuePair
в сравнительно интенсивном коде и был озадачен проверкой того, как он реализован в.NET (см. ниже)
Почему это не переопределить Equals
а также GetHashCode
для эффективности (а не реализовать ==
) но вместо этого использует реализацию по умолчанию, основанную на медленном отражении?
Я знаю, что структуры / типы значений имеют реализацию по умолчанию, основанную на отражении их GetHashCode()
а также Equals(object)
методы, но я полагаю, что это очень неэффективно по сравнению с переопределением равенства, если вы делаете много сравнений.
РЕДАКТИРОВАТЬ Я сделал несколько тестов и обнаружил, что в моем сценарии (списки WPF) оба по умолчанию KeyValuePair
и моя собственная реализация переопределения структуры GetHashCode()
а также Equals(object)
оба гораздо медленнее, чем реализация как класс!
// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
/*============================================================
**
** Interface: KeyValuePair
**
** <OWNER>[....]</OWNER>
**
**
** Purpose: Generic key-value pair for dictionary enumerators.
**
**
===========================================================*/
namespace System.Collections.Generic {
using System;
using System.Text;
// A KeyValuePair holds a key and a value from a dictionary.
// It is used by the IEnumerable<T> implementation for both IDictionary<TKey, TValue>
// and IReadOnlyDictionary<TKey, TValue>.
[Serializable]
public struct KeyValuePair<TKey, TValue> {
private TKey key;
private TValue value;
public KeyValuePair(TKey key, TValue value) {
this.key = key;
this.value = value;
}
public TKey Key {
get { return key; }
}
public TValue Value {
get { return value; }
}
public override string ToString() {
StringBuilder s = StringBuilderCache.Acquire();
s.Append('[');
if( Key != null) {
s.Append(Key.ToString());
}
s.Append(", ");
if( Value != null) {
s.Append(Value.ToString());
}
s.Append(']');
return StringBuilderCache.GetStringAndRelease(s);
}
}
}
3 ответа
Как указывают другие ответы, вы получаете равенство и хеширование "бесплатно", поэтому вам не нужно переопределять их. Однако вы получаете то, за что платите; Реализации по умолчанию равенства и хеширования (1) не особенно эффективны в некоторых случаях, и (2) могут выполнять побитовые сравнения, и, следовательно, могут выполнять такие вещи, как сравнение отрицательных нулей и положительных нулей как разных, когда они логически равны.
Если вы ожидаете, что ваша структура будет часто использоваться в контекстах, которые требуют равенства и хеширования, то вы должны написать собственные реализации обоих и следовать соответствующим правилам и рекомендациям.
https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/
Итак, чтобы ответить на ваш вопрос: почему никто не сделал этого для определенного типа? Вероятно, потому что они не верили, что это было хорошим использованием их времени по сравнению со всеми другими вещами, которые они должны были сделать, чтобы улучшить библиотеки базовых классов. Большинство людей не сравнивают пары ключ-значение на равенство, поэтому их оптимизация, вероятно, не была приоритетом.
Это, конечно, предположительно; если вы действительно хотите узнать причину, по которой что-то не было сделано в определенный день, вам нужно будет отследить всех людей, которые не выполняли это действие, и спросить их, что еще они делают, что было более важно для этот день.
Это структура, структуры наследуются от ValueType
и этот тип уже переопределяет реализацию Equals и GetHashCode.
Не поддерживает ==
делать следующее не будет даже компилировать
var result = new KeyValuePair<string, string>("KVP", "Test1") ==
new KeyValuePair<string, string>("KVP", "Test2");
Вы получите ошибку "Оператор '==" нельзя применить к операндам типа KeyValuePair<string, string>
а также KeyValuePair<string, string>
"
KeyValuePair
является структурой (неявно наследуется от ValueType
) и Равенство работает просто отлично
var a = new KeyValuePair<string, string>("a", "b");
var b = new KeyValuePair<string, string>("a", "b");
bool areEqual = a.Equals(b); // true
Ссылка ниже показывает равную стратегию:
1- та же ссылка.
2- Можно сравнить по битам.
3- Сравните каждое поле в структуре, используя отражение.
public abstract class ValueType {
[System.Security.SecuritySafeCritical]
public override bool Equals (Object obj) {
BCLDebug.Perf(false, "ValueType::Equals is not fast. "+this.GetType().FullName+" should override Equals(Object)");
if (null==obj) {
return false;
}
RuntimeType thisType = (RuntimeType)this.GetType();
RuntimeType thatType = (RuntimeType)obj.GetType();
if (thatType!=thisType) {
return false;
}
Object thisObj = (Object)this;
Object thisResult, thatResult;
// if there are no GC references in this object we can avoid reflection
// and do a fast memcmp
if (CanCompareBits(this))
return FastEqualsCheck(thisObj, obj);
FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
for (int i=0; i<thisFields.Length; i++) {
thisResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(thisObj);
thatResult = ((RtFieldInfo)thisFields[i]).UnsafeGetValue(obj);
if (thisResult == null) {
if (thatResult != null)
return false;
}
else
if (!thisResult.Equals(thatResult)) {
return false;
}
}
return true;
}