Opaque key dictionary template in C #

I came across many cases when a template for accessing elements in a collection with a key (for example, a dictionary) is saddled with the fact that the key type is not a simple type (string, int, double, etc.), and it’s not that what you would like to promote into a real named class.

C # 3.0 introduces the concept of anonymous types, which the compiler automatically generates. Unlike struct , these dynamically generated classes provide implementations of both Equals() and GetHashCode() - which are well suited for implementing a dictionary and hash table in .NET.

I grabbed this function to create an opaque key - essentially a generic class that allows you to create keys on the fly by providing types that are part of the key and using an anonymous class to actually provide Equals / GetHashCode Behavior. The purpose of this class is ONLY to provide an easy means of simultaneously using multiple values ​​as a key in a dictionary or hash table. It is not intended as a class to provide meaningful application logic or data manipulation.

The advantage of the template is that it simplifies the implementation of composite keys, which always provide appropriate equality and hashing behavior. It is also easily extensible for keys of any number of dimensions (although many C # can parse as template parameters, at least). We can even make improvements by letting the OpaqueKey <> class inherit from it so that its properties and constructor parameters can give more instructive names.

I am concerned that this picture may have some unintended consequences or hidden traps that I do not consider.

Are there any reasons why the following OpaqueKey code might be undesirable?

Are there any cases of ribs that I did not consider in the implementation?

Is there an easier way to achieve the same functionality?

 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
generics design-patterns
source share
9 answers

From what I can say, you can use the generic Tuple class and you don't need an internal anonymous class. The Tuple class can be useful elsewhere in your solution (so .NET 4.0 will have Tuple classes built in). Equals can compare First and Second values, while GetHashCode combines the hash codes of Tuple members.

  //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)); 
+2
source share

Are there any reasons why the following OpaqueKey code might be undesirable?

-> You save two times in a row.

Is there an easier way to achieve the same functionality?

-> Use a structure that avoids the anonymous type and stores it twice in a row. Equals / GetHashcode will be fine.

0
source share

Have you considered the edge case of (de) serializing a dictionary to / from XML?

0
source share

Wouldn't use KeyValuePair <TKey, TValue> how does the type of key have the same effect?

0
source share

I did the same without any adverse consequences. My opinion is this: go for it. I met a class that was often called "Tuple" in other languages ​​and in Microsoft internal classes, but I always called it CompositeKey<T1, T2> . I never implemented it the way you do it, relying on an anonymous type system for comparison and hashing.

Have you ever considered that your OpaqueKey will ever be needed for serialization? Should you worry that the default implementation of GetHashcode for anonymous types will change between .net versions?

You did the right thing to make sure your values ​​are truly read-only.

There is no simpler way to do this. I heard that dictionaries of dictionaries use the same effect, but it is much more complicated because you need to create a child dictionary if it does not exist.

0
source share

The disadvantages that I see are as follows:

  • Saving key data twice (once in the First and Second fields and again in m_Key), as Guillaume pointed out.
  • Perhaps the readability of the code is reduced. (The keys are pretty detailed, and new developers will need to become familiar with the OpaqueKey class and concepts.)
  • Future versions of the C # compiler may change the default implementation of GetHashCode for anonymous types. Although you are satisfied with the current results, I do not think that the logic will ever change.

I believe that this is not simpler, but I think that in most cases it is better to simply create a specific class (or structure) for each type of key that you need. Thus:

  • You do not lose memory, as you do with OpaqueKey.
  • This is more explicit, so the readability of the code is better, and you do not accept the dependence on the logic of creating an anonymous type of C # compilers for the important Equals and GetHashCode methods.
  • You can optimize for each type of composite key. (For example, some compound keys may have fields that are case insensitive for equality purposes, while other compound keys may yield a more optimal hash code algorithm than what the compiler chooses for anonymous types).
0
source share

Why not:

 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: just a thought, not tested ....

0
source share

I would make sure that GetHashCode for anonymous types is workable (i.e. does not use reflection and generates a good spread).

It is also possible to have restrictions on what types can be passed. Perhaps some types may cause problems?

0
source share

Something like:

 public class OpaqueKey { objects[] values; public OpaqueKey(params object[] values) { this.values = values; } //implement equals and GetHashCode() } 
0
source share

All Articles