1

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

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()的属性上使用扩展方法,但是属性的类型有一个类似于我上面提供的类定义。ColumnsColumns

4

2 回答 2

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-第十部分处理歧义/

正如另一个答案所指出的,您所处的特别糟糕的情况是,重载解析的类型推断步骤无法推断出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 上报告一个问题,也许这个问题可以得到修复。

于 2018-05-02T20:11:52.013 回答
1

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

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

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

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

于 2018-05-02T19:50:08.017 回答