Подтвердить что ты не робот

Почему я не могу использовать перечислитель массива вместо его реализации?

У меня есть такой код:

public class EffectValues : IEnumerable<object>
{
    public object [ ] Values { get; set; }

    public IEnumerator<object> GetEnumerator ( )
    {
        return this.Values.GetEnumerator ( );
    }

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator ( )
    {
        return this.GetEnumerator ( );
    }
}

Но компилятор жалуется, говоря:

"Невозможно неявно преобразовать тип 'System.Collections.IEnumerator' для 'System.Collections.Generic.IEnumerator. Явное преобразование существует (вы отсутствует листинг?)"

Я думал, что тип массива реализовал оба интерфейса IEnumerable, не так ли? Потому что я могу использовать функции Linq непосредственно в экземпляре значений.

4b9b3361

Ответ 1

Это тонкий и немного неудачный. Легкое обходное решение:

public IEnumerator<object> GetEnumerator ( )
{
     return ((IEnumerable<object>)this.Values).GetEnumerator ( );     
} 

Я думал, что тип массива реализовал оба интерфейса IEnumerable, не так ли?

Правила:

  • System.Array реализует IEnumerable "неявно" с помощью общедоступных методов.
  • каждый тип массива T [] наследует от System.Array.
  • каждый тип массива T [] реализует IList<T>, IEnumerable<T> и т.д.
  • поэтому каждый тип массива T [] можно преобразовать в IEnumerable<T>

Обратите внимание, что третий пункт был НЕ

  • каждый тип массива T [] реализует IList<T>, IEnumerable<T> и т.д. с общедоступными методами и свойствами, определенными в T [], которые неявно реализуют члены

И ты туда. Когда вы смотрите GetEnumerator, мы смотрим его на объект [] и не находим его, потому что объект [] реализует IEnumerable<object> явно. Он конвертируется в IEnumerable<object>, а конвертируемость не учитывается для поиска. (Вы бы не ожидали, что метод "double" появится в int только потому, что int преобразуется в double.) Затем мы рассмотрим базовый тип и обнаружим, что System.Array реализует IEnumerable с помощью общедоступного метода, поэтому мы наш наш GetEnumerator.

То есть подумайте об этом так:

namespace System
{
    abstract class Array : IEnumerable
    {
        public IEnumerator GetEnumerator() { ... }
        ...
    }
}

class object[] : System.Array, IList<object>, IEnumerable<object>
{
    IEnumerator<object> IEnumerable<object>.GetEnumerator() { ... }
    int IList<object>.Count { get { ... } }
    ...
}

Когда вы вызываете GetEnumerator по объекту [], мы не видим реализации, которая является явной реализацией интерфейса, поэтому переходим к базовому классу, который имеет один видимый.

Как все объекты [], int [], string [], SomeType [] генерируются "на лету"?

Магия!

Это не дженерики, не так ли?

Right. Массивы представляют собой очень специальные типы, и их запекают на глубоком уровне в систему типа CLR. Хотя они очень похожи на дженерики во многих отношениях.

Кажется, что это class object [] : System.Array - это то, что не может быть реализовано пользователем, правильно?

Правильно, это было просто для того, чтобы проиллюстрировать, как думать об этом.

Какой из них, по вашему мнению, лучше: лить GetEnumerator() в IEnumerable<object> или просто использовать foreach и yield?

Вопрос плохо сформирован. Вы не используете GetEnumerator для IEnumerable<object>. Вы либо передаете массив в IEnumerable<object>, либо добавили GetEnumerator в IEnumerator<object>.

Я бы, вероятно, использовал значения IEnumerable<object> и набрал GetEnumerator на нем.

Я, вероятно, буду использовать кастинг, но мне интересно, если это место, где вы или какой-то программист, который мог бы читать код, подумали бы об этом менее ясно.

Я думаю, что это довольно ясно с актерским составом.

когда вы сказали неявную реализацию, вы имеете в виду в виде интерфейса. Метод, правильно?

Нет, наоборот:

interface IFoo { void One(); void Two(); }
class C : IFoo
{
    public void One() {} // implicitly implements IFoo.One
    void IFoo.Two() {} // explicitly implements IFoo.Two
}

Первая декларация беззвучно реализует метод. Во-вторых, явствует, какой интерфейс он реализует.

Какая причина для реализации IEnumerable<T> как это, вместо неявной реализации с использованием общедоступных методов? Мне стало любопытно, потому что вы сказали: "Это тонкий и немного неудачный", так кажется, что из-за более старого решения, которое заставило вас сделать это, я думаю?

Я не знаю, кто принял это решение. Однако это печально. Это смутило хотя бы одного пользователя - вас... и это тоже смутило меня на несколько минут!

Я бы подумал, что тип массива будет примерно таким: public class Array<T> : IEnumerable<T> и т.д. Но вместо этого в нем есть какой-то магический код, верно?

Right. Как вы отметили в своем вчерашнем вопросе, все было бы очень по-другому, если бы у нас были генерики в CLR v1.

Массивы по существу представляют собой общий тип коллекции. Поскольку они были созданы в системе типов, у которой не было дженериков, для обработки их должно быть много специального кода в системе типов.

В следующий раз, когда вы создадите систему типов, поместите generics в v1 и убедитесь, что вы получаете сильные типы коллекций, типы с нулевым значением и типы с нулевым значением, испеченные в структуре с самого начала. Добавление генерических и нулевых типов значений post hoc было затруднительным.

Ответ 2

Вам нужно передать массив в IEnumerable<object>, чтобы иметь доступ к универсальному счетчику:

public IEnumerator<object> GetEnumerator() {
  return ((IEnumerable<object>)this.Values).GetEnumerator();
}