Страницы

Поиск по вопросам

среда, 5 февраля 2020 г.

Массив как ключ в коллекции C#

#c_sharp #массивы #коллекции


Здравствуйте.

Имеется коллекция _data (Dictionary), где ключем выступает массив типа int[], а значением
является условный тип.

private static Dictionary _data = new Dictionary();


Я добавил в коллекцию какие то данные, например так:

_data.Add(new int[2] { 1, 2 }, new SomeType(p1, p2));


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

_data.ContainsKey(new int[2] { 1, 2 });


То всегда будет false. Я так понял потому что инициализирую все время новый массив,
а не даю ссылку на него. Сам вопрос заключается в том, как лучше и проще всего в данном
случае оперировать с набором данных как ключем. Я только изучаю этот прекрасный язык,
недавно почитав о структурах, понял что может быть их лучше использовать в данном случае,
но все таки хотел узнать у опытных программистов.
    


Ответы

Ответ 1



Дело в том, что Dictionary использует GetHashCode. Класс Array не переопределяет этот метод, а значит используется реализация класса System.Object. Под капотом вызывается нативный метод CLR ObjectNative::GetHashCode, который делает что-то, чего никто не знает. Но факт в том, что одинаковые объекты имеют разный хеш код, если они находятся по разным адресам. Вот пример: int[] x1 = new[] { 1, 2 }; int[] x2 = new[] { 1, 2 }; Console.WriteLine(x1.GetHashCode()); Console.WriteLine(x2.GetHashCode()); Выводит: 46104728 12289376 Dictionary в первую очередь сравнивает хеш-коды, а потом уже пытается сравнить объекты. Для вашей задачи нужно создать класс, который будет содержать внутри себя массив, переопределить в нем GetHashCode и метод Equals. А еще лучше реализовать интерфейс IEquatable. Как правильно реализовать GetHashCode - отдельный вопрос. Можно например "скопипастить" GetHashCode из класса System.String, который уж точно реализован отлично: class MyArray : IEquatable { private int[] _array; public MyArray(int[] array) { this._array = array; } public override int GetHashCode() { int num = 5381; int num2 = num; for (int i = 0; i < _array.Length; i += 2) { num = ((num << 5) + num ^ _array[i]); if (i + 1 == _array.Length) break; num2 = ((num2 << 5) + num2 ^ _array[i + 1]); } return num + num2 * 1566083941; } // Equals и др. } Теперь вам вместо ключа int[] нужно использовать экземпляры MyArray. int[] x1 = new[] { 1, 2 }; int[] x2 = new[] { 1, 2 }; var a1 = new MyArray(x1); var a2 = new MyArray(x2); Console.WriteLine(a1.GetHashCode()); Console.WriteLine(a2.GetHashCode()); Такой код теперь покажет одинаковые хеш-коды: -1776415353 -1776415353

Ответ 2



Вы правы, получить данные не удается, потому что ваш ключ ссылочного типа, и вы всегда создаете новый объект, вместо того чтобы использовать ссылку на оригинальный. В тех случаях, когда вы нигде не храните ссылки на оригинальные объекты, удобнее использовать в качестве ключей значимые типы или строки. Например - вот так: private string GetKey(params int[] values) { return string.Join("_", values); } _data.Add(GetKey(1, 2), new SomeType(p1, p2)); В таком случае вы в любое сможете получить ключ, нигде не храня его. Использовать ссылки на объекты в качестве ключей удобно только тогда, когда эти объекты у вас где-то еще "работают", и время от времени возникает необходимость получить информацию, имея на руках ссылку на такой объект. Судя по коду, ваша программа устроена не так.

Комментариев нет:

Отправить комментарий