Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous call when a method has overloads for IDictionary and IDictionary<TKey, TValue>

When a method has two overloads, one accepting IDictionary and another accepting IDictionary<TKey, TValue>, passing new Dictionary<string, int>() to it is considered ambigous. However, if the two overloads are changed to accept IEnumerable and IEnumerable<KeyValuePair<TKey, TValue>>, the call is no longer ambigous.

As Dictionary<TKey, TValue> implements all of the above interfaces (to be precise, IDictionary<TKey, TValue>, ICollection<KeyValuePair<TKey, TValue>>, IDictionary, ICollection, IReadOnlyDictionary<TKey, TValue>, IReadOnlyCollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable, ISerializable, IDeserializationCallback in .NET 4.5); as IDictionary is inherited from IEnumerable and IDictionary<TKey, TValue> is inherited from IEnumerable<KeyValuePair<TKey, TValue>>, I can't understand why it happens.

Sample console application:

using System;
using System.Collections;
using System.Collections.Generic;

namespace AmbigousCall
{
    internal class Program
    {
        static void Main (string[] args)
        {
            var dic = new Dictionary<string, int>();
            FooDic(dic); // Error: The call is ambiguous
            FooEnum(dic); // OK: The generic method is called
            Console.ReadKey();
        }

        static void FooDic (IDictionary dic) {}
        static void FooDic<TKey, TValue> (IDictionary<TKey, TValue> dic) {}
        static void FooEnum (IEnumerable dic) {}
        static void FooEnum<TKey, TValue> (IEnumerable<KeyValuePair<TKey, TValue>> dic) {}
    }
}

The error I get is: The call is ambiguous between the following methods or properties: 'AmbigousCall.Program.FooDic(System.Collections.IDictionary)' and 'AmbigousCall.Program.FooDic(System.Collections.Generic.IDictionary)'

Question 1: Why does it happen?

Question 2: How to accept both generic and non-generic arguments without causing ambiguity if a class implements both?

like image 536
Athari Avatar asked Apr 24 '13 16:04

Athari


2 Answers

C# will call the most specific overload available. It has no trouble identifying IEnumerable<T> as more specific than IEnumerable because IEnumerable<T> extends IEnumerable. However, IDictionary<T, U> does not extend IDictionary, so even though Dictionary<T, U> implements both, the compiler cannot identify which is more specific. To the compiler, these might as well be completely unrelated interfaces.

You'd have to give the compiler a hint by using an explicit cast:

FooDic((IDictionary)dic); // not ambiguous
FooDic((IDictionary<string, int>)dic); // not ambiguous
like image 129
p.s.w.g Avatar answered Oct 17 '22 10:10

p.s.w.g


The difference is that IEnumerable<T> inherits IEnumerable, whereas IDictionary<TKey, TValue> does not inherit IDictionary.

As a result, resolving between overloads that accept IEnumerable<T> and IEnumerable is a simple matter of determining whether the argument matches the more specific or the more general version, whereas resolving between IDictionary and IDictionary<TKey, TValue> is impossible because the two interfaces are not related.

If you have overloads that accept IDictionary and IDictionary<TKey, TValue>, you'll have to cast your argument to the type you want:

FooDic((IDictionary)value);

or

FooDic((IDictionary<TKey, TValue>)value);
like image 4
Jeremy Todd Avatar answered Oct 17 '22 12:10

Jeremy Todd