Методы расширения IEnumerable (System.Linq) недоступны при наследовании от коллекции и реализации перечислимых интерфейсов. - PullRequest
0 голосов
/ 02 мая 2018

Задача
Если у меня есть класс с таким определением:

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 имеет определение класса, аналогичное приведенному выше.

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Прежде всего: пожалуйста, не делайте этого . Вы реализовали 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, и, возможно, эту проблему можно исправить.

0 голосов
/ 02 мая 2018

Причина в том, что у компилятора недостаточно информации о типе, чтобы однозначно вывести параметр типа универсального метода First<T>(). Возможные параметры типа в вашем случае: TestClass и ITestInterface.

Вы можете помочь компилятору, явно указав аргумент типа. Будет скомпилировано следующее:

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

И да, компилятор мог бы выдать лучшее сообщение об ошибке.

...