2016-08-29 67 views
3

在我的應用程序中,我碰到需要有三個字符串鍵爲一個類的實例(我使用C#3.5,所以我不能使用一個元組)。 https://stackoverflow.com/a/15804355/5090537正確使用多鍵詞典的自定義數據結構

調整其點點滴滴我的需要,在結束後我的自定義類是這樣的::

public class MultiKeyDictionary<K1, K2, K3, V> : Dictionary<K1, MultiKeyDictionary<K2, K3, V>> 
{ 
    public V this[K1 key1, K2 key2, K3 key3] 
    { 
     get 
     { 
      return ContainsKey(key1) ? this[key1][key2, key3] : default(V); 
     } 
     set 
     { 
      if (!ContainsKey(key1)) 
       this[key1] = new MultiKeyDictionary<K2, K3, V>(); 
      this[key1][key2, key3] = value; 
     } 
    } 

    public bool ContainsKey(K1 key1, K2 key2, K3 key3) 
    { 
     return base.ContainsKey(key1) && this[key1].ContainsKey(key2, key3); 
    } 

    public void Add(K1 key1, K2 key2, K3 key3, V value) 
    { 
     if (!ContainsKey(key1)) 
      this[key1] = new MultiKeyDictionary<K2, K3, V>(); 
     if (!this[key1].ContainsKey(key2, key3)) 
      this[key1][key2] = new Dictionary<K3, V>(); 
     this[key1][key2][key3] = value; 
    } 
} 

這對偉大的工作通過網上找,我碰到這個答案,其代碼我用來到我的需求,但我有這個數據結構的幾個問題:

1)因爲我實際上是從Dictionary(K1, Dictionary(K2, V))繼承,是否正確假設GetHashCode爲我實現,我不需要指定一個單獨的實現?對於Equals也一樣?

2)也是我需要創建自己的自定義類的正確前提?因爲我不能使用字符串數組或字符串列表,因爲那樣會有一個ReferenceEquals比較,而不是我需要的成員比較(key1等於key1,key2等於key2,key3等於key3)?

+0

使用'Dictionary ,YourClass>'一個簡單的解決方案或者你可以使用'List >' – Monah

+0

@HadiHassan:在問題中提到 - 「Tuple」類在C#4之前不可用。 –

+1

@GaryMcGill我認爲3個鍵的Tuple可以很容易地完成,但我不認爲要建立一個數據結構(字典字典的字典)來表示3個鍵的數據在這裏是一個不錯的選擇,(使用或從頭開始實現具有3個鍵和一個對象值的元組類更簡單直接)。我沒有仔細閱讀這個問題,直接閱讀下面的代碼。 – Monah

回答

2

嗯,創建一個可以存儲密鑰的自己的三鍵結構是一個很好的計劃,但首先讓我們來看看source codeKeyValuePair結構。

現在讓我們來定義自己的TripleKey結構:

[Serializable] 
public struct TripleKey<TKeyA, TKeyB, TKeyC> 
{ 
    public TKeyA KeyA { get; }; 
    public TKeyB KeyB { get; }; 
    public TKeyC KeyC { get; }; 

    public TripleKey(TKeyA keyA, TKeyB keyB, TKeyC keyC) 
    { 
     this.KeyA = keyA; 
     this.KeyB = keyB; 
     this.KeyC = keyC; 
    } 

    // this code is almost the same as it is in Microsoft implementation 
    public override string ToString() 
    { 
     var sBuilder = new StringBuilder(); 
     sBuilder.Append('('); 
     if (KeyA != null) 
     { 
      sBuilder.Append(KeyA.ToString()); 
     } 
     sBuilder.Append(", "); 
     if (KeyB != null) 
     { 
      sBuilder.Append(KeyB.ToString()); 
     } 
     sBuilder.Append(", "); 
     if (KeyC != null) 
     { 
      sBuilder.Append(KeyC.ToString()); 
     } 
     sBuilder.Append(')'); 
     return sBuilder.ToString(); 
    } 
} 

public static class TripleKey 
{ 
    public static TripleKey<TKeyA, TKeyB, TKeyC> Create<TKeyA, TKeyB, TKeyC>(TKeyA keyA, TKeyB keyB, TKeyC keyC) 
    { 
     return new TripleKey<TKeyA, TKeyB, TKeyC>(keyA, keyB, keyC); 
    } 
} 

public class MultiKeyDictionary<TKeyA, TKeyB, TKeyC, TValue> : Dictionary<TripleKey<TKeyA, TKeyB, TKeyC>, TValue> 
{ 
    public TValue this[TKeyA keyA, TKeyB keyB, TKeyC keyC] 
    { 
     get 
     { 
      var key = TripleKey.Create(keyA, keyB, keyC); 
      return base.ContainsKey(key) ? base[key] : default(TValue); 
     } 
     set 
     { 
      var key = TripleKey.Create(keyA, keyB, keyC); 
      if (!ContainsKey(key)) 
       base.Add(key, value); 

      this[key] = value; 
     } 
    } 

    public bool ContainsKey(TKeyA keyA, TKeyB keyB, TKeyC keyC) 
    { 
     var key = TripleKey.Create(keyA, keyB, keyC); 

     return base.ContainsKey(key); 
    } 

    public void Add(TKeyA keyA, TKeyB keyB, TKeyC keyC, TValue value) 
    { 
     base.Add(TripleKey.Create(keyA, keyB, keyC), value); 
    } 
} 

一個關於結構類型最大的事情之一是,因爲他們從ValueType繼承他們繼承其實現GetHashCode方法。這個實現的工作方式是,對於任何兩個具有相同值的結構,hashcode總是匹配的(然而,如果兩個hashcode匹配,則百分之百不保證所有值都相同)。

現在我們已經解決了,我們準備使用MultiKeyDictionary<TKeyA, TKeyB, TKeyC, TValue>或簡單的Dictionary<TripleKey<TKeyA, TKeyB, TKeyC>, TValue>

簡單的例子:

var myDict = new MultiKeyDictionary<string, double, double, string> 
{ 
    {"Goodbye", 0.55, 9.00, "yaya"} // collection initializer works fine 
}; 

myDict.Add("Hello", 1.11, 2.99, "hi"); 

Console.WriteLine(myDict.ContainsKey("Hello", 1.11, 2.99)); // true 
Console.WriteLine(myDict.ContainsKey("a", 1.11, 2.99));  // false 
Console.WriteLine(myDict["Hello", 1.11, 2.99]);    // hi 

myDict.Add(TripleKey.Create("Hello", 1.11, 2.99), "gh");  // bang! exception, 
                  // key already exists 

P.S.

正如ScottChamberlain指出的那樣,ValueTypeimplementation of GetHashcode方法有其優點和缺點。它使用反射,可能會導致性能問題,因此最好不要依賴struct的GetHashCode實現,並用自定義實現覆蓋它。

Eric Lippert的博客中有一篇很好的文章,名爲「Guidelines and rules for GetHashCode」。

工作例如:https://dotnetfiddle.net/y1a30V

+0

如果您依賴於'struct'的繼承'GetHashCode'實現,請注意。 [它可能不會做你的想法!](http://stackoverflow.com/a/5927853/98422) –

+1

這可能是值得它自己實現'GetHashCode()'。如果您正在進行大量操作,默認實現將使用反射,這可能是性能瓶頸。 –

+0

但另一方面,據我所知'KeyValuePair'方法'GetHashCode()'沒有實現。 – Fabjan

2

的GetHashCode

GetHashCode方法作爲「廉價」(快)的方式來測試你的類平等的兩個實例。調用GetHashCode兩個相同的實例應該總是產生相同的結果。因此,如果調用GetHashCode的結果對於兩個實例都不相同,則它們不可能相等,因此不必進行更詳細的(並且更「昂貴的」)比較。

[在另一方面,如果兩個實例具有相同的散列碼,那麼更詳細的比較是必要以確定它們是否實際上等於]

所以,除非你重新定義什麼「平等」意味着你的班級,你可能不需要擔心GetHashCode。無論如何,你們班的「平等」概念似乎並不是很有用。

級的設計

我不知道,你已經實現了類是理想的。因爲你從Dictionary繼承,你已經繼承了一些並不真正「適合」你的課程的方法。

例如,您的班級現在有一個Keys方法,該方法返回頂級鍵(key1),而不是您的班級實際表示的三值鍵。

我不知道是否會更好實現一類聚集一本字典,而不是一個來自字典繼承。

Tuple不存在的另一個選擇是定義自己的TriKey<K1, K2, K3>類(具有3個描述關鍵值的屬性),並且只使用Dictionary<TriKey<K1, K2, K3>, V>。在這種情況下,你絕對需要想爲你的TriKey類定義相等性,並且需要保持GetHashCode與相等定義一致,因爲字典查找是使用它的地方之一。

其它

最後一點,有些人可能認爲過早優化。代碼:

this[key1][key2][key3] = value; 

...是要執行2個查找對你已經獲取的值(因爲你已經訪問this[key1]this[key1][key2])。您可能需要考慮使用局部變量來存儲這些中間結果。

例如:

MultiKeyDictionary<K2, K3, V> d1; 
if (!TryGetValue(key1, out d1)) 
{ 
    d1 = new MultiKeyDictionary<K2, K3, V>(); 
    this[key1] = d1; 
} 

// now use d1 rather than "this[key1]" 

...等等的其他人。

+0

我喜歡使用我自己的TriKey類的想法。任何關於如何實現它並處理GetHashCode的建議/指導原則? – Iason

+0

@lason:參見[埃裏克利珀的帖子(https://blogs.msdn.microsoft.com/ericlippert/2011/02/28/guidelines-and-rules-for-gethashcode/)徵求意見的落實'GetGashCode',和[Marc Gravell的回答](http://stackoverflow.com/a/371348/98422)中有關如何組合多個值以生成複合哈希碼的示例。 –

0

這可能是實現你追求的最簡單的方法:

public class MultiKeyDictionary<TKey, TValue> : Dictionary<Tuple<TKey, TKey, TKey>, TValue> 
{ 
    public MultiKeyDictionary() 
     : base() 
    { 
    } 
    ... 
} 

class Program 
{ 
    static void Main(string[] args) 
    { 
     // C# 6.0 syntax 
     var multiKeyDictionary = new MultiKeyDictionary<string, int>(); 
     multiKeyDictionary.Add(Tuple.Create("key1", "key2", "key3"), 36); 

     // C# 7.0 syntax (not yet released). 
     var multiKeyDictionary1 = new MultiDictionary<string, int>(); 
     multiKeyDictionary1.Add(("key1", "key2", "key3"), 36); 
    } 
} 

在C#7.0發佈,你可以用漂亮的新的元組的聲明。