Generic Dictionary and the KeyNotFoundException. Examining speed of Nullable types versus integral types...
Brad wanted to know whether or not we minded the exception if the key value didn't exist on his original post on the matter The change from Hashtable to Dictionary . I figured the reason people would use the exception would be for using integral value types in their dictionaries. The workaround is to use a Nullable type over the top of the integral type, and then you can always have the ability to return a null value. You'd think the integral type dictionary would be slightly faster (it will at least take slightly less memory), and that Nullable would be a little slower. I've found that for some reasons Nullable is actually a little bit faster. Not sure why, but it is about 50% faster over a simple loop. Maybe it will be smart to use Nullable types instead of integral types (full source at the end).
Some people on the original thread also said that a Try* method would be sufficient. Well, there is a Try* method. It is called TryGetValue and returns a bool for found versus not and an out parameter of the type of your value parameter. Here is a short sample usage of the function, with a full sample at the end.
string[] keysToTest = new string[] { (keyBase + 50).ToString(), (keyBase + keys + 1).ToString() };
foreach(string key in keysToTest) {
int intValue = 0;
if ( intDictionary.TryGetValue(key, out intValue) ) {
Console.WriteLine("Found: {0}", intValue);
} else {
Console.WriteLine("Not Found: {0}", key);
}
}
I think they've done their job already. Why change the exception logic, when you already have to do extra work to create the generic dictionary, you might as well do a bit of work to switch over to using the TryGetValue method. One problem is that it isn't obvious (no code breaks) that you are using the indexer and so you might forget to change a piece of legacy code to use TryGetValue where needed. My original comments are on this matter are here Darnit, they break the Hashtable (err Dictionary) then ask us how we think they should fix it...
using System;
using System.Collections.Generic;
public class NullableVsValueType {
const int keyBase = 10000000;
const int keys = 1000000;
private static void Main(string[] args) {
string[] stringsForKeys = new string[keys];
int counter = 0;
Dictionary<string, int> intDictionary = new Dictionary<string, int>();
Dictionary<string, Nullable<int>> nullableDictionary = new Dictionary<string, Nullable<int>>();
DateTime start, end;
Console.WriteLine("Start Pre-Processing Strings");
start = DateTime.Now;
for(int i = keyBase; i < (keyBase + keys); i++) {
stringsForKeys[i - keyBase] = i.ToString();
}
end = DateTime.Now;
Console.WriteLine("Pre-Processing Strings Complete: {0}", end - start);
Console.WriteLine();
Console.WriteLine("Start Int Dictionary");
start = DateTime.Now; counter = 0;
for(int i = 0; i < keys; i++) {
intDictionary[stringsForKeys[i]] = i;
// counter += intDictionary[stringsForKeys[i]];
}
end = DateTime.Now;
Console.WriteLine("Int Dictionary Complete: {0}, {1}", end - start, counter);
Console.WriteLine();
Console.WriteLine("Start Nullable<Int> Dictionary");
start = DateTime.Now; counter = 0;
for(int i = 0; i < keys; i++) {
nullableDictionary[stringsForKeys[i]] = i;
// nullableDictionary[stringsForKeys[i]] = new Nullable<int>(i);
// counter += nullableDictionary[stringsForKeys[i]].Value;
}
end = DateTime.Now;
Console.WriteLine("Nullable<Int> Dictionary Complete: {0}, {1}", end - start, counter);
Console.WriteLine();
Console.WriteLine("Using TryGetValue");
string[] keysToTest = new string[] { (keyBase + 50).ToString(), (keyBase + keys + 1).ToString() };
foreach(string key in keysToTest) {
int intValue = 0;
if ( intDictionary.TryGetValue(key, out intValue) ) {
Console.WriteLine("Found: {0}", intValue);
} else {
Console.WriteLine("Not Found: {0}", key);
}
}
}
}
[Edit: Adding more complete test code for timing both string keying as above, and integer keying. One user's comments point that the keying is causing changes on his machine that make the integral value dictionary perform better than the Nullable type dictionary. I can't repro his case, so I'm attempting to standardize the test case so there aren't any discrepancies. You can get the latest test code here Nullable Versus Integral Data Types (C# 2.0 Test Code)]