Skip to main content
 首页 » 编程设计

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

2024年01月26日30mayingbao

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

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

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

注意事项

  • ObservableCollection 中的类型与集合接口(interface)中的类型不同,但 TestClass 确实实现了 ITestInterface
  • 仍然可以foreach TestList 对象中的项目。这样做会为项提供 TestClass
  • 的类型
  • 我在文件顶部有一个 using 语句用于 System.Linq

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

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

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

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 属性的类型有一个类似于我上面提供的类定义。

请您参考如下方法:

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

例如: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> 的类型参数是什么应该是因为有两个不兼容的选项。一个较小的 repro 是:

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 上报告一个问题,也许这个问题可以得到解决。