I wrote the following extension method to get an element from a dictionary, or null if the key isn’t present:
public static TValue ItemOrNull<TKey, TValue>(this IDictionary<TKey, TValue> dict, TKey key)
{
try
{
return dict[key];
}
catch (KeyNotFoundException ex)
{
return default(TValue);
}
}
I noticed that my program was running very slowly so I tracked down the problem to this extension method using a high precision timer class. I get similar results ~100 times in a row:
DebugTimer.ResetTimer();
dict1.ItemOrNull(key);
dict2.ItemOrNull(key);
DebugTimer.StopTimer();
takes about 110,000,000 ticks (more than 0.03 seconds on my processor). While the more verbose version:
DebugTimer.ResetTimer();
if (dict1.ContainsKey(key))
y = dict1[key];
if (dict2.ContainsKey(key))
z = dict2[key];
DebugTimer.StopTimer();
MessageBox.Show(y.ToString(), z.ToString()) // so the compiler doesn't optimize away y and z
takes about 6,000 ticks (less than 0.000002 seconds).
Is it clear to anyone why my extension method version is taking more than 4 orders of magnitude longer than the verbose version?
Don’t catch exceptions for flow control – it’s not just that it can cause performance problems (although they’re not nearly as bad as most people think – as Eric says, most people’s fear of exception performance comes from using the debugger). It’s more about the logical nature of exceptions. Personally I wouldn’t use exceptions in this way even if they were essentially free.
Has anything bad happened here? Is it in any way invalid for the user to be asking for this key’s value? Absolutely not – the whole purpose of the method is to provide a default. It’s not exceptional for the key to be absent – so you should look for a way of working without exceptions.
Now
Dictionary<,>already has a method to let you fetch a value if the key exists and let you know whether or not it was found:TryGetValue.Extension methods are just compiled into regular static method calls and thus have no performance difference to them.
You might also want to add an overload allowing the user to express the default value to return if the key wasn’t found:
I’ve adjusted the name of your method to match
TryGetValue, by the way. Obviously you don’t have to follow that – it’s just a suggestion.