I don't like to deal with null so my implementation will look like this:
interface Maybe<T> {
bool HasValue {get;}
T Value {get;}
}
class Nothing<T> : Maybe<T> {
public bool HasValue { get { return false; } }
public T Value { get { throw new Exception(); } }
public static const Nothing<T> Instance = new Nothing<T>();
}
class Just<T> : Maybe<T> {
private T _value;
public bool HasValue { get { return true; } }
public T Value { get { return _value; } }
public Just(T val) {
_value = val;
}
}
Maybe is a object that can contain value or not. Note that Nothing class contains static field Instance. We can use this value instead of creating new value each time we need to return Nothing from function.
Now, we need to create our own dictionary class:
class MyDictionary<TKey, TValue>
{
private Dictionary<TKey, TValue> _dict;
...
public Maybe<TValue> this[TKey key] {
TValue val;
if (_dict.TryGetValue(key, out val)) {
return new Just<TValue>(val);
return Nothing<TValue>.Instance;
}
}
Advantage of this approach is not clear, because C# doesn't have pattern matching. But it can be emulated with dynamic:
void ProcessResult(Just<string> val) {
Console.WriteLine(val);
}
void ProcessResult(Nothing<string> n) {
Console.WriteLine("Key not found");
}
var dict = new MyDictionary<string, string>();
...
dynamic x = dict["key"];
ProcessResult(x);
I think that this is very clear way to express the fact that dictionary can't always return meaningful result. Also it is obvious for reader that function overload ProcessResult(Just<T>) will be called only for values that present in dictionary and other overload will be called in case when key is not found.
Pros:
- Type serves as a specification.
- Dictionary can contain both value and reference types.
Cons:
- More keystrokes.
- Little more complexity to deal with.