Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 31
Текст из файла (страница 31)
Когда происходит упаковка Поскольку С№ обрабатывает упаковку неявно, важно знать случаи. когда С№ упаковывает значение. В основном значение упаковывается во время выполнения следующих преобразований: ° преобразование типа значения в объектную ссылку; ° преобразование типа значения в ссылку на Буягегл. Ча1петуре; ° преобразование типа значения в ссылку на интерфейс, реализованный этим типом значения; ° преобразование типа еппм в ссылку на Яуясен.
Епппг В каждом случае преобразование обычно принимает форму выражения присваивания. Первые два случая довольно очевидны, поскольку С).В здесь заполняет зазор между двумя категориями типов, превращая экземпляр типа значения в ссылочный тип.
Третий случай может показаться несколько неожиданным. Всякий раз, когда значение явно приводится к поддерживаемому им интерфейсу, происходит упаковка. Рассмотрим следующий код: риЫ1с 1псеггасе 1Ргьпт. чо1д Рг1пс(); ) рпЬ11с ясгпсс Мууя1пе: 1Ргтпс ( рпЫтс гог х; рпЫтс чога Ргьпс() ( Зуясет.оопзо1е.нг1се11пе( "(О)", х ); ) рпЫ1с с1яяя Епсгуро1пс ( ясасгс чотс( Мвтп() Мууа1ие тучя1 = пен Мууа1пе () жучя1.х = 1232 // нет упаковки ауча1.рг1пс О; // нужно упаковать значение 1Ргтпг ргьпсег = аучя12 ргьпсег.ргтпс(); Классы, структуры и объекты 113 Первый вызов Ргапс выполняется через ссылку на значение, что не требует упаковки.
Однако второй вызов Рг1пг осуществляется через интерфейс. В точке получения интерфейса происходит упаковка. Первым делом это все выглядит так, будто без упаковки можно было бы обойтись, если применять явную ссылку на тип интерфейса. В данном случае это верно, поскольку Рг1пг также является частью общедоступного контракта МуЧа1пе. Однако если реализовать метод Ргапс как явный интерфейс, о чем речь пойдет в главе 5, то единственным способом вызова метода будет применение ссылки на интерфейсный тип.
Поэтому важно отметить, что всякий раз, когда вы реализуете интерфейс на типе значений явно, то тем самым вынуждаете клиентов типа значений упаковывать его перед вызовом через этот интерфейс. Сказанное демонстрируется в следующем примере: рпьагс гпсегтасе 1Рггпт ( чсгб Рг1пс(); ) рпЬ11с всгпсс МУЧа1пе: 1Ргзпт ( рпЬ11с ьпс хк чогб 1РгапГ.Рггпс() ( Бункера.Сопзо1е.иг1сеъгпе ( "(О) ", х ); ) ) рпЬ11с с1азв ЕптгуРс1пс ( втатас чо1г) Маги() ( МуЧа1пе туча1 = пен МУЧа1пе (); иуча1.х = 123; г'г' нужно упаковать значение 1Ргапт рггпгег = иуча1г рггпсег.Рг1пг(); В качестве другого примера рассмотрим поддержку типом яузселг.
1пс32 интерфейса 1СопчегсаЬ1е. Большинство методов интерфейса 1СопчегсзЬ1е реализовано явно. Поэтому, даже если вы хотите вызвать метод 1СопчегсаЬ1е, такой как 1сопчегс1Ь1е. Товоо1еап, на простом гпс, то сначала должны упаковать его. На заметку! Обычно для выполнения преобразований, подобных упомянутым ранее, вы будете полагаться на внешний класс яузгелг. сопчегс. Вызов через 1сопчеггаь1е описан только в качестве примера, Эффективность и путаница Как и можно было ожидать, упаковка и распаковка — не самые эффективные операции в мире. Еще хуже то, что компилятор С№ молча выполняет эти операции.
Вам действительно нужно знать о том, когда происходит упаковка. Распаковка, как правило, более явная, поскольку обычно приходится выполнять операцию приведения для извлечения значения из упаковки, хотя бывают и случаи неявной распаковки, которые будут описаны далее. В любом случае должно быть уделено внимание аспекту эффективности. 114 Глава 4 Например, рассмотрим контейнерный тип. подобный Бузтегл. Оо11естзоп. АггауЬз.зт. Все значения он хранит в виде ссылок на тип оЬ1еск. Если требуется вставить в него множество типов значений, то все они будут упакованы! К счастью, обобщения, которые появились в С№ 2.0 и .НЕТ 2.0 и описаны в главе 11, помогут преодолеть эту неэффективность.
Однако всегда необходимо помнить о том. что упаковка — неэффективная операция, которой следует избегать, где толъко возможно. К сожалению, поскольку упаковка — неявная операция в С№, ее обнаружение требует острого глаза. Наилучший инструмент, который можно для этого применить, если возникают сомнения в ее наличии или отсутствии — зто ПЛАЗМ. С помощью 1ЬРАЗМ можно просмотреть код 1Ь, сгенерированный для методов, и легко обнаружить там операции упаковки.
Утилита 11.ОАБМ. ехе находится в папке \Ьап комплекта .НЕТ ЗРК. Как упоминалось ранее, распаковка — обычно явная операция, происходящая во время приведения от ссылки на объект упаковки к типу упакованного значения. Тем не менее, в одном случае распаковка все-таки бывает неявной. Вспомните, что говорилось об отличии в поведении ссылки Спзз внутри методов классов и внутри методов структур.
Ртавное отличие в том. что для типов значений ссылка СЬз.з действует нак параметр гег или оис — в зависимости от ситуации. Так что при вызове метода на типе значения скрытый параметр СЬ№з внутри метода должен быть управляемым указателем, а не ссылкой. Компилятор легко справляется с этим, когда вызов осуществляется непосредственно через экземпляр типа значения. Однако при вызове виртуального метода или метода интерфейса через упакованный экземпляр, т.е. через объект, среде СЬК приходится распаковывать экземпляр значения, чтобы получить управляемый указатель на тип значения, содержащийся в упаковке. Когда вызываете методы значения через упаковочный объект, не забывайте о скрытых операциях распаковки. На заметку1 Операции распаковки в СЬН неэффективны сами по себе.
Эта неэффективность происходит из того факта, что С№ обычно комбинирует операцию распаковки с операцией копирования значения. Класс Вув~ев. ОЬ~ ест Каляный объект в СЬК наследуетсн от Буз сепг. Оьб ест — базового типа для всех других типов.
В С№ ключевое слово оЬБесс представляет собой псевдоним Бузсев. Оьуесг. То, что каждый тип в СЬК и С№ наследуется от Оьйесс, может оназаться удобным. Например, коллекцию акземпляров разных типов можно трактовать как однородную, приведя их все к ссылкам на ОЬ1 ест. Даже Бузтегл. Чз1цетуре наследуется от ОЬБест. Однако получение ссылки на ОЬБесс регулируется некоторыми специальными правилами. На ссылочных типах можно преобразовывать ссылку на класс А в ссылну на класс Оьуест простым неявным преобразованием. Проход в обратном направлении требует проверки типа во времн выполнения и явного приведения с использованием хорошо знакомого синтаксиса— предваряя преобразуемый экземпляр именем нового типа в скобках.
Прямое получение ссылки ОЬБесс для типа значения формально невозможно. Семантически это оправдано, поскольку типы значений расположены в стеке. Выло бы опасно получить ссылку на кратковременно существующий экземпляр значения и сохранить ее для последующего использования, когда этот экземпляр значения уже, вероятно, исчезнет. По этой причине получение ссылки на ОЬбест для типов значений сопрнжено с операцией упаковки, как было описано в предыдущем разделе. Классы, структуры и объекты 115 Определение класса Яув сев.
ОЬ) ест выглядит так: рцЬ11с с1ввв ОЬ)есг ( рпЬ11с ОЬ)ест (); риЫ1с чзггцв1 чога Гзпв11ве() рпЫЕс чуггпа1 Ьоо1 Ет(па1в( оЬ)ест оЬ) ): риь11с вгвгзс ъоо1 ечпв1в( оь)ест оь)1, оь)ест оь)2 ); рцЫЕс чзгспв1 1пс Оекнввпсоое(); риЬ11с Туре Оектуре О к ргогесгег( оъ)есг мевьегну*ес1опе () к рцЬ11с вгвгзс Ьоо1 ЕегегепсеЕЧив1в( оЬ)есг оЪ)1, оЬ)ест оЬ)2 ); рцЬ11с чзггов1 всг1по Тозсгупс(); Оь)ест предоставляет ряд методов, которые проектировщики се!/с1л сочли важными и подходящими для каждого объекта. Методы, имеющие дело с эквивалентностью, требуют отдельного рассмотрения; они будут описаны в следующем разделе. В ОЬ)ест предусмотрен метод Оестуре для получения типа времени выполнения любого объекта, работающего в СЬК.
Возможность определения типов в системе во время выполнения в сочетании с рефлексией чрезвычайно удобна. Оесууре возвращает объект типа Туре, который представляет реальный, или конкретный, тип объекта. Используя этот возвращенный объект, можно узнать все о типе объекта, с которым был вызван метод Оестуре. К тому же, имея две ссылки на тип Оь)ест, можно сравнивать результат вызова Оегтуре для них обоих, чтобы узнать, являются ли они экземплярами одного и того же типа. Яувгем. Оь)ест содержит метод по имени мепьегнуяес1опе, возвращающий неглубокую копию объекта.
Он подробно рассматривается в главе 13. Когда метод МемЬе гну веС1опе создает копию, все поля типов значений копируются бит за битом, в то время как все поля ссылочного типа просто копируются, так что и оригинал, и копия содержат ссылки на один и тот же объект. Когда вы хотите получить копию объекта, такое поведение не обязательно устроит. Поэтому если объекты поддерживают копирование, можно подумать о поддержке 1С1опевЬ1е и предпринять необходимые действия в реализации интерфейса. Обратите также внимание, что метод МемЬегн1веС1опе объявлен как ргосессес(. Йтавная причина состоит в том, что только класс копируемого объекта может вызвать его, поскольку МемЬегнуяеС1опе может создать объект без вызова его конструктора экземпляра.
Такое поведение потенциально могло бы стать дестабиливирующим, если сделать метод общедоступным (рпЬ11с). Ма заметку! Прежде чем принимать решение касательно реализации интерфейса 1С1опевЫе, узнайте о нем больше в главе 13. Четыре из методов ОЬ) есг являются виртуальными, и если их реализация в ОЬ) есг по умолчанию не подходит, ее можно переопределить. Метод Тозсг1пд удобен для генерации текстового, читабельного для человека вывода и строкового представления объекта. Например, во время разработки может понадобиться возможность трассировки объекта в отладочном выводе во время выполнения. В таких случаях имеет смысл переопределить Тозгг1по, чтобы он предоставлял детальную информацию об объекте и его внутреннем состоянии.















