Search code examples
c#idictionary

Extension methods for both IDictionary and IReadOnlyDictionary


My question is similar to the previous one, but the answer to that question is inapplicable to this one.

Well, I want to write an extension method for both IDictionary and IReadOnlyDictionary interfaces:

public static TValue? GetNullable<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> dictionary, TKey key)
    where TValue : struct
{
    return dictionary.ContainsKey(key)
        ? (TValue?)dictionary[key]
        : null;
}

public static TValue? GetNullable<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key)
    where TValue : struct
{
    return dictionary.ContainsKey(key)
        ? (TValue?)dictionary[key]
        : null;
}

But when I use it with classes implementing both interfaces (e.g. Dictionary<Tkey, TValue>), I get the ‘ambiguous invocation’. I don't want to type var value = myDic.GetNullable<IReadOnlyDictionary<MyKeyType, MyValueType>>(key), I want it to be just var value = myDic.GetNullable(key).

Is this possible?


Solution

  • You only need one extension method. Redefine it as follows:

    public static TValue? GetNullableKey<TKey, TValue>(this IEnumerable<KeyValuePair<TKey, TValue>> dictionary, TKey, key) where TValue : struct
    {
        // Your code here.
        // Per @nmclean: to reap performance benefits of dictionary lookup, try to cast
        //               dictionary to the expected dictionary type, e.g. IDictionary<K, V> or
        //               IReadOnlyDictionary<K, V>. Thanks for that nmclean!
    }
    

    Both IDictionary<TKey, TValue> and IReadOnlyDictionary<TKey, TValue> inherit from IEnumerable<KeyValuePair<TKey, TValue>>.

    HTH.

    UPDATE: To answer your comment question

    If you don't need to call methods that only appear on one interface or the other (i.e. you're only calling methods that exist on IEnumerable<KeyValuePair<TKey, TValue>>), then no, you don't need that code.

    If you do need to call methods on either the IDictionary<TKey, TValue> or IReadOnlyDictionary<TKey, TValue> interface that don't exist on the common base interface, then yes, you'll need to see if your object is one or the other to know which methods are valid to be called.

    UPDATE: You probably (most likely) shouldn't use this solution

    So, technically, this solution is correct in answering the OP's question, as stated. However, this is really not a good idea, as others have commented below, and as I have acknowledged as correct those comments.

    For one, the whole point of a dictionary/map is O(1) (constant time) lookup. By using the solution above, you've now turned an O(1) operation into an O(n) (linear time) operation. If you have 1,000,000 items in your dictionary, it takes up to 1 million times longer to look up the key value (if you're really unlucky). That could be a significant performance impact.

    What about a small dictionary? Well, then the question becomes, do you really need a dictionary? Would you be better off with a list? Or perhaps an even better question: where do you start to notice the performance implications of using an O(n) over O(1) lookup and how often do you expect to be performing such a lookup?

    Lastly, the OP's situation with an object that implements both IReadOnlyDictionary<TKey, TValue> and IDictionary<TKey, TValue> is, well, odd, as the behavior of IDictionary<TKey, TValue> is a superset of the behavior of IReadOnlyDictionary<TKey, TValue>. And the standard Dictionary<TKey, TValue> class already implements both of these interfaces. Therefore, if the OP's (I'm assuming, specialized) type had inherited from Dictionary<TKey, TValue> instead, then when read-only functionality was required, the type could've simply been cast to an IReadOnlyDictionary<TKey, TValue>, possibly avoiding this whole issue altogether. Even if the issue was not unavoidable (e.g. somewhere, a cast is made to one of the interfaces), it would still be better to have implemented two extension methods, one for each of the interfaces.

    I think one more point of order is required before I finish this topic. Casting a Dictionary<TKey, TValue> to IReadOnlyDictionary<TKey, TValue> only ensures that whatever receives the casted value will not itself be able to mutate the underlying collection. That does not mean, however, that other references to the dictionary instance from which the casted reference was created won't mutate the underlying collection. This is one of the gripes behind the IReadOnly* collection interfaces—the collections referenced by those interfaces may not be truly "read-only" (or, as often intimated, immutable) collections, they merely prevent mutation of the collection by a particular reference (notwithstanding an actual instance of a concrete ReadOnly* class). This is one reason (among others) that the System.Collections.Immutable namespace of collection classes were created. The collections in this namespace represent truly immutable collections. "Mutations" of these collections result in an all-new collection being returned consisting of the mutated state, leaving the original collection unaffected.