2009-07-22 2 views
2

Я столкнулся с множеством случаев, когда шаблон для доступа к элементам в коллекции с ключом (например, словарь) обременен тем фактом, что тип ключа не является простым типом (строка, 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... 
} 

ответ

2

Из того, что я могу сказать, вы можете использовать общий класс Tuple, и вам не нужен внутренний анонимный класс. Класс Tuple может быть полезен в другом месте вашего решения (поэтому .NET 4.0 будет иметь встроенные классы Tuple). Equals могут сравнивать значения First и Second, в то время как GetHashCode объединяет хэш-коды членов Tuple.

 //4.0 Beta1 GetHashCode implementation 
     int a = 5; int b = 10; 
     Tuple<int, int> t = new Tuple<int, int>(a, b); 

     Console.WriteLine(t.GetHashCode() == (((a << 5) + a)^b)); 
+0

Я пытался избежать нескольких реализаций Equals и GetHashCode. С анонимными типами компилятор уже генерирует их для вас. Слишком плохие кортежи не являются частью самого языка. С C# 4.0 - необходимость в OpaqueKey может быть уменьшена ... особенно если они предоставляют версии Tuple <> с большим количеством параметров типа. – LBushkin

+0

'Tuple' фактически поддерживает кортежи с неограниченной длиной, вам просто нужно кодировать очень длинные из них несколько неочевидным способом (в основном, кортеж, последний элемент которого также является кортежем, и вы можете удерживать вложенность неограниченно, пока не получите многие элементы, как вы хотите). –

0

Есть ли какие-либо причины, почему следующий код OpaqueKey может нежелательными?

-> Вы храните два раза свои строки.

Существует ли более простой способ достижения такой же функциональности?

-> Используйте структуру, которую вы избежите анонимного типа и сохраните два раза подряд. Equals/GetHashcode будет в порядке.

+1

Вы не можете использовать в качестве-структуру ключа в словаре, как эффективно. Словари полагаются на хэш-код объекта для размещения разных значений в разных кодах. Это то, где хеширование достигает своей производительности. Если вы спросите произвольную структуру для своего хэш-кода, то не рекомендуется производить хороший разброс значений хэш-кода. С другой стороны, OpaqueKey <> возвращает разумное распространение хэш-кодов для большинства входов. – LBushkin

0

Рассматривали ли вы крайний случай (де) сериализации словаря в/из XML?

0

Не использовал KeyValuePair < TKey, TValue >, так как тип ключа оказывает такое же влияние?

+0

Как ни странно, KeyValuePair не переопределяет Equals и GetHashCode. –

0

Я сам сделал то же самое без каких-либо неблагоприятных последствий. Мое мнение таково: пойдите для этого. Я встречал такой класс, который часто назывался «Tuple» на других языках и во внутренних классах Microsoft, но я всегда называл такую ​​вещь CompositeKey<T1, T2>. Я никогда не реализовывал его так, как вы это делаете, полагаясь на систему анонимного типа, чтобы выполнить сравнение и хеширование.

Рассматривали ли вы, что ваш OpaqueKey когда-нибудь понадобится для сериализации? Стоит ли беспокоиться о том, что реализация GetHashcode по умолчанию для анонимных типов будет изменяться между версиями .net?

Вы сделали все, чтобы ваши ценности были эффективно только для чтения.

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

0

Недостатков я вижу, являются:

  1. Хранение ключевых данных дважды (один раз в полях Первый и Второй и еще раз в m_Key), как указал Гийом.
  2. Возможно, читаемость кода уменьшается. (Ключи довольно подробные, и новые разработчики должны будут ознакомиться с классом и концепциями OpaqueKey.)
  3. Будущие версии компилятора C# могут изменить реализацию GetHashCode по умолчанию для анонимных типов. Хотя вы удовлетворены текущими результатами, я не думаю, что логика никогда не изменится.

Я полагаю, это не просто, но я думаю, что в большинстве случаев лучше просто создать определенный класс (или структуру) для каждого типа ключа, который вам понадобится. Таким образом:

  1. Вы не тратите память так же, как на OpaqueKey.
  2. Это более очевидно, поэтому читаемость кода лучше и вы не зависите от логики создания анонимного типа компиляторов C# для важных методов Equals и GetHashCode.
  3. Вы можете оптимизировать для каждого типа составного ключа. (Например, некоторые составные клавиши могут иметь поля, которые нечувствительны к регистру для целей равенства, а другие составные ключи могут поддаваться более оптимальному алгоритму хэш-кода, чем то, что выбирает компилятор для анонимных типов).
0

Почему нет:

public class OpaqueKey<A,B> 
{ 
    private readonly object m_Key; 

    public A First 
    { 
     get 
     { 
      return m_Key.GetType().GetProperty("K1").GetValue(m_key, null); 
     } 
    } 
    public B Second 
    { 
     get 
     { 
      return m_Key.GetType().GetProperty("K2").GetValue(m_key, null); 
     } 
    } 

    public OpaqueKey(A k1, B k2) 
    { 
     m_Key = new { K1 = k1, K2 = 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(); 
    } 
} 

PS: только мысль, не проверял ....

0

Я бы гарантировать, что GetHashCode для анонимных типов производительный (т.е. не использует отражения и генерирует хороший спред).

Возможно, также стоит иметь ограничения на то, какие типы могут быть переданы. Возможно, некоторые типы могут вызвать проблемы?

0

Что-то вроде:

public class OpaqueKey 
{ 
    objects[] values; 

    public OpaqueKey(params object[] values) 
    { 
     this.values = values; 
    } 

    //implement equals and GetHashCode() 
} 
+0

Это не безопасное для типа - вам нужно проверять тип и отливать при использовании ключа/значения из OpaqueKey. –

+0

, конечно, вы могли бы использовать дженерики, чтобы улучшить его. Но это только ваш ключ, который вы, например, будете использовать со словарем. Таким образом, вы, вероятно, не будете читать переданные значения, но метод Equals() будет использоваться для сравнения таких «многозначных ключей», игнорирующих его внутренние значения объекта – Juri

Смежные вопросы