Я столкнулся с множеством случаев, когда шаблон для доступа к элементам в коллекции с ключом (например, словарь) обременен тем фактом, что тип ключа не является простым типом (строка, int, double и т. д.) и не является тем, что вы хотели бы продвигать в настоящий именованный класс.Непрозрачный ключевой шаблон словаря в C#
В C# 3.0 вводится концепция анонимных типов, которые автоматически генерирует компилятор. В отличие от struct
, эти динамически сгенерированные классы обеспечивают реализацию как Equals()
, так и GetHashCode()
- которые хорошо подходят для реализации словаря и хэш-таблицы в .NET.
Я ухватил эту функцию, чтобы создать непрозрачный ключ - по существу общий класс, который позволяет вам создавать ключи «на лету», предоставляя типы, которые являются частью ключа, и используя анонимный класс для фактического предоставления равных/GetHashCode. Цель этого класса - ТОЛЬКО - это простой способ использовать несколько значений в качестве ключа в словаре или хеш-таблице. Он не предназначен как класс для обеспечения значимой логики приложения или манипуляции с данными.
Преимущество шаблона в том, что он упрощает реализацию составных клавиш, которые всегда обеспечивают соответствующее равенство и поведение хэширования. Он также легко расширяется для ключей любого числа измерений (однако многие C# могут анализировать как параметры шаблона, по крайней мере). Мы даже можем сделать улучшения, позволив классу OpaqueKey <> наследоваться, чтобы его свойства и параметры конструктора могли быть даны более поучительные имена.
Я обеспокоен тем, что у этого шаблона могут быть непредвиденные последствия или скрытые подводные камни, которые я не рассматриваю.
Есть ли причины, по которым следующий код OpaqueKey может быть нежелательным?
Есть ли какие-либо краевые случаи, которые я не рассматривал в реализации?
Существует ли более простой способ достижения такой же функциональности?
public class OpaqueKey<A,B>
{
private readonly object m_Key;
// Not strictly necessary, but possibly convenient...
public A First { get; private set; }
public B Second { get; private set; }
public OpaqueKey(A k1, B k2)
{
m_Key = new { K1 = k1, K2 = k2 };
First = k1;
Second = k2;
}
public override bool Equals(object obj)
{
var otherKey = obj as OpaqueKey<A, B>;
return otherKey == null ? false : m_Key.Equals(otherKey.m_Key);
}
public override int GetHashCode()
{
return m_Key.GetHashCode();
}
}
public static void TrivialTestCase()
{
var dict = new Dictionary<OpaqueKey<string,string>, string>();
dict.Add(new OpaqueKey<string, string>("A", "B"), "AB");
dict.Add(new OpaqueKey<string, string>("A", "C"), "AC");
dict.Add(new OpaqueKey<string, string>("A", "D"), "AD");
dict.Add(new OpaqueKey<string, string>("A", "E"), "AE");
var value = dict[new OpaqueKey<string,string>("A","D")];
Debug.Assert(value == "AD"); // trivial test case...
}
Я пытался избежать нескольких реализаций Equals и GetHashCode. С анонимными типами компилятор уже генерирует их для вас. Слишком плохие кортежи не являются частью самого языка. С C# 4.0 - необходимость в OpaqueKey может быть уменьшена ... особенно если они предоставляют версии Tuple <> с большим количеством параметров типа. – LBushkin
'Tuple' фактически поддерживает кортежи с неограниченной длиной, вам просто нужно кодировать очень длинные из них несколько неочевидным способом (в основном, кортеж, последний элемент которого также является кортежем, и вы можете удерживать вложенность неограниченно, пока не получите многие элементы, как вы хотите). –