Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
каждый поток имеет свой собственный генератор для хэш-кодов, так что мы не можем попасть в ситуацию, где два потока последовательно генерируют одинаковый хэш-код.Таки в коде по-английски написано иное: «… ситуацию, где два потока устойчиво генерируют одинаковые хэш-коды». Ещё точнее было бы сказать – ситуация, когда совпадут две последовательности хэш-кодов, генерируемые двумя разными потоками (не обязательно в одно и то же время).
CLR выбирает первое поле структуры, на основание которого и создает хеш-код. Это поле по возможности должно быть неизменяемым, например, иметь тип string, иначе при его изменении хеш-код будет так же меняться, и мы не сможем уже найти нашу структуру в хеш-таблице, если она использовалась в качестве ключа. Получается, если первое поле структуры будет изменяемым, то это ломает стандартную логику метода GetHashCode.Когда изменяем содержимое структуры, её хэш-код как правило должен измениться, неправда ли.
структуры должны быть неизменяемыми.Поржал
readonly. Чтобы хэш-код структуры не мог измениться. Якобы изменяемая структура может иметь проблемы с хэш-таблицами.readonly (а также тип System.String) или что?class Program
{
struct Test
{
public DateTime Field;
}
static void Main()
{
Test v = new Test(){ Field = DateTime.Now};
Console.WriteLine( "{0}", v.GetHashCode()); // Одно значение
v.Field = DateTime.Today;
Console.WriteLine( "{0}", v.GetHashCode()); // Другое значение
}
}readonly не требуется.void Main()
{
var key1 = new Key{
RefField = new Reference {InnerField = 5},
ValueField = 3
};
SetKey(key1);
"Before change".Dump();
ShowSetKeyHashCode();
key1.ValueField = 3;
"After value field change".Dump();
ShowSetKeyHashCode();
key1.RefField.InnerField = 8;
"After reference field change".Dump();
ShowSetKeyHashCode();
}
private Key _setKey;
void SetKey(Key key)
{
_setKey = key;
}
void ShowSetKeyHashCode()
{
ShowKeyHashCode(_setKey);
}
void ShowKeyHashCode(Key key)
{
string.Format("Val:{0}, ref: {1}, hash: {2}",key.ValueField,key.RefField.InnerField,key.GetHashCode()).Dump();
}
class Reference
{
public int InnerField;
public override int GetHashCode()
{
return InnerField;
}
}
struct Key
{
public Reference RefField;
public int ValueField;
}В общем случае хэш-код как раз должен быть неизменяемым после инициализации ключа, т. к. реализации таблиц могут быть всякиеПожалуйста, обоснуйте как-нибудь. Какой такой должна быть хэш-таблица, чтобы стала проблемой изменяемая структура в качестве ключа?
у структуры Key поле RefFieldХорошо, пусть так. А как в этом случае мы относимся к такому коду:
Key key2 = key1;
key1.RefField.InnerField = 8;
Reference сразу в двух объектах типа Key. Код компилироваться не должен бы.Reference должен быть неизменяемым. Но это никак не связано с хэш-кодами. Неизменяемость Reference требуется и без применения хэш‑таблиц.Key ссылаются на один Reference и этот Reference изменяем (живёт какой‑то своей жизнью).Reference) не должен бы переопределять метод GetHashCode. Лучше использовать базовый GetHashCode.Key.GetHashCode, так чтобы он вовсе не использовал Reference или использовал только его неизменяемые поля, а если таковых нет, то добавить, например, неизменяемое поле Reference.Id.Какой такой должна быть хэш-таблица, чтобы стала проблемой изменяемая структура в качестве ключа?
Вариант 1.
Например, предоставляющей исходный массив этих ключейТогда появляется возможность присвоить значение целиком элементу массива. Неизменяемость типа ключа не спасает.
предлагается политика, позволяющая обезопаситьсяА вот есть политика лучше: структура должна переопределить
GetHashCode. Тем более что умолчательный GetHashCode неказист – чреват плохим распределением хэш-кодов. А написать переопределение просто (ниже).на вопрос зачем передавать как ключ изменяемую структуру — гораздо сложнее.Без проблем.
struct Segment
{
Image OwningImage; // Ссылочный тип.
int X1, Y1;
int X2, Y2;
... // Конструктор копирования.
}
seg2 такой же как seg1, но первый конец сдвинуть на 10 точек.Segment seg2 = new Segment( seg1){ X1 = seg1.X1 + 10,};
Segment seg2 = new Segment( seg1.OwningImage, seg1.X1 + 10, seg1.Y1, seg1.X2, seg1.Y2);
Читабельность хуже плюс шанс ошибиться в порядке аргументов. Либо добавлять вспомогательный метод CloneWithMovedEdge1.public в определениях полей в структуре Segment.Тогда появляется возможность присвоить значение целиком элементу массива. Неизменяемость типа ключа не спасает.
А вот есть политика лучше: структура должна переопределить GetHashCode.
Либо добавлять вспомогательный метод CloneWithMovedEdge1.
Хотя ситуация с изменением объекта после его помещения в хэш таблицу...Пожалуйста, поподробнее про это изменение после помещения. Как такое вообще могло бы выглядеть? Пример кода бы.
using System;
using System.Collections.Generic;
namespace HashTest
{
public struct MutableStruct
{
private static readonly Random _rnd = new Random();
public readonly int Id;
public string Title;
public MutableStruct(string title)
{
Id = _rnd.Next();
Title = title;
}
}
class Program
{
static void Main(string[] args)
{
var tree = new Dictionary<MutableStruct, object>();
var key = new MutableStruct("Nice title");
tree[key] = new object();
Console.WriteLine("Struct is in tree: " + tree.ContainsKey(key)); // True
key.Title = "Even better title";
Console.WriteLine("Struct is in tree: " + tree.ContainsKey(key)); // False
Console.ReadLine();
}
}
}
разработчики CLR говорят, чтобы все пользовательские значимые типы данных (да и не только значимые, а все вообще) переопределяли метод GetHashCode.А сами не переопределили для
KeyValuePair, как мы теперь видим.struct Point {
public int X { get; private set; }
public int Y { get; private set; }
...
public override int GetHashCode() {
return new { X, Y }.GetHashCode();
}
}

Откуда растут руки у GetHashCode в .NET