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>();
dict.Add(1, "A");
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];
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:
class Container {
public string S { get; set; }
public decimal D { get; set; }
}
Dictionary<int, Container> dict;
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; }
}
}
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>();
trict.Add(1, "A", 1.1);
trict[2] = new DualObjectContainer<string,double>("B", 2.2);
string s;
double d;
trict.Get(2, out s, out d);
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.
| You must Sign In to use this message board. |
|
|
 |
|
 |
in 4.0, I guess its just Dictionary<tk,tuple><t1,t2>>
you already metioned Dictionary<tk,keyvaluepair><t1,t2>>
why not just:
public class Trictionary<tk,t1,t2> : Dictionary<tk,tuple><t1,t2>> {}
and call it done?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
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?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@Jay:
Thanks for the comment. Looking back, I think you're probably right, that my need for this would probably be solved with that strategy. One specific thing: I believe the Tuple type is a value type, and I wanted to use reference types - so I'd probably still use my DualObjectContainer or other named class for the value.
One difference between these two strategies - simple inheritance means that the Keys and Values properties are objects of type KeyCollection<TKey, Tuple<TValue1, TValue2>> instead of KeyCollection<TKey, TValue1, TValue2> (same for ValueCollection). Probably not a difference in real life, but just a note that there is a difference.
I'd just need to add a few methods - Add(TKey, TValue1, TValue2), Get(TKey, out TValue1, out TValue2), TryGet(TKey, out TValue1, out TValue2) - but after that, I think I'd have everything I needed.
I suppose this whole project was unnecessary - but I will say that it was fun to do. I spent a good amount of time digging into the inner workings of the Dictionary, which was a worthwhile effort. So I'm going to say that it wasn't a complete waste of time.
Thanks for the insight - I guess this is why it's better to bounce an idea off a few people before getting started...
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I thought this was a very interesting class and I just gave it a 5. In fact I got so interested that I went ahead and wrote my own version of the Trictionary here :
A generic Trictionary class[^]
So thanks for giving me an hour's worth of fun coding and writing
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@Nishant:
Thanks for the comment. I did check out your entry, and it looks pretty interesting. I'm glad my article provided some inspiration for you.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Just a thought I had. Is it possible to implement a new collection using
Trictionary<tkey,tvalue1,tvalue2>
And Implement it with a tree with two value per node? Just a thought I had....
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@salinger:
Thanks for the comment. Not sure exactly what you're referring to...I suppose the Trictionary can be thought of as a tree, where the keys represent the first node, and the two values represent the child nodes:
KEY1 KEY2 / \ / \ Val1 Val2 Val1 Val2
But other than visually, I don't see what benefit this is. If you'd like, please expand on what you're thinking. I'd be happy to hear ways to improve the project.
In any case, you are welcome to take this code and expand it to meet your needs, if this is something you could use.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Follow-up: That little diagram thing apparently doesn't work very well inside a message...It was supposed to show KEY1 and KEY2 as parent elements, and each of them pointing two their own child elements.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
A Dictionary can be implemented using a Binary Search tree (I really mean, build it from the ground up) so The tree should look like this ROOT(First added element Contains key and two values) / \ First Child Second Child
I'd love to take this challenge and program this.... Should be fun (A little change from coding asp.net applications)
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I really haven't spent a lot of time working with binary search trees, so I'm not sure I know where you're going. If you end up publishing or blogging anything about your end result, I'd be interested in reading it, especially if the Trictionary technique helped in any way. Have fun with your project.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
You could just do the following: Dictionary<string, KeyValuePair<string, int>> While I am not sure how much use this would get in the real world, I am not unimpressed with the effort. There is nothing wrong with having more than one way to do something, and overall it probably made the author learn more about .NET and programming in general, so it is definitely not a bad thing.
I wrote something similar, a multi-key dictionary, instead of a multi-value dictionary. http://www.codeproject.com/KB/recipes/multikey-dictionary.aspx[^]
While it might not be much use to others in whatever they are doing, it works great for me.
Kudos on the article
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@Aron:
Thanks for the comment. I took a look at the MultiKeyDictionary, and that seems like kind of a cool idea as well. Like mine, I haven't run across many scenarios where I'd use it, but it's definitely an interesting technique.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
is to offset the d-bags who voted 1 because they didn't understand it. Joe, regardless of how useful the end product may or may not be, I wish more CodeProject articles were done as well as this one. You included lots of code, explained everything very well, and included some good coding practices. Keep up the good work.
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
@BigTuna:
Thanks for your comment. I appreciate you taking the time to read and understand what I was trying to accomplish.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
@binarycheese:
Thanks for the comment. I actually did research PowerCollections prior to writing this, and was surprised they didn't have what I was looking for. MultiDictionary does expose multiple values per key, but those values are all of the same type. For the Trictionary, Value1 and Value2 are different types, defined by the generic.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
It's funny, no one seems to have read:
you'd need to define a container each type you need this functionality
Yes, the other comments are correct, as you pointed out in the article, that you could write a container.
But, if you need to do this a lot, creating all these little containers for a two-value key becomes a pain, and what you've done is to correctly identify a pattern that, with your class, reduces the amount of work if infact this is something you're doing a lot of in some application.
One thing I'd question though is, what else are you using these value pairs for? Would it actually make more sense for other parts of the application to use a container as well? I've encountered the need for this a lot, but pretty much every time, I can see the benefit of writing the container, because it's useful for other parts of the code as well.
Anyways, I think you deserve more than a 1 vote, because you're obviously thinking about patterns and being more efficient at coding.
Marc
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@Marc:
Thanks for your comment. Here's an example: suppose I have a database with values as follows - not a great real-world example, but it's demonstrative: STUDENT table has STUDENT_ID and information about the student. SCHEDULE table has one record for each class that each student is taking, so more than one record per student.
I have a list of thousands of individual students ids, and want to output the student name and the number of classes that each of them is taking. I can either: A) Query the database one student at a time. B) Assuming I have plenty of memory, query the database once, like SELECT ST.STUDENT_ID, ST.NAME, COUNT(SC.SCHEDULE_ID) FROM STUDENT ST JOIN SCHEDULE SC on ST.STUDENT_ID = SC.STUDENT_ID. Create a Trictionary with StudentID as the key, Name as value1, and the count as value2. In this scenario, it's kind of silly to create a custom container, since it's only used for this one purpose.
I'd absolutely say that if a container makes sense in more than one place, or if you already have one defined, or if you're only doing this once in your project and don't mind building the container, than definitely use that. But if you ever find yourself in a situation where a container just makes things more complex, or if you're doing this over and over again, then this might just be a solution for you.
Thanks again for taking the time to think about this before just immediately writing it off.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
@thehawk:
Thanks for the comment. Of course you can do it that way. But if you don't want to define custom types, then this is your alternative. If you're doing this many times, with simple types, then it might not be worth your while to keep building more and more types.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
 |
Can't really see the need for it. Can be accomplished using the existing Dictionary and a custom type as value.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@Michael:
Thanks for the comment. Of course you can do it that way. But if you don't want to define custom types, then this is your alternative. If you're doing this many times, with simple types, then it might not be worth your while to keep building more and more types.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Nice work but why not use a struct or a class object as the data type thus allowing for N number of values to be stored.
using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms;
namespace DicStructTest { public partial class MainForm : Form { public struct Person { int age; string name; public int Age { get { return age; } set { age = value; } } public string Name { get { return name; } set { name = value; } } } Dictionary personDic = new Dictionary(); public MainForm() { InitializeComponent(); Person member = new Person(); member.Age=28; member.Name="Stephen"; personDic.Add(1,member); member.Age=30; member.Name="Roy"; personDic.Add(2,member); member.Age=33; member.Name="Burrows"; personDic.Add(3,member); } void Button1Click(object sender, EventArgs e) { Person activePerson = personDic[2]; MessageBox.Show( "Active Member Name Is: " + activePerson.Name + " \nThey Are " + activePerson.Age.ToString() + " Years Old"); } } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
@bombdrop:
Thanks for the comment. Of course, anytime you have a type already defined for the value, you'd be better off using that instead of the Trictionary. However, if you don't already have a type defined, and you don't want to create one, then Trictionary saves you the trouble.
It may not make sense to use it if you only have to do it once. But if you are doing this multiple times, it may be better to use the Trictionary rather than continuing to define new types over and over again, based on the situation.
Joe Enos joe@jtenos.com
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
|