Утка набирает в C # - PullRequest
       23

Утка набирает в C #

61 голосов
/ 16 июня 2011

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

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

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

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

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

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

Ответы [ 3 ]

39 голосов
/ 16 июня 2011

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

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

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

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

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

12 голосов
/ 16 июня 2011

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

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

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

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

0 голосов
/ 16 июня 2011

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

IEnumerator e = arr.GetEnumerator();

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

Так что компилятор C # настроен для случая foreach.

...