devxlogo

Generic Dictionaries: Hidden Treasure of System.Collections.Generic

Generic Dictionaries: Hidden Treasure of System.Collections.Generic

he Dictionary class in .NET 2.0’s System.Colllections.Generic namespace provides the ability to create a strongly-typed dictionary where both the Keys and Values collections can accept any object-type. This capability enables novel solutions to typical programming problems?for example in coordinating the current selection where multiple controls represent the same data, such as synchronizing selection between a ListView and TreeView.

This article presents a few simple and practical examples of using generic dictionaries. It concludes with one “far-out” example (that could be used to implement late binding) and that I hope will get you excited about the power of these dictionaries and trigger creative ideas in your own work.

When you first encounter .NET 2.0’s new generic facilities, you may assume, as I did, that these are some very neat tools for easily and flexibly creating strongly typed collections; for avoiding casting, boxing, and unboxing in collections or lists; and for ensuring type-safety in your code so that errors are flagged at compile time, rather than erupting at run-time.

What You Need
You need NET 2.0 to run the examples. I created them with Visual Studio 2005, but they should be easily replicable in any IDE that supports .NET 2.0, and has access to the standard TreeView and ListView controls.

The List and Collection objects in the Systems.Collections.Generic namespace give you all that functionality, and amplify your programming power by letting you create custom types using the generic type designator . You can use the Comparer and Enumerator facilities to make custom generic types both enumerable and comparable.

More advanced structures such as Queue and Stack give you a head start on modeling typical data structures, and SortedDictionary gives you the tools for a Dictionary type with an inherent sort order.

What surprised and delighted me when I finally got around to looking at the capabilities of the generic Dictionary class was to find that it was much more flexible than I thought it would be. Having once programmed extensively in PostScript, which had similarly flexible dictionaries, this flexibility made me think of late-binding scenarios where, by either changing a value associated with a key, or changing the dictionary in which you looked up the key, you could manipulate semantic contexts. In fact a “dictionary stack” is, internally, how PostScript manipulates name-look-up and syntactic scoping; let me know if you find the party where you can bring that into the conversation!

Building a Generic Dictionary
To start, download and unzip the sample code, and open the GD_Example_1 project.

Open the code-behind Form1.cs file and check to make sure you see the declaration:

   using System.Collections.Generic;

Either in your head, or looking at the code, visualize/observe two controls on the form: a TreeView and a ListView. Each contains a set of 10 items. Now, add a generic dictionary declaration:

   private Dictionary nodeDictionary =       new Dictionary();

You now have a dictionary named nodeDictionary in which the Keys collection contains a set of TreeNode objects and the Values collection holds ListViewItems.

What (I hope) just came into your mind was all the times you may have manipulated the Tag property of control “inner-collection-objects” such as TreeNodes and ListViewItems so that when a user selected a TreeNode you could find and select a corresponding ListViewItem in the ListView?and vice-versa. You also probably remember that every time you accessed the Tag property, you had to cast the value to the proper object type (and casting, as we all know, is very expensive, and contributes to global warming). In other words, to get the corresponding ListViewItem stored in the Tag property of a TreeNode you had to write:

You probably remember using the Tag property to coordinate control selection, but until generics, you had to cast the value to the proper object type.
   ListViewItem currentListViewItem = (ListViewItem)       currentTreeNode.Tag;

Now you can use the nodeDictionary to look up a ListViewItem using a TreeNode as the key with no casting required:

   ListViewItem currentListViewItem =        nodeDictionary[currentTreeNode];

See the sidebar Getting Under the Hood of Generic Dictionaries for more information on the way generic dictionaries work.Using Two Dictionaries for Bidirectional Lookup
“But wait!” (I hope you’re already saying) “Okay, you’ve got the ability to look up a ListViewItem using a key that’s a TreeNode, but what about when you get a hit on the ListViewItem, and you want to know which corresponding TreeNode should be selected?”

Well, one answer would be to have two generic dictionaries. For example, you could add a second one of the form:

   private Dictionary LVIDictionary =       new Dictionary();

Now you have a way to get the corresponding TreeNode from a selected ListViewItem using the LVIDictionary instance.

Loading the Generic Dictionaries
Now the question becomes: How can you get these dictionaries properly loaded with the TreeNode and ListViewItem key/value pairs, and link them to selection events at run-time?

You might immediately think of creating custom classes that inherit from TreeView and ListView with overridden methods, but because you can’t inherit from important items such as TreeNodeCollection, you’d have to begin casting to use custom Nodes or ListViewItems in the methods you didn’t override.

I strongly encourage you to forget that route unless you are a rodeo champion of such fancy overriding?with a library of already-well-tested code to draw from. Alternatively, you could load the dictionaries from DataSets or XML files, from which you would construct the TreeView Nodes and ListView ListViewItems dynamically. Or perhaps with some applications, you might want to give your users the ability to create/edit/delete entries on the fly.

This article has sufficient time and space to examine only a simple example of the mechanics of creating new entries. Look at the code for the Click event-handler of the makeNewItemBtn below:

   private int nodeCount = 0;         private void makeNewItemBtn_Click(object sender, EventArgs e)   {      currentNode = new TreeNode();      currentNode.Text = "Item " + nodeCount.ToString();      tv1.Nodes.Add(currentNode);            currentLVI = new ListViewItem();      currentLVI.Text = currentNode.Text;      lv1.Items.Add(currentLVI);            nodeDictionary.Add(currentNode, currentLVI);      LVIDictionary.Add(currentLVI, currentNode);                // foreach (TreeNode theNode in nodeDictionary.Keys)      // {      //    Console.WriteLine(theNode.Text);      // }            nodeCount++;   }

The commented-out code in the preceding example shows foreach iteration over the current Keys collection in the nodeDictionary, writing the Text property value of each key to the console. Using such code, you can easily synchronize the current selection in ListView and TreeView by defining an AfterSelect event-handler for the TreeView, and an ItemActivate handler for the ListView:

   private void tv1_AfterSelect(object sender, TreeViewEventArgs e)   {      nodeDictionary[tv1.SelectedNode].Selected = true;   }         // note : if you use SelectedIndexChanged instead   // you'll have to handle the case of SelectedItems being    // set to null when the ListView loses focus ...      // note : ItemActivate seems a little "sluggish" compared to    // SelectedIndexChanged at run-time !   private void lv1_ItemActivate(object sender, EventArgs e)   {      tv1.SelectedNode = LVIDictionary[lv1.SelectedItems[0]];   }

While this example is very simple, because it adds only one pair of TreeNode/ListViewItem at a time, you can easily envision more complex versions that, might, for example, synchronize?ListViewItems with TreeNodes at different levels of the TreeView structure, or that base synchronization on some ordering principle other than linear sequence.

Introducing Dynamic Code Dictionaries
Let’s take a look at a more radical use of generic dictionaries.

Suppose that, instead of the generic dictionary you’ve seen so far, you were to create a generic dictionary of the form:

   Dictionary ControlDictionary

In the ControlDictionary type shown above, each key is an object reference to a Control type, and each value is executable C# code. Whoa! Did you just say, “executable?” Ah, yes, I did.

At this point, you’re heading “down the rabbit-hole” into the interesting world of late-binding, because you could alter the value associated with a key at any time, plugging-in a new piece of executable code.

The code isn’t directly executable, though; you execute it “indirectly” after retrieving it from the ControlDictionary using a control in an application as a key. At this point, you’re heading “down the rabbit-hole” into the interesting world of late-binding, because you could alter the value associated with a key at any time, plugging-in a new piece of executable code.

To accomplish this you set the value associated with any given key to a Delegate instance bound to a MethodBody (executable code). You can then execute the bound method using DynamicInvoke(). Whether ’tis better to use Invoke rather than DynamicInvoke is left for you to investigate/debate/flame.

While you’re “downstairs with Alice” in late-binding land, it’s worth showing off how to use anonymous methods bound to one (and only one) Delegate declaration. The code below shows how to do that:

   private delegate void theMethod();      private Dictionary controlMethodDictionary;         private void testForm_Load(object sender, EventArgs e)   {      controlMethodDictionary = new Dictionary();      controlMethodDictionary.Add(tv1,          delegate          {            nodeDictionary[tv1.SelectedNode].Selected = true;         }      );               controlMethodDictionary.Add(lv1,          delegate          {            tv1.SelectedNode = LVIDictionary[lv1.SelectedItems[0]];         }      );   }

The example reuses the TreeView and ListView controls and generic dictionary classes from the previous example, but this time you have a dictionary object containing a single key that’s either a ListView or TreeView control on the form. The value associated with that key is a delegate to a procedure that keeps the TreeView and ListView selections synchronized.

The “surprise” may be that you can get away with using one delegate named theMethod as the type identifier in the definition of a generic dictionary.

You can see that the above code shows how to use anonymous methods bound to nameless delegates. Surprised? The “surprise” may be that you can get away with using one delegate named theMethod as the type identifier in the definition of a generic dictionary, but just remind yourself, as I had to, that a delegate is a first-class type!

Now all you have to do is invoke those methods in the AfterSelect event for the TreeView and the ItemActivate for the ListView, which is so easy. Note that using DynamicInvoke does not require a reference to the Reflection library in your code header.

   private void tv1_AfterSelect(object sender, TreeViewEventArgs e)   {      controlMethodDictionary[tv1].DynamicInvoke();   }      private void lv1_ItemActivate(object sender, EventArgs e)   {      controlMethodDictionary[lv1].DynamicInvoke();   }

This article has only scratched (tickled?) the surface of one aspect of the functionality in generics in .NET 2.0. Consider the possibilities opened up by your freedom to derive/extend from custom or built-in generic types, such as adding virtual or abstract methods to your extended/derived types and using generic interfaces and delegates.

But here’s a reality check: Should you use late-binding? Should you stuff anonymous methods into values in a generic dictionary, and then execute them later? I’m not going to attempt to answer that question. My goal here is to broaden your awareness of what’s possible with generic dictionaries, so you can add them to your arsenal of Ninja tools. When and where you use late-binding, in what context, and what specific techniques you use (or don’t use) with generic dictionaries is between you and Donald Knuth, thanks.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist