Не должно быть duck typing в строго типизированном языке.
Это не duck typing, это
структурная типизация. Проверки, как вы могли заметить, выполняются во время компиляции, а не выполнения - где ж тут duck typing? А то что наследоваться не обязательно от опр. интерфейса - так что ж тут плохого? Язык даёт синтаксический сахар в виде foreach, задача компилятора - сгенерить код, содержащий вызовы определённых методов с определённой сигнатурой, не более. Это задача именно что для структурной типизации, когда компилятору без разницы что у вас там за класс и какие у него интерфейсы, лишь бы можно было сгенерировать нужный код, и это правильно.
В конце-концов шаблоны в C++ работают точно также. И for-range циклы в C++ работают похожим образом (требуют чтобы были реализованы нужные методы, или свободные функции, принимающие аргумент нужного типа). В C# советуют реализовывать интерфейс, т.к. это даёт дополнительные возможности: а) передавать объект туда, где ожидается этот интерфейс (т.к. C# всё таки в основном язык с номинативной типизацией); б) контролировать что вы не забыли ничего реализовать.
Прочитал, но по факту, класс всё равно реализует IEnumerable, просто явно об этом не заявляет.
Не соглашусь, довольно спорный момент. Номинативная типизация говорит, что если только явное заявление о том, что мы реализуем какой-то интерфейс, даёт нам отношение is-a между классом и интерфейсом. Если мы НЕ говорим, что реализуем IEnumerable, то мы его НЕ реализуем, даже если у нас там такие же методы.
И да, разберитесь заодно в разнице между статической и строгой типизацией, если вы не оговорились в комментарии, а действительно не понимаете разницы.