问题
如果我有一个类似这样的定义的类:
public class TestList : ObservableCollection<TestClass>, IList<ITestInterface>, ICollection<ITestInterface>, IEnumerable<ITestInterface>, IEnumerable
我无法在此类的对象上使用IEnumerable
扩展方法(System.Linq
命名空间)。
注意事项
ObservableCollection
中的类型与集合界面中的类型不同,但TestClass
确实实现了ITestInterface
foreach
对象中的项目仍可TestList
。这样做会使项目的类型为TestClass
using
有一个System.Linq
声明这显然与ObservableCollection
中的类型与类定义中的其他类型不同这一事实有关,但为什么呢?该集合仍然可以在foreach
语句中枚举,这基本上就是那些扩展方法所做的事情(当然还有其他逻辑)。
问题
为什么创建一个继承自一个类型的集合并实现另一个类型的集合接口的类会导致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)时遇到了这个问题。我试图在网格的.First()
属性上使用Columns
扩展方法,但Columns
属性的类型具有类似于我上面提供的类定义。 / p>
答案 0 :(得分:5)
首先关闭:请不要这样做。您已在同一类型上同时实施了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>
的类型参数应该是什么,因为有两个不兼容的选项。一个较小的复制品将是:
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上报告一个问题,也许这个可以修复。
答案 1 :(得分:1)
原因是编译器没有足够的类型信息来明确推断通用First<T>()
方法的类型参数。您的案例中可能的类型参数是TestClass
和ITestInterface
。
您可以通过显式指定type参数来帮助编译器。以下将编译:
var item1 = t.First<TestClass>();
var item2 = t.First<ITestInterface>();
是的,编译器可能产生了更好的错误消息。