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... }