Зачем реализовывать IEnumerable (T), если я могу просто определить один GetEnumerator?

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

так что да, считайте меня убежденным: это была бы плохая идея.


прежде чем вы отвергнете этот вопрос как абсурдный, я прошу Вас рассмотреть следующий:

  1. IEnumerable<T> наследует от* IEnumerable, что означает, что любой тип, реализующий IEnumerable<T> вообще должен реализовать и IEnumerable<T>.GetEnumerator и (явно) IEnumerable.GetEnumerator. Это в основном сводится к шаблонному коду.
  2. вы можете foreach над любым типом, который имеет GetEnumerator метод, если этот метод возвращает объект некоторого типа с MoveNext способ и Current собственность. Поэтому, если ваш тип определяет один метод с подписью public IEnumerator<T> GetEnumerator(), законно перечислять его с помощью foreach.
  3. очевидно, что существует много кода, который требует IEnumerable<T> interface -- например, в основном все методы расширения LINQ. К счастью, перейти от типа, который вы можете foreach на IEnumerable<T> тривиально, используя автоматическую генерацию итератора, которую C# поставляет через yield ключевое слово.

Итак, собирая все это вместе, у меня было это сумасшедшая идея: что, если я просто определю свой собственный интерфейс, который выглядит так:

public interface IForEachable<T>
{
    IEnumerator<T> GetEnumerator();
}

затем всякий раз, когда я определяю тип, который я хочу быть перечисляемым, я реализую этой интерфейс вместо IEnumerable<T>, устраняя необходимость реализации двух GetEnumerator методы (явных). Например:

class NaturalNumbers : IForEachable<int>
{
   public IEnumerator<int> GetEnumerator()
   {
       int i = 1;
       while (i < int.MaxValue)
       {
           yield return (i++);
       }
   }

   // Notice how I don't have to define a method like
   // IEnumerator IEnumerable.GetEnumerator().
}

наконец-то, чтобы сделать этот тип совместимым с кодом, который тут ждать IEnumerable<T> интерфейс, I можно просто определить метод расширения, чтобы перейти от любого IForEachable<T> до IEnumerable<T> вот так:

public static class ForEachableExtensions
{
    public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
    {
        foreach (T item in source)
        {
            yield return item;
        }
    }
}

мне кажется, что это позволяет мне разрабатывать типы, которые можно использовать во всех отношениях как реализации IEnumerable<T>, но без этого досадного явные IEnumerable.GetEnumerator реализация в каждом из них.

например:

var numbers = new NaturalNumbers();

// I can foreach myself...
foreach (int x in numbers)
{
    if (x > 100)
        break;

    if (x % 2 != 0)
        continue;

    Console.WriteLine(x);
}

// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
                  where x % 2 == 0
                  select x;

foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
    Console.WriteLine(x);
}

что вы думаете об этой идее? Я упускаю какую-то причину, почему это было бы ошибкой?

я понимаю это, вероятно кажется, слишком сложное решение того, что не так уж важно для начала (я сомневаюсь, что кто-нибудь действительно заботится о том, чтобы явно определить IEnumerable интерфейс); но он просто выскочил в моей голове, и я не вижу никаких очевидных проблем, которые этот подход будет представлять.

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

*это правильная терминология для использования? Я всегда не решаюсь сказать, что один интерфейс "наследует" от другого, поскольку это, похоже, не правильно отражает отношения между ними. Но, может быть, это правильно.

6 ответов


вы просто перемещаете шаблонную табличку куда - то еще-от написания IEnumerable.GetEnumeratorметод для каждого класса для вызова вашего


вы упускаете одну огромную вещь -

если вы реализуете свой собственный интерфейс вместо IEnumerable<T>, ваш класс не будет работать с методами framework, ожидающими IEnumerable<T> - в основном, вы не сможете использовать LINQ или использовать свой класс для создания List<T>, или многие другие полезные абстракции.

вы можете сделать это, как вы упомянули, с помощью отдельного метода расширения-однако, это происходит по цене. Используя метод расширения для преобразования в IEnumerable<T>, вы добавляете еще один уровень абстракции, необходимый для использования вашего класса (что вы будете делать гораздо чаще, чем создание класса), и уменьшаете производительность (Ваш метод расширения, по сути, генерирует новую реализацию класса внутри, что действительно не нужно). Самое главное, любой другой пользователь вашего класса (или вы позже) должен будет изучить новый API, который ничего не делает - вы делаете свой класс более сложным в использовании, не используя стандартные интерфейсы, так как он нарушает ожидания пользователя.


вы правы: это тут кажется слишком сложным решением довольно простой проблемы.

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

кроме того, хотя ваш метод расширения позволяет конвертировать любые IForEachable<T> на IEnumerable<T>, это означает, собственно ваш тип не удовлетворяет общему ограничению, как это:

public void Foo<T>(T collection) where T : IEnumerable

или тому подобное. В принципе, выполняя преобразование, вы теряете возможность обрабатывать один объект как и реализация IEnumerable<T> и реальный конкретный тип.

кроме того, не реализовала IEnumerable, вы считаете себя из инициализаторов коллекции. (Я иногда реализую IEnumerable явно просто выбрать в инициализация коллекции, выбрасывание исключения из GetEnumerator().)

О, и вы также ввели дополнительный бит инфраструктуры, которая незнакома всем остальным в мире, по сравнению с огромными ордами разработчиков C#, которые уже знают о IEnumerable<T>.


в нашей кодовой базе, вероятно, более 100 экземпляров этого точного фрагмента:

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

и я действительно в порядке с этим. Это крошечная цена, чтобы заплатить за полную совместимость со всеми другими частями кода .NET, когда-либо написанными;)

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


это намного проще в использовании IEnumerable для размышлений. Попытка вызвать общие интерфейсы через отражение-такая боль. Штраф бокса через IEnumerable теряется накладными расходами самого отражения, так зачем беспокоиться об использовании общего интерфейса? Например, на ум приходит сериализация.


Я думаю, что все уже упоминали технические причины не делать этого, поэтому я добавлю это в микс: требование вашего пользователя вызвать AsEnumerable() в вашей коллекции, чтобы иметь возможность использовать перечисляемые расширения, нарушит принцип наименьшего удивления.