2016-08-29 62 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发布,你可以用漂亮的新的元组的声明。