Search code examples
c#interface

Why can't the compiler see IEnumerable<T> when IReadonlyDictionary<U, T> is also present


I have a class in .NET Framework 4.8 that implements both IEnumerable<T> and IReadonlyDictionary<U, T>:

public class Foo : IEnumerable<Bar>, IReadOnlyDictionary<int, Bar>
{
    public Bar this[int key] => /* ... */;

    public IEnumerable<int> Keys => /* ... */;

    public IEnumerable<Bar> Values => /* ... */;

    public int Count => /* ... */;

    public bool ContainsKey(int key) => /* ... */;

    public IEnumerator<Bar> GetEnumerator() => /* ... */;

    public bool TryGetValue(int key, out Bar value) => /* ... */;

    IEnumerator IEnumerable.GetEnumerator() { /* ... */; }

    IEnumerator<KeyValuePair<int, Bar>> IEnumerable<KeyValuePair<int, Bar>>.GetEnumerator()
    {
        /* ... */;
    }
}

Using this class in a foreach loop works fine:

var foo = new Foo();
foreach (var bar in foo)
{
    /* ... */
}

But if I try to use an extention method for IEnumerable<T>, for example

bool b = foo.Any();

I'm getting the error

CS1061 'Foo' does not contain a definition for 'Any' and no accessible extension method 'Any' accepting a first argument of type 'Foo' could be found (are you missing a using directive or an assembly reference?)

My code already contains using System.Linq, and If I remove IReadOnlyDictionary<int, Bar> from my class the error goes away. Why can't the compiler see IEnumerable<T> when IReadonlyDictionary<U, T> is also present?


Solution

  • Since IReadOnlyDictionary<K, V> inherits from IEnumerable<KeyValuePair<K, V>>. Your class implements both IEnumerable<KeyValuePair<int, Bar>> and IEnumerable<Bar>.

    The extension method IEnumerable.Any is generic, and is an extension on all IEnumerable<T>s.

    public static bool Any<TSource> (this System.Collections.Generic.IEnumerable<TSource> source);
    

    The compiler cannot figure out what TSource should be - whether it should be Bar or KeyValuePair<int, Bar>.

    foreach doesn't have this issue only because you implemented IEnumerable<Bar> implicitly. This allows GetEnumerator to be found directly on Foo when the compiler does a member lookup. If you had implemented it explicitly like this:

    IEnumerator<Bar> IEnumerable<Bar>.GetEnumerator() => ...;
    

    foreach wouldn't be able to figure out what you want either. Follow the steps that the compiler takes, specified in the language spec to see exactly how this works.

    In addition to casting it to the desired instantiation of IEnumerable<T> like in CodeCaster's answer, you can also tell it what TSource you want by specifying the type arguments of Any:

    bool b = foo.Any<Bar>();