Утка, набирая в компиляторе С#
Примечание Это не вопрос о том, как реализовать или эмулировать утиную печать на С#...
В течение нескольких лет у меня создалось впечатление, что некоторые функции языка С# не зависят от структур данных, определенных в самом языке (который всегда казался мне странным сценарием курица и яйца). Например, у меня создалось впечатление, что цикл foreach
доступен только для использования с типами, которые реализовали IEnumerable
.
С тех пор я понял, что компилятор С# использует утиную типизацию, чтобы определить, можно ли использовать объект в цикле foreach, ища метод GetEnumerator
, а не IEnumerable
. Это имеет большой смысл, поскольку он удаляет куриную и яичную головоломку.
Я немного смущен, почему это не похоже на блок using
и IDisposable
. Есть ли какая-то особая причина, по которой компилятор не может использовать утиную печать и искать метод Dispose
? В чем причина этой несогласованности?
Возможно, что что-то еще происходит под капотом с IDisposable?
Обсуждая, почему вы когда-либо имели объект с методом Dispose, который не реализовал IDisposable, выходит за рамки этого вопроса:)
Ответы
Ответ 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
.