Должен ли метод GetEnumerator по-прежнему быть идемпотентным, если класс не реализует IEnumerable
Этот вопрос является копилкой другого вопроса, который я поднял относительно злоупотребления интерфейсом IEnumerable путем изменения объекта по мере его итерации.
Общее мнение состоит в том, что ни одна вещь, реализующая IEnumerable, не должна быть идемпотентной. Но .net поддерживает время компиляции duck typing с помощью оператора foreach. Любой объект, предоставляющий метод IEnumerator GetEnumerator (), может использоваться внутри оператора foreach.
Таким образом, метод GetEnumerator должен быть идемпотентна она или когда реализует IEnumerable?
EDIT (добавлен контекст)
Чтобы поместить некоторый контекст вокруг этого, я предлагаю, чтобы при итерации по очереди каждый элемент был удален из очереди по мере его прохождения. Кроме того, все новые объекты, помещенные в очередь после вызова GetEnumerator, будут по-прежнему повторяться.
4 ответа:
Это неТип , который является идемпотентным - это даже не имеет большого смысла; выможете иметь в виду неизменяемость, но это не ясно. Это сам метод
GetEnumerator
, который обычно является идемпотентным.Хотя я бы сказал, что это типичный случай, я могу представить себе частные случаи, когда имеет смысл иметь неидемпотентный метод
Такие типы и методы, конечно, должны быть очень тщательно документированы, но я думаю, что они разумны.GetEnumerator
. Например, у вас могут быть данные, которые можно прочитать только один раз (потому что они передаются с веб-сервера, который не будет обслуживать опять та же просьба или что-то в этом роде). В этом случаеGetEnumerator
должен был бы фактически аннулировать источник данных, так что будущие вызовы вызвали бы исключение.
Эта дискуссия-старая, и, насколько мне известно, единого мнения нет.
Пожалуйста, не путайте концепцию (runtime) Duck-Typing со злоупотреблением компилятором, поддерживающим
Еще одна концепция, которую вы, кажется, путаете, - это Идемпотенция и неизменность. В соответствии с вашей формулировкой вы пытаетесь описать второй, что означает, что объект, предоставляющий перечислитель, изменяется во время перечисления. Идемпотенция, с другой стороны, означает ваше перечислитель, при вызове дважды даст те же результаты.foreach
для поддержки желаемой семантики.Теперь, когда нам все ясно, вам нужно тщательно решить, какую семантику должна поддерживать ваша операция IEnumerable. Некоторые виды перечислений трудно сделать идемпотентными (т. е. включать кэширование) и обычно попадают в одну из следующих категорий:
С другой стороны, это относится только к операциям "источник". Если вы реализуете операции фильтрации или преобразования с помощью перечислителей, вы всегда должны стараться сделать их идемпотентными.
- перечисление над случайным изменением данные (то есть генератор случайных чисел, потоки датчиков)
- перечисление по общему состоянию (например, файлы, базы данных, потоки и т. д.)
Похоже, вам нужен класс очереди, из которого вы можете удалить все элементы в красивом однострочном режиме.
Нет ничего плохого в этой идее как таковой; я бы просто поставил под сомнение ваше предпочтение конкретно использовать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(); } } }
(Обратите внимание, что выше может быть даже метод расширения , если он представляет буквальнотолько функциональность, которую вы хотели бы видеть выше и за пределами того, что уже предусмотрено
Queue<T>
.)Таким образом, вы все еще можете получить "чистый"код, который я подозреваю, что вы ищете, без (потенциальной) путаницы неидемпотентного
GetEnumerator
:// Pretty clean, right? foreach (T item in queue.DequeueAll()) { Console.WriteLine(item); }
Я бы предположил, что использование ForEach для коллекции не должно изменять ее, если только имя типа коллекции не подразумевает, что это произойдет. Вопрос в моем уме будет заключаться в том, что должно быть возвращено, если метод выполняется для использования коллекции в нечто перечисляемое (например, чтобы разрешить " для каждого Foo в MyThing.DequeueAsEnum"). Если DequeueAsEnum возвращает iEnumerable, то кто-то может ожидать, что ему сойдет с рук "Dim myIEnumerable As IEnumerable = MyThing.DequeueAsEnum", а затем использовать Мой номер состоит из двух непересекающихся друг для друга петель. Если DequeueAsEnum возвращает тип EnumerableOnlyOnce, то было бы немного яснее, что его возвращение должно быть перечислено только один раз. Безусловно, существование неявной типизации в более новых C# и VB.Net диалекты делают немного более вероятным, что кто-то может назначить функцию return переменной, когда они не должны, но я не знаю, как предотвратить это.
Кстати, есть ряд обстоятельств, при которых было бы полезно предотвратить существует ли способ объявить класс таким образом, чтобы внешний код мог использовать выражения этого типа класса, но не мог объявлять переменные этого типа?