Alex
Alex

Reputation: 191

IEnumerable extension methods (System.Linq) unavailable when inheriting from collection and implementing enumerable interfaces

Problem
If I have a class with a definition like this:

public class TestList : ObservableCollection<TestClass>, IList<ITestInterface>, ICollection<ITestInterface>, IEnumerable<ITestInterface>, IEnumerable

I cannot use the IEnumerable extension methods (System.Linq namespace) on objects of this class.

Things to Note

It clearly has something to do with the fact that the type in the ObservableCollection is different than the other types in the class definition, but why? The collection can still be enumerated in a foreach statement, and that is basically what those extension methods do anyways (with additional logic of course).

Question
Why does creating a class that inherits from a collection of one type and implements collection interfaces of another type cause the extension methods in System.Linq to be unavailable to use on objects of the class?

Minimal, Complete, and Verifiable Example

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

namespace TestApp
{
    class Program
    {
        static void Main(string[] args)
        {
            var t = new TestList();

            t.First(); //Extension method unavailable, compiler error

            foreach (var item in t)
            {
                //item is of type TestClass
            }
        }

    }

    public interface ITestInterface { }


    public class TestClass : ITestInterface { }


    public class TestList : System.Collections.ObjectModel.ObservableCollection<TestClass>, IList<ITestInterface>, ICollection<ITestInterface>, IEnumerable<ITestInterface>, IEnumerable
    {
        //Method implementations are unnecessary for the example
        ITestInterface IList<ITestInterface>.this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }

        public bool IsReadOnly => throw new NotImplementedException();

        public void Add(ITestInterface item) => throw new NotImplementedException();

        public bool Contains(ITestInterface item) => throw new NotImplementedException();

        public void CopyTo(ITestInterface[] array, int arrayIndex) => throw new NotImplementedException();

        public int IndexOf(ITestInterface item) => throw new NotImplementedException();

        public void Insert(int index, ITestInterface item) => throw new NotImplementedException();

        public bool Remove(ITestInterface item) => throw new NotImplementedException();

        IEnumerator<ITestInterface> IEnumerable<ITestInterface>.GetEnumerator() => throw new NotImplementedException();
    }
}

Background
For those that are curious, I came across this while working with the Telerik RadGridView control (WPF). I was trying to use the .First() extension method on the Columns property of the grid, but the type of the Columns property has a class definition that is similar to what I provided above.

Upvotes: 2

Views: 1096

Answers (2)

Eric Lippert
Eric Lippert

Reputation: 660004

First off: please do not do this. You have implemented both IEnumerable<TestClass> and IEnumerable<ITestInterface> on the same type and this can cause some really nasty problems.

For example: IEnumerable<T> is covariant, so if you convert your type to IEnumerable<Object>, what happens? Does the sequence of objects contain only TestClass objects or can it contain any object that implements ITestInterface? Hard to say! (For an extended discussion of this point, see the comments on my 2007 article on this subject: https://blogs.msdn.microsoft.com/ericlippert/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity/)

The particularly nasty situation you're in is, as the other answer notes, the type inference step of overload resolution is unable to deduce what the type argument of First<T> should be because there are two incompatible options. A smaller repro would be:

public class C : IEnumerable<string>, IEnumerable<object>
{
    IEnumerator<string> IEnumerable<string>.GetEnumerator() => null;
    IEnumerator<object> IEnumerable<object>.GetEnumerator() => null;
    IEnumerator IEnumerable.GetEnumerator() => null;
} 

And now new C().First() gives you the same error.

The error message is horrid, and I apologize for that. The common case is that there is no such applicable extension method and the error message is optimized for that case. It would have been better to detect that the reason that there was no applicable extension method was due to type inference failure. In other error reporting scenarios I did that, but not this one.

Consider reporting an issue on GitHub and perhaps this one can get fixed.

Upvotes: 6

Ondrej Tucny
Ondrej Tucny

Reputation: 27962

The reason is the compiler has no enough type information to unambiguously infer the type parameter of the generic First<T>() method. The possible type parameters in your case are TestClass and ITestInterface.

You can help the compiler by specifying the type argument explicitly. The following will compile:

var item1 = t.First<TestClass>();
var item2 = t.First<ITestInterface>();

And yes, the compiler could have produced a better error message.

Upvotes: 2

Related Questions