Click here to Skip to main content
Click here to Skip to main content

Trictionary - Multi-Value Dictionary

, 11 Mar 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
A wrapper around the Dictionary that returns two values instead of one.

Introduction

The Dictionary<TKey, TValue> object is a very powerful collection in the .NET framework. It's a type-safe implementation of the Hashtable that was prevalent in .NET 1.1. Recently, I needed something extra - instead of returning a single value for a given key, I needed two values. I found the solution - instead of a Dictionary, I created a Trictionary.

Background

A Dictionary is pretty straight-forward to use. For this example, we have a Dictionary with a key of type int and a value of type string. There are two ways to add keys and values:

Dictionary<int, string> dict = new Dictionary<int, string>();

// Option 1:
dict.Add(1, "A");

// Option 2:
dict[2] = "B";

The two options are slightly different, but generally produce the same results. Option 1 will throw an exception if the key already exists in the dictionary. Option 2 will not throw an exception in this scenario, but will simply overwrite the value found.

Retrieving values is pretty straightforward as well:

string s = dict[1]; // returns "A"

If the Dictionary doesn't contain the key 1, then a KeyNotFoundException will be thrown. To avoid this, you can call the ContainsKey method first to ensure that the key exists.

Now, suppose you need the dictionary to return two objects instead of just one. For example, suppose you want your key to be an int, and you want to return a string and decimal. There are two simple ways of accomplishing this:

// Option 1: Create a container class
class Container {
    public string S { get; set; }
    public decimal D { get; set; }
}
Dictionary<int, Container> dict;

// Option 2: Use a KeyValuePair<TKey, TValue> as the value
Dictionary<int, KeyValuePair<string, decimal>> dict;

With option 1, you'd need to define a container for each type you need this functionality in. With option 2, the code tends to get kind of messy. I wanted a better solution.

Using the code

I decided to build the Trictionary class as a wrapper around the Dictionary, to utilize the features that make the Dictionary so powerful. The value of the dictionary is actually a type called DualObjectContainer<TValue1, TValue2>. It's a pretty simple class, with just two values. I overrode ToString() to show the values, and implemented IEquatable so two containers can be compared.

public class DualObjectContainer<TValue1, TValue2> 
    : IEquatable<DualObjectContainer<TValue1, TValue2>>
{
    public DualObjectContainer(TValue1 value1, TValue2 value2) {
        Value1 = value1;
        Value2 = value2;
    }

    public TValue1 Value1 { get; set; }
    public TValue2 Value2 { get; set; }

    public bool Equals(DualObjectContainer<TValue1, TValue2> other) {
        return ((EqualityComparer<TValue1>.Default.Equals(Value1, other.Value1)) 
            && (EqualityComparer<TValue2>.Default.Equals(Value2, other.Value2)));
    }

    public override string ToString() {
        return string.Format("[{0}, {1}]", Value1, Value2);
    }
}

The Trictionary class contains a private Dictionary, and many of the methods and properties are simply wrappers around this Dictionary.

public class Trictionary<TKey, TValue1, TValue2> 
    : ITrictionarylt;TKey, TValue1, TValue2>, ITrictionary, 
      ISerializable, IDeserializationCallback
{
    private readonly Dictionary<TKey, DualObjectContainer<TValue1, TValue2>> _dictionary;

    public void Clear()
    {
        _dictionary.Clear();
    }

    public bool ContainsKey(TKey key)
    {
        return _dictionary.ContainsKey(key);
    }

    public int Count
    {
        get { return _dictionary.Count; }
    }
    // etc.
}

In order to add and retrieve values from the Trictionary, I had to do something a little different. Just like the Dictionary, there are two ways to add keys/values. First, you can use the Add method, passing in the key and both values. You can also use the indexer, but to do that, you need to pass in a DualObjectContainer with the two values. There are also two ways to retrieve values - the Get method and the indexer.

public void Add(TKey key, TValue1 value1, TValue2 value2) {
       _dictionary.Add(key, new DualObjectContainer<TValue1, TValue2>(value1, value2));
}

public void Get(TKey key, out TValue1 value1, out TValue2 value2) {
    DualObjectContainer<TValue1, TValue2> container = _dictionary[key];
    value1 = container.Value1;
    value2 = container.Value2;
}

public DualObjectContainer<TValue1, TValue2> this[TKey key] {
    get { return _dictionary[key]; }
    set { _dictionary[key] = value; }
}

Here is how you call the code:

Trictionary<int, string, double> trict = new Trictionary<int, string, double>();

// ADDING:
// Option 1:
trict.Add(1, "A", 1.1);

// Option 2:
trict[2] = new DualObjectContainer<string,double>("B", 2.2);

// RETRIEVING:
// Option 1:
string s;
double d;
trict.Get(2, out s, out d);

// Option 2:
DualObjectContainer<string, double> container = trict[1];

The Trictionary is more than just a simple wrapper. In order to expose the same features as a Dictionary, I had to define the ITrictionary interfaces, both generic and non-generic, which required a significant amount of additional code. This code really isn't important to this presentation, but it is there to make the Trictionary more useful. For example, you can access the keys and values, and all of the different constructors that are available in the Dictionary.

Conclusion

This class can provide a useful benefit in some circumstances. One scenario where I found it useful was with a data object: the ID was the key, the object itself was Value1, and Value2 represented additional information about the object. I hope you find a situation where the download will be of some value as well. Please feel free to leave a comment letting me know if you've found this technique helpful in your projects.

The attached ZIP file contains a Visual Studio 2008 solution, with projects designed to compile under the .NET 3.5 Framework. It contains a Trictionary project, which contains all of the necessary code files, and a Trictionary.TestFixture project, which contains various Unit Tests designed to run in NUnit.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Joe Enos
Software Developer Desert Schools Federal Credit Union
United States United States
Joe Enos is a software engineer in Phoenix, Arizona, with 8 years of .NET experience. Currently working as a software developer on a medium-sized team in Phoenix.
Follow on   Twitter

Comments and Discussions

 
Generalin 4.0, I guess its just Dictionary&lt;tk,tuple&gt;&lt;t1,t2&gt;&gt; PinmemberJay R. Wren17-Mar-09 9:14 
GeneralRe: in 4.0, I guess its just Dictionary&lt;tk,tuple&gt;&lt;t1,t2&gt;&gt; PinmemberJay R. Wren17-Mar-09 9:27 
WOW... something mangled my text.
 
in 4.0, I guess its just Dictionary < TK, Tuple < T1, T2 > >
 
you already metioned Dictionary < TK, Tuple < T1, T2 > >
 
why not just:
 
public class Trictionary < TK, T1, T2 > : Dictionary < TK, Tuple < T1, T2 > > {}
 
and call it done?
GeneralRe: in 4.0, I guess its just Dictionary&lt;tk,tuple&gt;&lt;t1,t2&gt;&gt; PinmemberJoe Enos17-Mar-09 10:58 
GeneralMy vote of 5 PinsitebuilderNishant Sivakumar11-Mar-09 17:16 
GeneralRe: My vote of 5 PinmemberJoe Enos11-Mar-09 17:58 
GeneralJust An Idea Pinmembersalinger11-Mar-09 11:57 
GeneralRe: Just An Idea PinmemberJoe Enos11-Mar-09 12:18 
GeneralRe: Just An Idea PinmemberJoe Enos11-Mar-09 12:25 
GeneralRe: Just An Idea Pinmembersalinger11-Mar-09 12:38 
GeneralRe: Just An Idea PinmemberJoe Enos11-Mar-09 12:51 
GeneralAnother way to do this PinmemberAron Weiler11-Mar-09 10:23 
GeneralRe: Another way to do this PinmemberJoe Enos11-Mar-09 10:56 
GeneralMy vote of 5 PinmemberBigTuna11-Mar-09 5:51 
GeneralRe: My vote of 5 PinmemberJoe Enos11-Mar-09 5:58 
GeneralA better implmentation is MultiDictionary Pinmemberbinarycheese11-Mar-09 4:17 
GeneralRe: A better implmentation is MultiDictionary PinmemberJoe Enos11-Mar-09 5:13 
GeneralI say "good job" PinprotectorMarc Clifton11-Mar-09 4:17 
GeneralRe: I say "good job" PinmemberJoe Enos11-Mar-09 5:32 
GeneralMy vote of 1 Pinmemberthehawk_220011-Mar-09 4:07 
GeneralRe: My vote of 1 PinmemberJoe Enos11-Mar-09 5:19 
GeneralMy vote of 1 PinmemberMichael B. Hansen11-Mar-09 3:18 
GeneralRe: My vote of 1 PinmemberJoe Enos11-Mar-09 5:17 
QuestionStruct? Pinmemberbombdrop11-Mar-09 3:06 
AnswerRe: Struct? PinmemberJoe Enos11-Mar-09 5:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141216.1 | Last Updated 11 Mar 2009
Article Copyright 2009 by Joe Enos
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid