Мой текущий проект использует Dapper для создания класса на основе строк из базы данных.
Я сократил свой класс, чтобы сосредоточиться на компонентах равенства моего вопроса, поэтому, пожалуйста, дайте мне знать, если какой-либо дополнительный коднеобходимо:
public class Computer
{
public int Id { get; internal set; }
public string OperatingSystem { get; set; }
}
public class Hardware : IRevertibleChangeTracking
{
public int Id { get; internal set; }
private string serialNumber;
public string SerialNumber
{
get => this.serialNumber;
set => this.SetField(ref this.serialNumber, value);
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
// compare against OriginalValues and store changed value in Changes
}
public ObservableCollection<Computer> Computers { get; }
private Dictionary<string, object> OriginalValues { get; set; }
// Equals override
public override bool Equals(object value)
{
return this.Equals(value as Hardware);
}
// Equals implementation
public bool Equals(Hardware hardware)
{
if (ReferenceEquals(null, hardware)) return false;
if (ReferenceEquals(this, hardware)) return true;
return object.Equals(this.Id, hardware.Id)
&& string.Equals(this.SerialNumber, hardware.SerialNumber)
&& this.Computers.SequenceEquals(hardware.Computers);
public static bool operator ==(Hardware hardwareA, Hardware hardwareB)
{
if (object.ReferenceEquals(hardwareA, hardwareB))
{
return true;
}
return !object.ReferenceEquals(null, hardwareA) && hardwareA.Equals(hardwareB);
}
public static bool operator !=(Hardware hardwareA, Hardware hardwareB)
{
return !(hardwareA == hardwareB);
}
public override void AcceptChanges()
{
// clear changes and update original calues
}
public override void RejectChanges()
{
// clear changes and revert object state
}
}
Я переопределил Equals, чтобы позволить моему классу сравнивать себя с другими экземплярами и упростить модульные тесты, включающие все свойства:
[Test]
public void CanCheckTwoHardwareAreTheSameWithEquals()
{
var firstHardware = Database.GetHardwareById(931);
var secondHardware = Database.GetHardwareById(931);
// i would rather this than asserting 50 properties are equal
Assert.IsTrue(firstAsset == secondAsset);
}
Тогда переопределяя Equalsгенерирует предупреждение в Visual Studio, чтобы также переопределить GetHashCode.Основываясь на моих собственных исследованиях ( MSDN , поиск в Google, похожие вопросы StackOverflow), я пришел к выводу, что:
- Если вы переопределяете Equals, вам также следует переопределить GetHashCode
- Если объект равен другому объекту, GetHashCode должен предоставлять одинаковое значение для обоих объектов
- GetHashCode используется различными внутренними коллекциями, и неправильная его переопределение может вызвать проблемы при использовании моего класса с этими коллекциями
- Значение из GetHashCode не должно изменяться в течение всего времени жизни объекта
Поскольку все свойства моего класса могут изменяться (даже идентификатор установлен на -1 и обновлен дляновые объекты), вместо того, чтобы полагаться на текущие значения, я мог бы реализовать GetHashCode, используя свойство ID и словарь OriginalValues, поскольку они меняют наименьшее значение:
public override int GetHashCode()
{
unchecked
{
// Choose large primes to avoid hashing collisions
const int HashBase = (int)2166136261;
const int HashMultiplier = 16777619;
var hash = HashBase;
hash = (hash * HashMultiplier) ^ (!object.ReferenceEquals(null, this.Id) ? this.Id.GetHashCode() : 0);
hash = (hash * HashMultiplier) ^ (!object.ReferenceEquals(null, this.OriginalValues["SerialNumber"]) ? this.OriginalValues["SerialNumber"].GetHashCode() : 0);
return hash;
}
}
Однако это будет работать, пока кто-то не вызовет AcceptChanges()
метод в моем классе и OriginalValues изменены (таким образом, производяt значение для GetHashCode()
).
Я также видел несколько ответов, которые просто return 0
как реализация GetHashCode()
public override int GetHashCode()
{
return 0;
}
. Это вернет значение, котороевсегда одно и то же, независимо от того, сколько раз менялись свойства моего класса.
Вопросы
- Является ли
return 0
допустимой реализацией GetHashCode
для изменчивых объектов, свойства которых, как ожидается, изменятся? - Есть ли какие-либо недостатки, о которых следует знать в этой реализации?(меньше производительности при поиске в словаре?)