You may have noticed inconsistencies in the .NET Framework BCL (Base Class Library) since 1.0 with regards to the behavior of Get/Indexer methods when the requested value does not exist. For instance, the indexer on the 1.1 System.Collections.Hashtable class returns null/Nothing if the requested value does not exist. However, the indexer on System.Data.DataRow, which you often use to retrieve column values from a DataTable, throws an exception if the requested column does not exist. In yet another twist, System.Reflection.Assembly’s GetType() method has two overloads that accept boolean parameters indicating whether or not to throw an exception if the type is not found.
These differences are the result of various Microsoft BCL development teams and individuals with differing opinions on “throw on missing” vs. “don’t throw on missing.” This has been a long-running debate inside Microsoft, and it’s something that you should also consider carefully when designing your classes. While working at Microsoft several years ago, I had the opportunity to monitor some of these debates on several of the internal technical discussion groups (which carry massive amounts of emails).
The first question you might ask is: in an average execution flow, would I expect to encounter missing values, or does that represent an exceptional circumstance? In a general purpose class when you don’t know how it will be used, there’s no way to answer this question. This, of course, is the problem that Microsoft faces.
Fortunately, it appears that a consensus has finally formed within the BCL team. I believe that it is a reasonable compromise between the two approaches and introduces a pattern that is easily recognizable by developers while maximizing flexibility. This pattern includes a pair of methods: Try<Operation>() and <Operation>(). For example, TryGet() and Get(). It’s a very simple idea — the Try version of the method returns a boolean indicating whether the operation will succeed or fail, and the non-Try version throws an exception if the operation fails. The biggest downside of this approach, in my opinion, comes down to the additional clutter it brings to a class – namely, doubling the method count for certain types of methods.
It’s important to be aware of this pattern, because you will find it in many places in the .NET Framework 2.0 BCL. The new classes in System.Collections.Generic might be the ones you encounter most often, but you will also find this pattern on the common types, like Int32.TryParse() and Int32.Parse(). It’s interesting to note that this pattern existed in 1.0/1.1, but only on the Double class!
One potential gotcha, which represents a big difference between 1.0/1.1 and 2.0, is that the indexer on 2.0’s Dictionary (hashtable) throws KeyNotFoundException when the key is missing, and in 1.1’s Hashtable, the indexer does not throw an exception in that case. Be careful of this difference when you’re used to 1.1 and working in 2.0, or vice versa.
When you’re designing classes, I encourage you to take advantage of this pattern (where appropriate) to make the behavior of your classes more predictable and consistent with the rest of the Framework.