C#/.NET Little Wonders: Using ‘default’ to Get Default Values
- by James Michael Hare
Once again, in this series of posts I look at the parts of the .NET Framework that may seem trivial, but can help improve your code by making it easier to write and maintain. The index of all my past little wonders posts can be found here. Today’s little wonder is another of those small items that can help a lot in certain situations, especially when writing generics. In particular, it is useful in determining what the default value of a given type would be. The Problem: what’s the default value for a generic type? There comes a time when you’re writing generic code where you may want to set an item of a given generic type. Seems simple enough, right? We’ll let’s see! Let’s say we want to query a Dictionary<TKey, TValue> for a given key and get back the value, but if the key doesn’t exist, we’d like a default value instead of throwing an exception. So, for example, we might have a the following dictionary defined: 1: var lookup = new Dictionary<int, string>
2: {
3: { 1, "Apple" },
4: { 2, "Orange" },
5: { 3, "Banana" },
6: { 4, "Pear" },
7: { 9, "Peach" }
8: };
And using those definitions, perhaps we want to do something like this:
1: // assume a default
2: string value = "Unknown";
3:
4: // if the item exists in dictionary, get its value
5: if (lookup.ContainsKey(5))
6: {
7: value = lookup[5];
8: }
But that’s inefficient, because then we’re double-hashing (once for ContainsKey() and once for the indexer). Well, to avoid the double-hashing, we could use TryGetValue() instead:
1: string value;
2:
3: // if key exists, value will be put in value, if not default it
4: if (!lookup.TryGetValue(5, out value))
5: {
6: value = "Unknown";
7: }
But the “flow” of using of TryGetValue() can get clunky at times when you just want to assign either the value or a default to a variable. Essentially it’s 3-ish lines (depending on formatting) for 1 assignment.
So perhaps instead we’d like to write an extension method to support a cleaner interface that will return a default if the item isn’t found:
1: public static class DictionaryExtensions
2: {
3: public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dict,
4: TKey key, TValue defaultIfNotFound)
5: {
6: TValue value;
7:
8: // value will be the result or the default for TValue
9: if (!dict.TryGetValue(key, out value))
10: {
11: value = defaultIfNotFound;
12: }
13:
14: return value;
15: }
16: }
17:
So this creates an extension method on Dictionary<TKey, TValue> that will attempt to get a value using the given key, and will return the defaultIfNotFound as a stand-in if the key does not exist.
This code compiles, fine, but what if we would like to go one step further and allow them to specify a default if not found, or accept the default for the type? Obviously, we could overload the method to take the default or not, but that would be duplicated code and a bit heavy for just specifying a default. It seems reasonable that we could set the not found value to be either the default for the type, or the specified value.
So what if we defaulted the type to null?
1: public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dict,
2: TKey key, TValue defaultIfNotFound = null) // ...
No, this won’t work, because only reference types (and Nullable<T> wrapped types due to syntactical sugar) can be assigned to null.
So what about a calling parameterless constructor?
1: public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dict,
2: TKey key, TValue defaultIfNotFound = new TValue()) // ...
No, this won’t work either for several reasons. First, we’d expect a reference type to return null, not an “empty” instance. Secondly, not all reference types have a parameter-less constructor (string for example does not). And finally, a constructor cannot be determined at compile-time, while default values can.
The Solution: default(T) – returns the default value for type T
Many of us know the default keyword for its uses in switch statements as the default case. But it has another use as well: it can return us the default value for a given type. And since it generates the same defaults that default field initialization uses, it can be determined at compile-time as well.
For example:
1: var x = default(int); // x is 0
2:
3: var y = default(bool); // y is false
4:
5: var z = default(string); // z is null
6:
7: var t = default(TimeSpan); // t is a TimeSpan with Ticks == 0
8:
9: var n = default(int?); // n is a Nullable<int> with HasValue == false
Notice that for numeric types the default is 0, and for reference types the default is null. In addition, for struct types, the value is a default-constructed struct – which simply means a struct where every field has their default value (hence 0 Ticks for TimeSpan, etc.).
So using this, we could modify our code to this:
1: public static class DictionaryExtensions
2: {
3: public static TValue GetValueOrDefault<TKey, TValue>(this Dictionary<TKey, TValue> dict,
4: TKey key, TValue defaultIfNotFound = default(TValue))
5: {
6: TValue value;
7:
8: // value will be the result or the default for TValue
9: if (!dict.TryGetValue(key, out value))
10: {
11: value = defaultIfNotFound;
12: }
13:
14: return value;
15: }
16: }
Now, if defaultIfNotFound is unspecified, it will use default(TValue) which will be the default value for whatever value type the dictionary holds.
So let’s consider how we could use this:
1: lookup.GetValueOrDefault(1); // returns “Apple”
2:
3: lookup.GetValueOrDefault(5); // returns null
4:
5: lookup.GetValueOrDefault(5, “Unknown”); // returns “Unknown”
6:
Again, do not confuse a parameter-less constructor with the default value for a type. Remember that the default value for any type is the compile-time default for any instance of that type (0 for numeric, false for bool, null for reference types, and struct will all default fields for struct).
Consider the difference:
1: // both zero
2: int i1 = default(int);
3: int i2 = new int();
4:
5: // both “zeroed” structs
6: var dt1 = default(DateTime);
7: var dt2 = new DateTime();
8:
9: // sb1 is null, sb2 is an “empty” string builder
10: var sb1 = default(StringBuilder());
11: var sb2 = new StringBuilder();
So in the above code, notice that the value types all resolve the same whether using default or parameter-less construction. This is because a value type is never null (even Nullable<T> wrapped types are never “null” in a reference sense), they will just by default contain fields with all default values.
However, for reference types, the default is null and not a constructed instance. Also it should be noted that not all classes have parameter-less constructors (string, for instance, doesn’t have one – and doesn’t need one).
Summary
Whenever you need to get the default value for a type, especially a generic type, consider using the default keyword. This handy word will give you the default value for the given type at compile-time, which can then be used for initialization, optional parameters, etc.
Technorati Tags: C#,CSharp,.NET,Little Wonders,default