Чтобы гарантировать, что обе стороны составного ключа также уникальны, кортеж не будет разрезать его. Вместо этого создайте свой собственный ключ, который проверяет это в средстве проверки на равенство.
public struct CompositeKey<T1, T2> : IEquatable<CompositeKey<T1, T2>>
{
private static readonly EqualityComparer<T1> t1Comparer = EqualityComparer<T1>.Default;
private static readonly EqualityComparer<T2> t2Comparer = EqualityComparer<T2>.Default;
public T1 Key1;
public T2 Key2;
public CompositeKey(T1 key1, T2 key2)
{
Key1 = key1;
Key2 = key2;
}
public override bool Equals(object obj) => obj is CompositeKey<T1, T2> && Equals((CompositeKey<T1, T2>)obj);
public bool Equals(CompositeKey<T1, T2> other)
{
return t1Comparer.Equals(Key1, other.Key1)
&& t2Comparer.Equals(Key2, other.Key2);
}
public override int GetHashCode() => Key1.GetHashCode();
}
Таким образом, словарь работает на ведрах. Он помещает все ключи в сегменты на основе хеш-кода, сгенерированного GetHashCode()
. Затем он ищет этот сегмент, используя цикл for Equals()
. Идея состоит в том, что ведра должны быть как можно меньше (в идеале один предмет).
Таким образом, мы можем контролировать, когда ключ будет совпадать, и сколько блоков / элементов существует, управляя хеш-кодом. Если мы возвращаем постоянный хэш-код, например, 0, тогда все в одном и том же сегменте, и все зависит от метода равенства для сравнения каждого элемента.
Этот компаратор возвращает только хэш первого ключевого элемента. Предполагая, что первый ключевой элемент должен быть уникальным, этого достаточно. Каждый сегмент должен по-прежнему быть одним элементом, и при выполнении поиска (в котором используется метод полного равенства) именно тогда проверяется и второй ключ, чтобы убедиться, что тип является тем же значением.
Если вы хотите использовать ValueTuple
в качестве типа ключа, вы можете передать пользовательский компаратор в словарь для достижения того же эффекта.
public class CompositeValueTupleComparer<T1, T2> : IEqualityComparer<(T1, T2)>
{
private static readonly EqualityComparer<T1> t1Comparer = EqualityComparer<T1>.Default;
private static readonly EqualityComparer<T2> t2Comparer = EqualityComparer<T2>.Default;
public bool Equals((T1, T2) x, (T1, T2) y) =>
t1Comparer.Equals(x.Item1, y.Item1) && t2Comparer.Equals(x.Item2, y.Item2);
public int GetHashCode((T1, T2) obj) => obj.Item1.GetHashCode();
}
new Dictionary<(int, string), Book>(new CompositeValueTupleComparer<int, string>());