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

StackOverflow https://stackoverflow.com/questions/3737997

Вопрос

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

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

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


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

  1. IEnumerable<T> наследует от * IEnumerable, что означает, что любой тип, который реализует IEnumerable<T> Обычно необходимо реализовать обе IEnumerable<T>.GetEnumerator а также (явно) IEnumerable.GetEnumerator. Отказ Это в основном составляет код котельной.
  2. Ты сможешь foreach над любым типом, который имеет GetEnumerator метод, до тех пор, пока этот метод возвращает объект некоторых типов с MoveNext метод и а Current имущество. Так что, если ваш тип определяет один Способ с подписью public IEnumerator<T> GetEnumerator(), это законно перечислять его, используя foreach.
  3. Ясно, есть много кода, который требует IEnumerable<T> Интерфейс - например, в основном все методы расширения 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> Интерфейс, я могу просто определить метод расширения, чтобы перейти от любого 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 интерфейс); Но он только что выскочил в голову, и я не вижу никаких очевидных проблем, что этот подход будет позировать.

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

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

Это было полезно?

Решение

Вы не просто перемещаете котельной где-то еще - от записи IEnumerable.GetEnumeratorметод на каждом классе, чтобы позвонить AsEnumerable расширение каждый раз IEnumerable<T> ожидается? Как правило, я ожидал, что перечислимый тип, который будет использоваться для запроса гораздо больше раз, чем написано (что ровно один раз). Это означало бы, что этот шаблон приведет к более Котельная, в среднем.

Другие советы

Ты скучаешь по одной огромной вещи -

Если вы реализуете свой собственный интерфейс вместо IEnumerable<T>, ваш класс не будет работать с ожиданиями рамочных методов 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 потерял накладные расходы о себе отражения, так почему же беспокоит общий интерфейс? Что касается примера, сериализация приходит на ум.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top