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?
You only need one extension method. Redefine it as follows:
Both
IDictionary<TKey, TValue>
andIReadOnlyDictionary<TKey, TValue>
inherit fromIEnumerable<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>
orIReadOnlyDictionary<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>
andIDictionary<TKey, TValue>
is, well, odd, as the behavior ofIDictionary<TKey, TValue>
is a superset of the behavior ofIReadOnlyDictionary<TKey, TValue>
. And the standardDictionary<TKey, TValue>
class already implements both of these interfaces. Therefore, if the OP's (I'm assuming, specialized) type had inherited fromDictionary<TKey, TValue>
instead, then when read-only functionality was required, the type could've simply been cast to anIReadOnlyDictionary<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>
toIReadOnlyDictionary<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 theIReadOnly*
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 concreteReadOnly*
class). This is one reason (among others) that theSystem.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.