Есть ли способ определить, является ли IEnumerable генератором последовательности или истинной коллекцией?

#c# #linq

#c# #linq

Вопрос:

Позвольте мне привести пример:

Предположим, у меня есть метод:

 public void DoStuff(IEnumerable<T> sequence)
{
    if (/* is lazy sequence */) throw ...

    // Do stuff...
}
  

И я хочу защититься от потенциально бесконечных последовательностей.

Редактировать:

Чтобы уточнить, защита от бесконечной коллекции — это только одно из применений. Как упоминал Джон. Вы можете легко иметь бесконечный IList . Хороший момент.

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

Комментарии:

1. Ну, тогда почему бы просто не взять ICollection<T> (примечание: оно все равно может быть бесконечным, оно все равно может быть ленивым, но это «коллекция»).

2. Что требуется для «истинной коллекции»? Произвольный доступ в O(1)? Разрешен ли связанный список? Является ли результат File.ReadLines истинной коллекцией?

3. Поскольку IEnumerable — это просто интерфейс, удовлетворяемый любой упомянутой вещью (коллекция против генератора) Я так не думаю … хотя вы могли бы попробовать какой-нибудь хак, например, вызов Last , но это может быть опасно для «генератора», если он бесконечен…

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

5. Вы говорите, что если кто-то пишет реализацию конечного автомата вручную, это нормально, но если компилятор пишет это за вас, это плохо? Для меня это не имеет никакого смысла. Какую проблему вы действительно пытаетесь решить здесь?

Ответ №1:

Нет ничего гарантированного, нет. Вы могли бы увидеть, реализует ли последовательность также IList<T> — это запрещало бы реализации блоков итераторов, но все равно могло бы быть «виртуальным» списком, который продолжается вечно, и он также потерпел бы неудачу для некоторых других конечных коллекций без итераторов.

С точки зрения того, от чего вы пытаетесь защититься, подойдет ли последовательность длиной в 2 миллиарда для того, что вы пытаетесь сделать? Будет ли метод расширения, который выдает исключение, если у вас в итоге больше некоторого «предела» (но делает это лениво), работать для вас? (Что-то вроде Take , но которое взорвалось в конце.)

Ответ №2:

Это невозможно.

API IEnumerable не может сказать вам, является ли он бесконечным или нет. Вы можете попробовать применить его к ICollection, чтобы уловить распространенный случай, когда кто-то передал вам один из них, но если это то, что вы хотите, то почему бы вам в первую очередь не использовать объект ICollection<…>?

Ответ №3:

Итераторы не поддерживают метод IEnumerable<>.Reset():

 public void DoStuff(IEnumerable<T> sequence)
{
    sequence.Reset();
    // etc..
}
  

Вы получаете NotSupportedException , что хорошо, с сообщением «Указанный метод не поддерживается», что допустимо.

Комментарии:

1. Интересно, но кажется очень хакерским 🙂

2. Ну, ответ «это невозможно!» очень популярен. Возможно, вам следует согласиться с этим.

3. @Hans 1 для snark и истинности импликации.

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