Должен ли метод GetEnumerator оставаться идемпотентным, когда класс не реализует IEnumerable - PullRequest
2 голосов
/ 16 ноября 2010

Этот вопрос в ответ на другой вопрос , который я поднял в связи с злоупотреблением интерфейсом IEnumerable путем изменения объекта во время его итерации.

По общему мнению, все, что реализует IEnumerable, не должно быть идемпотентным. Но .net поддерживает утку во время компиляции с помощью оператора foreach. Любой объект, который предоставляет метод GetEnumerator () IEnumerator, может использоваться внутри оператора foreach.

Так должен ли метод GetEnumerator быть идемпотентным или он реализует IEnumerable?

РЕДАКТИРОВАТЬ (Добавленный контекст)

Для краткости вокруг этого я предлагаю следующее: при итерации по очереди каждый элемент снимается с очереди. Кроме того, любые новые объекты, помещенные в очередь после вызова GetEnumerator, все равно будут повторяться.

Ответы [ 4 ]

3 голосов
/ 16 ноября 2010

Это обсуждение старое, и, насколько мне известно, нет единого мнения.

Пожалуйста, не путайте концепцию Duck-Typing (во время выполнения) со злоупотреблением поддерживаемым компилятором foreach для поддержки желаемой семантики.

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

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

  • Перечисление в случайном порядке данные (то есть генератор случайных чисел, потоки датчиков)
  • Перечисление по общему состоянию (например, файлы, базы данных, потоки и т. д.)

С другой стороны, это учитывает только «исходные» операции. Если вы реализуете операции фильтрации или преобразования с использованием перечислителей, всегда старайтесь сделать их идемпотентными.

3 голосов
/ 16 ноября 2010

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

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

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

0 голосов
/ 27 ноября 2010

Я бы предположил, что использование ForEach в коллекции не должно ее менять, если только название типа коллекции не подразумевает, что это произойдет.На мой взгляд, проблема заключается в том, что должно быть возвращено, если выполняется метод для использования коллекции в нечто перечисляемое (например, чтобы разрешить «Для каждого Foo в MyThing.DequeueAsEnum»).Если DequeueAsEnum возвращает iEnumerable, то кто-то может ожидать, что ему удастся избежать «Dim myIEnumerable As IEnumerable = MyThing.DequeueAsEnum», а затем использовать MyIEnumerable в двух непересекающихся циклах For-Each.Если DequeueAsEnum возвращает тип EnumerableOnlyOnce, то было бы немного яснее, что его возвращение должно быть перечислено только один раз.Безусловно, существование неявной типизации в новых диалектах C # и VB.Net несколько повышает вероятность того, что кто-то может назначить функцию, возвращающую переменную, когда этого не следует делать, но я не знаю, как это предотвратить.

Кстати, существует ряд обстоятельств, в которых было бы полезно предотвратить сохранение ссылки на класс в переменной;Есть ли способ объявить класс таким образом, что внешний код может использовать выражения этого типа класса, но не может объявлять его переменные?

0 голосов
/ 16 ноября 2010

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

В этой идее нет ничего плохого; Я бы просто поставил под сомнение ваше предпочтение специально использовать GetEnumerator для достижения того, что вы хотите.

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

Пример:

// Just a simplistic example. Not the way I'd actually write it.
class CustomQueue<T> : Queue<T>
{
    public IEnumerable<T> DequeueAll()
    {
        while (Count > 0)
        {
            yield return Dequeue();
        }
    }
}

(Обратите внимание, что вышеприведенное может даже быть метод расширения , если он представляет буквально only функциональность, которую вы хотели бы выше, помимо того, что уже предоставляется Queue<T>. )

Таким образом, вы все еще можете получить «чистый» код, который, я подозреваю, вам нужен, без (потенциальной) путаницы с неидемпотентным GetEnumerator:

// Pretty clean, right?
foreach (T item in queue.DequeueAll())
{
    Console.WriteLine(item);
}
...