Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 149
Текст из файла (страница 149)
Очень легко заметить, к каким свойствам производится обращение, и какие методы вызываются. Как видите, тип бупав№с приносит много пользы и повышает читабельность кода в ситуациях, требующих взаимодействия. Как работает тип йупашл.с? В чем заключается секрет этой магииУ Несмотря на то что с1упав№с — зто настоящий статический тип в языке С№, на уровне С|В компилятор транслирует экземпляры с1упавта в экземпляры оЬ1 ест с присоединенным к ним атрибутом.
Чтобы проиллюстрировать это, рассмотрим следующий код, который не скомпилируется: с1аяя С ( /7 Это компилироваться не будет!!! чобб Роо( оЬ7'еат о ) ( ) чотб Роо( бупав1с б ) 1 ) ) Попытка скомпилировать этот код приводит к выдаче следующей ошибки компиляции: еггог СБ0111: Туре 'С' а1геабу бе61пея а вевЬет са11еб 'Гоо' нттЬ тпе заве ратаветег турея ошибка СЯ0111: В типе С ухсе определен член по имени Рос а теми же самыми типами параметров 560 Глава ) 7 Таким образом.
в отношении разрешения перегрузки типы с(упасв1с и оЬ) есг эквивалентны. Теперь, чтобы увидеть атрибут в действии, попробуйте сномпнлировать следующий код в библиотечную сборку: с1ввв С ( вогб Гоо( с)упвн1с с( ) ( ) ) Этот код проще всего скомпилировать в командной строке с использованием следующей команды, в которой <иия Файла> заменяется именем файла с кодом С№: свс /Свгоес:11Ьгагу <имя ()вйлв> После компиляции загрузите полученную сборку в Кейес1ог и просмотрите код, реализующий класс. На момент написания книги средству Кейесгог пока ничего не было известно о новом типе с(упаньс, потому код класса С выглядит следующим образом: ьпсегпв1 с1ввв С // Мвспос(в рпЬ|ьс С(); ргьввсе вогб Гоо([Рупвньс) оЬ)есс с)); Как видите, компилятор присоединил атрибут РупатьсдгсгьЬпге к параметру с), чтобы указать на то, что этот параметр на самом деле относится к типу с(упатьс.
Как упоминалось в предыдущем разделе, когда компилятор встречает динамические типы, он отклалывает завершение своей работы до момента выполнения. По сути, динамические типы и динамические выражения не видны компилятору. Поэтому номпилятор собирает всю известную ему информацию и генерирует то, что называется меспюм динамического вызова (бупапз(с сай з(1е). Во время выполнении, когда вся информация о типах доступна, средство принязки времени выполнения С№ вместе с РЬК завершают работу по разрешению динамических типов и выполняют необходимые операции. Естественно, это значит, что система типов компилятора до известной степени обходится во время компиляции.
В действительности средство привязки времени выполнения С№ содержит подмножество функциональности компилятора. Когда это средство должно сгенерировать исключение, сообщение об ошибке будет тем же самым, что и в компиляторе. Эта унификация на самом деле помогает, когда необходимо диагностировать проблему во время выполнения, поскольку вы имеете дело со знакомыми ошибками. Чтобы проиллюстрировать это утверждение, рассмотрим следующий код, который не скомпилируется: с1ввв С рпЬ11с во1с) Гоо() () ) вгвггс с1авв Впсгуроьпг ( вгвгьс вогд Мв1п() ( С оЬ) = пев С(); оЬ).ввг(); ) ) Как и можно было ожидать, возникнет ошибка компиляции, которая выглядит так: Динамические типы 561 еггог СБ1061: 'С' боев поС соптагп а бесспсС]оп Рог 'Ваг' апб по ехСепз1оп шеСЬоб 'Ваг' ассерСгпд а Гсгзп агдцшепС оТ Суре 'С' соп1б Ье Топпб (аге уоо ш1зз]пд а озгпд бггессгче ог ап аззещЬ1у гесегепсе?) ошибка С$1061: С не содержит определения Ваг, и не обнаружены расширяющие методы Ваг, которые принимают первый аргумент типа С (возможно, пропущена директива иягпд или ссылка на сборку) Теперь рассмотрим приведенный ниже пример, который компилируется, поскольку в нем для хранения экземпляра С используется тип с)упаш1с: с1азз С ( рихугс чогб Гоо() () ) згаггс с1азз ЕпггуРогпг зкатгс чо1б Магп() ( бупащсс бупоЬО = пен С() бупоЬО.Ваг()1 ) В этом случае выдача ожидаемой ошибки откладывается до времени выполнения— после запуска этого кода на консоли будет следующий результат: пппапб1еб Ехсерггоп: МссгозоСС.СБЬагр.яопСстеВ]сбег.яопС]шеВ1пбегххсергсоп: 'С' боев поС соптатп а беЕСпгС1оп Рог 'Ваг' аС Са11$1Се.тагдеС(С1озпге, Са11Ягте, ОЬ)есг ) аС Яузтее.Рупаш1с.прбагере1едагез.0рбакехпбЕхесотедогб1[ТО](Са11Ягге згге, ТО агдО) аС ЕптгуРогпС.Мага() Необработанное исключение: М]сгозоТС.СЯПагр.липябяеВ]лбег.липятзе$1пбегЕхсерС]ол: С не содержит определения Ваг в Са11$1Се.ТагдеС(С1озиге, Са11$]Се, РЬуесг ) в Яузгев.Рупав]с.РрбагеРе1едагея.РрбагеллбЕхесияеуогб1(ТО](Са11$]ге я]ге, ТО агдб) в ЕлггуРсглг.на1л() На заметку! Некоторое отличие между сообщением исключения и сообщением ошибки компиляции действительно имеется.
Причина в том, что тип бупашгс в настоящее время не поддерживает расширяющие методы. Замечательная унификация По-настоящему ценным тип с)увалы'. с делает унификация различных способов вызова типов, реализованных различными динамическими языками и технологиями. При написании кода на СЗ 4.0 беспокоиться об источниках динамического объекта не придется. Ниже перечислены примеры таких источников: ° объект из языка на основе 1)ЬН, такого ках [гопрутЬоп или 1топйпЬу; ° СОМ-объехт с поздним связыванием, реализующий только интерфейс 1Р1 яра с сЬ; ° объект. реализующий 10упашгсмесаСЬ]ессргочгбег (см.
раздел "Объекты со специальным динамическим поведением" далее в главе); ° традиционные статически типизированные объекты . Р)ЕТ. 562 Глава (7 Для объектов .Р)ЕТ в месте вызова для привязки к соответствующему члену используется рефлексия. Если объект представляет собой СОМ-объект, его рефлексия осуществляется через объект КСтд, который действует как иронси-объект .МЕТ к СОМ-объекту. Для целей привязки объект КСЮ транслирует операции рефлексии в соответствующие операции 101ярагсЬ. Если экземпляр динамического объекта реализует интерфейс 1РупаттсметаОЬ3есСРгочтбег, то среда П).К использует его при выполнении привязки. Интерфейс 10упзаъсмесаОЬ3есгргоч1бег является мощным механизмом расширения для реализакии специального динамического поведения.
Места вызовов В приведенных до сих пор примерах демонстрировался только доступ к членам экземпляра с1упааъс, те. с использованием динамического приемника. Противоположностью динамического приемника является статический приемник, который используется для доступа к членам через статически типизированную переменную. С точки зрения реализации это, вероятно, самая простая из всех динамических операций, поскольку компилятору практически ничего о ней не известно, так как динамический приемник ему не виден. Поэтому компилятор упаковывает всю известную ему информацию и создает то. что называется местом вызова.
Место вызова имеет целевой делегат, который вызывается для обработки динамического вызова. Таким образом, место вызова служит своего рода порталом в среде ПЬК. На заматку) Если интересно исследовать внутреннее устройство мест вызовов, воспользуйтесь (ЮЯЗМ, чтобы заглянуть во внутренности скомпилированной сборки, использующей тип бупаатс, Если вы не фанат Ц попробуйте открыть скомпилированную сборку в небес(ог, что позволит увидеть места вызовов на СЗ.
Это также даст представление о сложности работы, выполняемой компилятором. Почему говорилось, что обращение к членам экземпляров с1упалп'.с выглядит просто для компиляторад Рассмотрим следующий пример; сяяпд яуягеа) с1аяз С ( чозб Ргосеяя1прик( 1пя х ) ( Сопяо1е.нгаяа11па( "1птп " ч х.Тоэегтпд() )1 ) рсв11с чоха Ргосеяя1прсе( зяг1пд взд ) ( сопяо1е.игтсвь1пе ( "яггтпд: " ч п1яд ) ) РСЬ11с чотб Ргосяяя1прик( босЬ1з б ) ( Сопяо1е.иг1сеъапе( "бссЬ1а: " ч б.Тсятг1пд() ); ) ) зсастс с1зяя ЕпггуРотпт ( яса11с чотб Мазо() ( бупаа1с оЪ31 = 123; бупазгс оЬ32 = "Сз кос)ся!"; бупаясгс оЬ33 = 3.141тп С с = пен С(); с.Ргосааз1прсе( оЬ31 ); // ()1 с.Ргосвзз1прос( оЬ32 ); // ()2 булате сОЬ) = сс сОЬЭ.Ргосаяа1прое( оЬ53 ); // ()Э Динамические типы 563 Какой вывод можно ожидать от этого кодад В случае вызовов, помеченных в комментариях как №1 и №2, компилятору известно, что вызываются члены статического типа С через статический приемник.
Поэтому он может проверить наличие общедоступных членов с таким именем. Если их не окажется, возникнет ошибка компиляции. Однако компилятор не имеет понятия о том, как выполнить разрешение перегрузки, поскольку не имеет достаточно информации из-за того, что параметры относятся к непрозрачному типу с(упапп'.с. Таким образом, компилятор должен отложить разрешение перегрузки до времени выполнения. Для вызова №3 компилятор знает меньше, чем для вызовов №1 и №2, потому что вызов осуществляется через динамичесэий приемник. Запуск приведенного выше кода на выполнение дает следующие результаты: с)опЬ1е: 123 вгг1пд: С№ воске) боиЫек 3.1415 Обратите внимание, какие перегрузки были вызваны.












