Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 151
Текст из файла (страница 151)
В качестве еще одного примера: вспомните, что статические типы в СЗ допускают лишь одиночное наследование, т.е. разрешают повторное использование классом реализации только одного другого класса. Применяя специальные динамические типы, можно строить типы, которые эмулируют поведение агрегирования СОМ, с помощью которого несколько типов объединяются в один.
В языке С++, поддерживающем множественное наследование, для простого добавления функциональности к типу можно осуществлять наследование от так называемого смешанного класса. В СЗ такое поведение эмулируется за счет реализации специальных динамических типов. Эффективность Озабоченные проблемой производительности читатели могут предположить, что тип с(упзы1с представляет собой "узкое место" в плане эффективности, замедляя абсолютно все динамические выполнения в программе. Не стоит беспокоиться! Результаты каждого динамического вызова кэшируются в РЬК для последующего извлечения. Поэтому здесь ситуация такая же, как с ЛТ-компиляцией — затраты происходят при первом вызове.
Чтобы продемонстрировать это, рассмотрим приведенный ниже код, в котором используется черновой метод для хронометража множества обращений к методу в месте динамического вызова: нв1пд Яув»еи! цвгпч ЯувпеилщпГ1ие. Тпбегорбегч1сев; с1авв С рнЬ11с чо1б Ооког»() ( Сопво1е.кг1»е( "Выполняется работа... веаГ1с с1авв ЕпегуРо1пг ( [О111ирогп("»егпе132.б11 Ч 1 рг1чаге вгаггс ехгегп гпг ОнегуРеггогиапсесоцпгег( оц( 1пгб4 соцпс 1; вга»гс чотб Иагп() ( // Вызвать Оовог» один рав статически, чтобы выполнилась Я1т-компиляция.
С с = пен С (1; с.Ооиог»((' Динамические типы 567 сопво1е.игтгеьтпе() т бупавтс б = ст Тот( тпт г = 0; т < 10' ч+1 ) ( 1пт64 втвгт, епб; ОпегуретГогввпсесопптег( опт втагт )~ б.поиск)т(); ОпегуРегТогвапсесопптег( отт епб )~ Сопво1е.итттеьапе( "Тиков: (0)", епб — втагт ) Этот нод был скомпилирован и запущен на виртуальной машине под управлением %((пботчз 7 с использованием высокопроизводительного счетчика для оценки времени каждого динамического вызова. Приведенный ниже вывод ясно доказывает, что результаты динамического разрешения кэшируются для последующего использования. что экономит массу времени при последуютшпк динамических вызовах: Первый вызов Ооиот)т осуществлялся через статический приемник, чтобы выполнить ЛТ-компиляцию метода перед получением числовых значений.
Таким образом, первое значение тиков не отражает время ЛТ-компиляции. Упаковка с помощью типа с1уаап~с Упаковка относится к областям, которые таят в себе множество опасностей. Как всегда, когда упаковка присутствует в коде. следует проявлять особую осторожность. Но как с ней справляется тип бупавбсР Он делает все правильно, соответствуя самому духу кода. Рассмотрим следующий фрагмент: пв1п9 Бувтеп1( втаттс с1авв ЕптгуРагпт втвтгс тотб Ма1п() ( бупввбс б = 42; ++бт оЬ)ест о = 427 о = (тпт)о 4 1; Сопво1е.кг1те11пе( "б = " + б + "1по = " + о ) В данном примере есть экземпляр бупавбс, содержащий целочисленное значение. Внутренне это экземпляр о)73 ест, упаковывающий целое значение.
В случае бупав1с можно просто вызвать операцию инкремента для модификации значения внутри упаковки бупав1с. Сразу после этого показано, что понадобится сделать для выполнения Выполняется работа... Выполняется работа... Выполняется работа... Выполняется работа... Выполняется работа... Выполняется работа...
Выполняется работа... Выполняется работа... Выполняется работа... Выполняется работа... Выполняется работа... тиков: тиков: Тиков: Тиков: Тиков: Тиков: тиков: тиков: Тиков: Тиков: 83760 2161 1937 1858 1845 1981 1853 1834 1995 1887 568 Глава (7 той же операции на статическом упакованном экземпляре оЬ7 еег. По сути, необходимо скопировать значение из упаковки, увеличить его и поместить обратно. Нельзя просто применить операцию инкремента к переменной о.
Но когда операция инкремента используется с переменной б, компилятор через сгенерированные места вызовов выполняет ту же работу, которую нужно сделать для переменной о. Таким образом, применение типа с(упавус существенно упрощает нотацию. На заметку! Хотя тип бупав1с упрощает взаимодействив С упакованнЫмн ЗначЕниями, этО нв значит, что вы должны проявлять меньшую осторожность и не заботиться о влиянии упаковки.
Как обычно, только тот факт, что некоторое средство языка облегчает нечто, еще не означает, что всегда стоит им пользоваться. Динамические преобразования Работая над дизайном и реализацией типов бупаввс в СЗ 4.0, команда проектировщиков СЗ должна была найти решения для ряда весьма непростых проблем.
Вспомните, что в СЗ существует два вида преобразований — явные и неявные. Неявные преобразования не требуют синтаксиса приведения, в то время как явные — требуют. Кроме того, для любого типа можно определять специальные операции явных и неявных преобразований. Динамические выражения на самом деле добавляют целое новое измерение преобразований! Давайте рассмотрим для начала неявные преобразования. Какие типы должны быть неявно преобразуемыми в бупавас? Оказывается, что все типы неявно преобразуются в бупавас в соответствии с перечисленными ниже правилами. ° Существует неявное преобразование из любого ссылочного типа в тип бупавзс. ° Существует неявное упаковывающее преобразование из любого типа значений в тип бупавзс.
° Существуетнеявноепреобразованиеидентичностиизтипа бупавзс втип бупавзс. ° Существуетнеявноепреобразованиеидентичностииз типа оЬтесг в тип бупавзс. ° Существует неявное преобразование идентичности между сконструированными типами, у которых обобщенные аргументы отличаются только типами оь7 есг и с(упав1с. Этого следовало ожидать, поскольку описанные правила позволяют вьпюлнять ожидаемые вещи, вроде показанных в следующем коде: с1азэ С эгаг1с с1аээ Епггурогпг ( эгаггс тока Мазп() бупавгс б1 = 42; бупав1с б2 = пен С()! бупав1с бЗ = б2! бупав1с б4 = пен оЬ)есс()! неявное // неявное /? неявное // неявное упаковывэющее преобразование преобразование ссылки преобразование идентичности преобразование ссылки А как насчет преобразования в противоположном направлении? Должны ли экземпляры бупав1с быть преобразуемыми в любой тип? Определенно, для динамических Динамические типы 569 выражений имеет смысл быть явно преобразуемыми в статические типы.
Для целей обсуждения подумайте о динамическом выражении, в котором некоторый компонент вынуждает все обрабатываться динамически. Например, если переменная б является экземпляром бупапьс, то справа от знака равенства в следующем операторе будет динамическое выражение: ОопЬ1е бЬ1 = 3.14 + ьй Теперь, когда должно быть понятно, что подразумевается под динамическим выражением, рассмотрим следующий пример: с1азз С ( зкакьс с1азз ЕпкгуРо1пс ( зкак1с чо16 Мауп() ( бупзаас д = пан С(); // Явное приведение обратно к ссылке на С С сбь? = (С) бт /?' Что произойдет здесь??? зсгапс зкг = б; ) В примере создается новый экземпляр С, который неявно приводится к ссылке бупааас.
Затем выполняется явное приведение ссылки бупаюус, представленной коротким динамическим выражением, обратно к ссылке на С, что имеет смысл. Но не будет ли громоздким явное приведение динамического выражения к ссылочным типам и типам значений? Все это приведение может быть трудоемким, особенно если при этом используются длинные имена, что обычно бывает для обобщенных типов или типов, генерируемых ЫМЯ. Кроме того, все эти приведения противоречат духу динамического выражения, поскольку предполагается, что они должны упростить код, облегчая его чтение и понимание. А что означает последний оператор в приведенном выше коде? Оказывается, этот код нормально компилируется. В последнем операторе динамическое выражение б явно приводится к ссылке зггупЯ, что приведет к генерации во время выполнения следующего исключения: Вппапб1еб Ехсеркьоп: Мьсгозогк.СВЬагр.иппг1юеВ1пбег.иппгьюеВь аегЕхсергьоп: Саппог 1юр11сак1у сопчегк Суре 'С' Ко 'зсгьпд' Необработанное исключение: М1сгпзпГГ.СВЛагр.иипскюевкпбег.иипсктеВЕпбегЕхпергтопт Незозиоинп неявно преобразовать тип С в зсгупд На самом деле это очень интересно.
Компилятор выполняет явное приведение от бупамьс к зсг1пд,не требуя ввода нотации приведения. В реальности при преобразовании динамического выражения обратно к ссылке на С нотация приведения использоваться не должна. Загрузив скомпилированный код в Кебесйог, можно увидеть, что для выполнения явного преобразования применяется место вызова. Не правда ли, это нечто большее чем удобство? Неявные преобразования динамических выражений В команде проектировщиков СЕ поняли. что для того, чтобы динамические выражения были по-настоящему динамическими, они должны допуснать присваивания ссылочным типам и типам значений. Поэтому присваивание динамических выражений ссылочным типам или типам значений синтаксически неявно и выполняется динами- 570 Глава 17 чески во время выполнения.
Однако следует четко отличать динамические выражения от типа с(упа~1с. Тип Оупапьс не является неявно преобразуемым ни во что кроме с(упзт1с. В противном случае серьезно запуталось бы разрешение перегрузок методов, которое рассматривается в следующем разделе. На заметку! На определенном этапе разработки С№ 4.0 этот тип неявного преобразования из динамических выражений в ссылочные типы назывался преобразованием присваивания (авв(рпглеп! сопчегиоп). Этот термин часто встречается а статьях, написанных ао время разработки С№ 4.0. Динамические выражения вступают в игру в случаях, отличных от простого присваивания динамического выражения типу значения или ссылочному типу, и наоборот.
Если тело метода возвращает динамическое выражение, но объявление метода возвращает статический тип, используется преобразование динамического выражения для преобразования экаемпляра Оупамьс обратно в статически типизированный экземпляр. Есть и другие случаи (например, инициализация массива, блоки уье1О и операторы Гогеасл), в которых участвуют преобразования динамических выражений. Неявное преобразование типа г(упам1с к любому ссылочному типу или типу значений невозможно, потому что оно породило бы хаос. Чтобы понять — почему, взгляните на следующий фрагмент кода: с1ввв С ( вкакьс рып11с тока Гоо( 1пг 1 ) () всвкьс рпь11с то1г( Гоо( всг1по всг ) () с1эвв Р ( ) вквгьс с1авв ЕпггуРозпг ( вкагьс тока Мв1п() ( Оупавьс г( = пен Р() г С.гоо( О ); ) Если бы компилятор позволял типу с(упамьс быть неявно преобразуемым во все, что угодно, то при выборе нужной перегрузки С. Гоо, возникла бы неоднозначность.















