Подтвердить что ты не робот

Утка, набирая в компиляторе С#

Примечание Это не вопрос о том, как реализовать или эмулировать утиную печать на С#...

В течение нескольких лет у меня создалось впечатление, что некоторые функции языка С# не зависят от структур данных, определенных в самом языке (который всегда казался мне странным сценарием курица и яйца). Например, у меня создалось впечатление, что цикл foreach доступен только для использования с типами, которые реализовали IEnumerable.

С тех пор я понял, что компилятор С# использует утиную типизацию, чтобы определить, можно ли использовать объект в цикле foreach, ища метод GetEnumerator, а не IEnumerable. Это имеет большой смысл, поскольку он удаляет куриную и яичную головоломку.

Я немного смущен, почему это не похоже на блок using и IDisposable. Есть ли какая-то особая причина, по которой компилятор не может использовать утиную печать и искать метод Dispose? В чем причина этой несогласованности?

Возможно, что что-то еще происходит под капотом с IDisposable?

Обсуждая, почему вы когда-либо имели объект с методом Dispose, который не реализовал IDisposable, выходит за рамки этого вопроса:)

4b9b3361

Ответ 1

Здесь нет ничего особенного в IDisposable, но в итераторах есть что-то особенное.

До С# 2 использование этого типа утки на foreach было единственным, что вы могли бы реализовать строго типизированный итератор, а также единственный способ повторения типов значений без бокса. Я подозреваю, что если бы у С# и .NET были дженерики для начала, foreach потребовал бы IEnumerable<T> вместо этого, и не имел бы утиный ввод.

Теперь компилятор использует этот тип утиного набора текста в нескольких других местах, о которых я могу думать:

  • Инициализаторы коллекции ищут подходящую перегрузку Add (а также тип, который должен реализовывать IEnumerable, чтобы показать, что это действительно какая-то коллекция); это позволяет гибко добавлять отдельные элементы, пары ключ/значение и т.д.
  • LINQ (Select и т.д.) - таким образом LINQ достигает своей гибкости, позволяя использовать один и тот же формат выражения запроса для нескольких типов без изменения IEnumerable<T>
  • Ожидающие выражения С# 5 требуют, чтобы GetAwaiter возвращал тип awaiter с IsCompleted/OnCompleted/GetResult

В обоих случаях это облегчает добавление этой функции к существующим типам и интерфейсам, где концепция ранее не существовала.

Учитывая, что IDisposable находится в каркасе с самой первой версии, я не думаю, что в утике, использующей инструкцию using, будет какая-то польза. Я знаю, что вы явно пытались оспаривать причины отсутствия Dispose без реализации IDisposable из обсуждения, но я считаю это решающим моментом. Должны быть веские причины для реализации функции на языке, и я бы сказал, что утиная типизация - это функция, которая выше и выше поддерживает известный интерфейс. Если в этом нет явной выгоды, это не будет на языке.

Ответ 2

Там нет курицы и яйца: foreach может зависеть от IEnumerable, так как IEnumerable не зависит от foreach. Причиной foreach разрешено на коллекции, не реализующие IEnumerable, вероятно, в значительной степени исторический:

В С# это не является строго необходимым для класса коллекции, наследуемого от IEnumerable и IEnumerator в порядке быть совместимым с foreach; как долго поскольку класс имеет необходимый GetEnumerator, MoveNext, Reset и Текущие участники, он будет работать с для каждого. Опуская интерфейсы, преимущество, позволяющее вам определить тип возвращаемого тока быть более конкретным, чем объект, тем самым обеспечивая безопасность типов.

Кроме того, не все проблемы с курицей и яйцом на самом деле являются проблемами: например, функция может вызывать себя (рекурсия!) или ссылочный тип может содержать себя (например, связанный список).

Итак, когда using пришел, почему бы им использовать что-то столь же сложно, как указывать в качестве утиной, когда они могут просто сказать: реализовать IDisposable? В основном, используя утиную печать, вы выполняете контур вокруг системы типов, что полезно только тогда, когда система типов недостаточно (или непрактична) для решения проблемы.

Ответ 3

Вопрос, который вы задаете, - это не курица и яйцо. Его больше похоже на то, что компилятор языка реализован. Подобно компилятору С# и VB.NET реализованы по-разному. Если вы пишете простой код мира hello и скомпилируете его как с компилятором, так и с проверкой кода IL, они будут разными. Возвращаясь к вашему вопросу, я хотел бы объяснить, какой IL-код генерируется компилятором С# для IEnumerable.

IEnumerator e = arr.GetEnumerator();

while(e.MoveNext())
{
   e.Currrent;
}

Итак, компилятор С# исправлен для случая foreach.