Hashtable with MultiDimensional Key in C#

Viewed 90468

I'm basically looking for a way to access a hashtable value using a two-dimensional typed key in c#.

Eventually I would be able to do something like this

HashTable[1][false] = 5;
int a = HashTable[1][false];
//a = 5

This is what I've been trying...hasn't worked

Hashtable test = new Hashtable();
test.Add(new Dictionary<int, bool>() { { 1, true } }, 555);
Dictionary<int, bool> temp = new Dictionary<int, bool>() {{1, true}};
string testz = test[temp].ToString(); 
16 Answers

I think a better approach is to encapsulate the many fields of your multi-dimensional key into a class / struct. For example

struct Key {
  public readonly int Dimension1;
  public readonly bool Dimension2;
  public Key(int p1, bool p2) {
    Dimension1 = p1;
    Dimension2 = p2;
  }
  // Equals and GetHashCode ommitted
}

Now you can create and use a normal HashTable and use this wrapper as a Key.

You can do this in C# 7.0 now with the new tuples:

// Declare
var test = new Dictionary<(int, bool), int>();

// Add
test.Add((1, false), 5);

// Get
int a = test[(1, false)];

How about using a regular Dictionary with some kind of Tuple structure as a key?

public class TwoKeyDictionary<K1,K2,V>
{
    private readonly Dictionary<Pair<K1,K2>, V> _dict;

    public V this[K1 k1, K2 k2]
    {
        get { return _dict[new Pair(k1,k2)]; }
    }

    private struct Pair
    {
        public K1 First;
        public K2 Second;

        public override Int32 GetHashCode()
        {
            return First.GetHashCode() ^ Second.GetHashCode();
        }

        // ... Equals, ctor, etc...
    }
}

I think this might be closer to what you're looking for...

var data = new Dictionary<int, Dictionary<bool, int>>();

I'd suggest a slight variation on jachymko's solution which will allow you to avoid creating a class for key pairs. Instead, wrap a private dictionary of dictionaries, as so:

public class MultiDictionary<K1, K2, V>
{
    private Dictionary<K1, Dictionary<K2, V>> dict = 
        new Dictionary<K1, Dictionary<K2, V>>();

    public V this[K1 key1, K2 key2]
    {
        get
        {
            return dict[key1][key2];
        }

        set
        {
            if (!dict.ContainsKey(key1))
            {
                dict[key1] = new Dictionary<K2, V>();
            }
            dict[key1][key2] = value;
        }
    }
}

You need a key class for the Dictonary that implements GetHashCode correctly. And you can extend Dictonary to let you access it in a friendly way.

The KeyPair class:

public class KeyPair<Tkey1, Tkey2>
{
    public KeyPair(Tkey1 key1, Tkey2 key2)
    {
        Key1 = key1;
        Key2 = key2;
    }

    public Tkey1 Key1 { get; set; }
    public Tkey2 Key2 { get; set; }

    public override int GetHashCode()
    {
        return Key1.GetHashCode() ^ Key2.GetHashCode();
    }
    public override bool Equals(object obj)
    {
        KeyPair<Tkey1, Tkey2> o = obj as KeyPair<Tkey1, Tkey2>;
        if (o == null)
            return false;
        else
            return Key1.Equals(o.Key1) && Key2.Equals(o.Key2);
    }
}

Extend Dictonary<>:

public class KeyPairDictonary<Tkey1, Tkey2, Tvalue> 
    : Dictionary<KeyPair<Tkey1, Tkey2>, Tvalue>
{
    public Tvalue this[Tkey1 key1, Tkey2 key2]
    {
        get
        {
            return this[new KeyPair<Tkey1, Tkey2>(key1, key2)];
        }
        set
        {
            this[new KeyPair<Tkey1, Tkey2>(key1, key2)] = value;
        }
    }
}

You can use it like this:

KeyPairDictonary<int, bool, string> dict = 
    new KeyPairDictonary<int, bool, string>();

dict[1, false] = "test";
string test = dict[1, false];

I would suggest that you create a small custom class exposing the bool and int properties, and override its GetHashCode and Equals methods, then use this as the key.

You might be able to "double-nest" your hashtables - in other words, your main Dictionary is of type Dictionary<int, Dictionary<bool, my_return_type>>.

That accomplishes your goal of being able to use the double bracket notation in your first code snippet.

Of course, the management side is a little trickier. Every time you add an entry, you need to test if the main dictionary contains a dictionary for the primary key, and add a new dictionary if not, then add the secondary key and value to the inner Dictionary.

Could you use a Dictionary<KeyValuePair<int,bool>,int>?

Wrap your two-dimensional key in a separate type and use that type as a key. Also consider overriding GetHashCode() and Equals() methods. Preferably use Dictionary<> instead of HashTable since apparently you can use that.

A quick and dirty way would be to create a composite key from the two pieces of information, e.g.

IDictionary<string, int> values = new Dictionary<string, int>();
int i = ...;
bool b = ...;
string key = string.Concat(i, '\0', b);
values[key] = 555;

To encapsulate this a bit better you could wrap the dictionary:

public class MyDict
{
    private readonly IDictionary<string, int> values = new Dictionary<string, int>();

    public int this[int i, bool b]
    {
        get
        {
            string key = BuildKey(i, b);
            return values[key];
        }

        set
        {
            string key = BuildKey(i, b);
            values[key] = value;
        }
    }

    private static string BuildKey(int i, bool b)
    {
        return string.Concat(i, '\0', b);
    }
}

To make this more robust, encapsulate the composite key as a type, e.g. a class that contains the two fields, ensuring you override the Equals() and GetHashCode() methods correctly.

Look, this code works just fine:

    public Form1()
    {
            InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {

        this.Services = new Dictionary<object, Hashtable>();
        this.Services.Add("array1", new Hashtable());

        this.Services["array1"]["qwe"] = "123";
        this.Services["array1"][22] = 223;

        object zz = null;
        zz = this.Services["array1"]["qwe"];
        MessageBox.Show(zz.ToString()); // shows qwe

        zz = this.Services["array1"][22];
        MessageBox.Show(zz.ToString()); // shows 22
    }

Now we just need a wrapper to avoid manually doing this.Services.Add("array1", new Hashtable());

I think the easiest way to do it now is to use Tupple.Create and ValueTuple.Create:

> var k1 =  Tuple.Create("test", int.MinValue, DateTime.MinValue, double.MinValue);
> var k2 = Tuple.Create("test", int.MinValue, DateTime.MinValue, double.MinValue);
> var dict = new Dictionary<object, object>();
> dict.Add(k1, "item");
> dict.Add(k2, "item");
An item with the same key has already been added....
> dict[k1] == dict[k2]
true

or use new c#7 tuple syntax to create tuple-keys:

var k = (item1: "value1", item2: 123);

This is my nested Dictionary implementation:

public class TwoKeysDictionary<K1, K2, T>:
        Dictionary<K1, Dictionary<K2, T>>
{
    public T this[K1 key1, K2 key2]
    {
        get => base.ContainsKey(key1) && base[key1].ContainsKey(key2) ? base[key1][key2] : default;
        set
        {
            if (ContainsKey(key1) && base[key1].ContainsKey(key2))
                base[key1][key2] = value;
            else
                Add(key1, key2, value);
        }
    }

    public void Add(K1 key1, K2 key2, T value)
    {
        if (ContainsKey(key1))
        {
            if (base[key1].ContainsKey(key2))
                throw new Exception("Couple " + key1 + "/" + key2 + " already exists!");
            base[key1].Add(key2, value);
        }
        else
            Add(key1, new Dictionary<K2, T>() { { key2, value } });
    }

    public bool ContainsKey(K1 key1, K2 key2) => ContainsKey(key1) && base[key1].ContainsKey(key2);
}
Related