从集合继承和实现可枚举接口时,IEnumerable 扩展方法 (System.Linq) 不可用

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

问题
如果我有一个 class 定义如下:

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

我无法在此 class 的对象上使用 IEnumerable 扩展方法(System.Linq 命名空间)。

注意事项

这显然与 ObservableCollection 中的类型与 class 定义中的其他类型不同这一事实有关,但为什么呢?集合仍然可以在 foreach 语句中枚举,这基本上就是那些扩展方法所做的(当然还有额外的逻辑)。

问题
为什么创建一个继承自一种类型的集合并实现另一种类型的集合接口的 class 会导致 System.Linq 中的扩展方法无法用于 class 的对象?

最小、完整且可验证的示例

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();
    }
}

背景
对于那些好奇的人,我在使用 Telerik RadGridView 控件 (WPF) 时遇到了这个问题。我试图在网格的 Columns 属性 上使用 .First() 扩展方法,但是 Columns 属性 的类型有一个 class 与我上面提供的类似的定义。

原因是编译器没有足够的类型信息来明确推断泛型 First<T>() 方法的类型参数。您的情况下可能的类型参数是 TestClassITestInterface.

您可以通过显式指定类型参数来帮助编译器。以下将编译:

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

是的,编译器本可以生成更好的错误消息。

首先:请不要这样做。您在同一类型上实现了 IEnumerable<TestClass>IEnumerable<ITestInterface>,这可能会导致一些 really 讨厌的问题。

例如:IEnumerable<T> 是协变的,所以如果将类型转换为 IEnumerable<Object>,会发生什么情况?对象序列只包含 TestClass 个对象还是可以包含任何实现 ITestInterface 的对象?很难说! (有关这一点的详细讨论,请参阅我 2007 年关于此主题的文章的评论:https://blogs.msdn.microsoft.com/ericlippert/2007/11/09/covariance-and-contravariance-in-c-part-ten-dealing-with-ambiguity/

你所处的特别糟糕的情况是,正如另一个答案所指出的,重载决策的类型推断步骤无法推断出 First<T> 的类型参数应该是什么,因为有两个不兼容的选项.较小的复制将是:

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

现在 new C().First() 给你同样的错误。

错误消息很糟糕,对此我深表歉意。常见的情况是没有这种适用的扩展方法,并且针对这种情况优化了错误消息。如果检测到 原因 没有适用的扩展方法是由于类型推断失败,那就更好了。在其他错误报告场景中我这样做了,但不是这个。

考虑在 GitHub 上报告一个问题,也许这个问题可以得到解决。