Нэш Трей - C# 2010. Ускоренный курс для профессионалов (2010) (1160865), страница 28
Текст из файла (страница 28)
Другими словами, это доступная только для чтения ссылка на объект в методах класса. Однако с типами значений ЬЫз ведет себя кан обычный параметр гег. В конструкторах экземпляра, которые не имеют инициализатора, значение ГЫя ведет себя как параметр опс. Это значит, что в действительности ГЫя присваивается значение, нак показано в следующем примере: рпЬ11с ясгпсс Совр1ехипвпег рсЬ11с Совр1ехиивЬег( оопЬ1е геа1, оосЬ1е 1вас1пагу ) ( гьгя.геа1 = геа1; гЫя.1вадупагу = 1васгпагу~ ) рпЬ11с Совр1ехнпвЬег( Совр1ехмивпег оспег ) ( ГЬгя = оспег; ) рггчаге сопЬ1е геа1; рггчаге оопЫе гвадгпагу; ) рпЫ1с с1аяя Епггууогп ( ясасгс чогб Маго() ( Совр1ехмивЬег ча1А = печ Совр1ехмпвЬег( 1, 2 ); Совр1ехкпвпег соруА = печ Совр1ехнсвЬег( ча1А )у ) ) Обратите внимание, что второй конструктор принимает в качестве параметра другое значение Совр1ехмпвЬег. Этот конструктор ведет себя подобно копирующему конструктору в С++.
Но вместо присваивания наждого поля индивидуально можно просто выполнить присваивание СЫ я, что обеспечит копирование состояния параметра в единственной строне кода. В этом случае юпочевое слово ЬЫя действует как параметр опг. Вспомните, что параметры опс ведут себя подобно параметрам гег, но с одним специфическим отличием. Когда параметр помечается как осе, компилятор знает, что значение не инициализировано в точке, где начинается выполнение тела метода. Поэтому компилятор должен удостовериться, что каждое поле значения инициализировано перед тем,как произойдет выход из конструктора. Например, рассмотрим следующий код, который не компилируется: риЬ11с ясгисс Совр1ехкпвпег ( рпЬ11с Ссвр1ехмчвЬег( оопЬ1е геа1, с(ооЬ1е 1васгпагу ) гЫя.геа1 = геа1; якая.
1васгпагу = 1васгпагу; ) рпЫ1с Совр1ехмовЬег( оопЬ1е геа1 ) ГЫя.геа1 = геа1) рггчаге с(опЬ1е геа1; рггчаге оопЫе 1взс1пагу; Классы, структуры и объекты 101 Проблема связана со вторым конструктором. Поскольку типы значений обычно создаются в стеке, распределение пространства под такие значения просто требует изменения указателя стека. Конечно, распределение такого рода ничего не говорит о состоянии памяти. Дело в том,что память, зарезервированная в стеке для значения, содержит случайный мусор. Среда СЬК могла бы инициализировать эти блоки памяти нулями, но это свело бы на нет добрую половину преимуществ, связанных с типами значений.
Типы значений задуманы как легковесные и быстрые. Если С1.К придется инициализировать нулями стековую память для типов значений при каждом резервировании памяти для них, то это будет достаточно длительной операцией. Конечно, конструктор по умолчанию без параметров. сгенерированный системой, именно это и делает. Но его необходимо вызывать явно, создавая экземпляр с помощью ключевого слова пеы. Поскольку в конструкторах экземпляра ключевое слово ЬЬ1з трактуется как оис-параметр, конструктор экземпляра должен инициализировать каждое поле значения перед тем, как завершить свою работу.
И это обязанность компилятора СЗ, от которого ожидается, что он сгенерирует верифицируемый и безопасный в отношении типов код. Вот почему предыдущий пример кода приводит к ошибке компиляции. Несмотря на то что конструкторы экземпляров типов значений не могут использовать ключевое слово Ьазе для вызова конструкторов базового класса, они имеют инициализатор. Инициэлизатору разрешено использовать ключевое слово СЬ1з, чтобы вызывать другие конструкторы той же структуры во время инициализации.
Поэтому в код предыдущего примера можно внести одно небольшое изменения, чтобы он начал компилироваться: рпЬ11с зггпсг Совр1ехкивЬег риЫгс Спвр1ехысвЬег ( попЫе геа1, с(спЫе гвачтпагу ) ( спал.геа1 = геа1т гп1з. 1вадапагу = 1вас1пагу; рпЫ1с Совр1ехкпвЬег ( оопЬ1е геа1 пеЬ1з( геа1, О ( спал.геа1 = геа1( ) рггчаге с(спЬ1е геа1( рг1чаге оссЫе 1вап1пагут ) рпЫас с1азэ Епсгурсапс ( зсагас чсгд Мз1п О ( Совр1ехкпвЬег ча1А = пев Ссвр1ехыэвЬег( 1, 2 )т ) Обратите внимание на отличие во втором конструкторе.
К нему добавлен инициэлизатор, который вызывает первый конструктор. Хотя единственная строка кода во втором конструкторе является излишней, она оставлена, чтобы подтвердить мысль. Она только присваивает значение геа1, как и в предыдущем примере, но компилятор уже не жалуется. Это объясняется тем, что когда конструктор экземпляра содержит инициэлизатор, ключевое слово СЬаз ведет себя в теле конструктора как параметр тес, а не как параметр опс.
И, поскольку зто тес-параметр, компилятор может предположить, что значение было инициализировано правильно перед входом в блок кода метода. 102 Глава 4 По сути, бремя инициализации возлагается на первый конструктор, чья обязанность— гарантировать инициализацию всех полей значения. Н последний момент, о нотором следует помнить: несмотря на то, что система автоматически генерирует стандартный инициализатор без параметров, вызывать его через ключевое слово ЬЬ1з нельзя. Например, следующий код не скомпилируется: роЬ11с эсгссс Совр1ехкотьег раЬ11с Сопр1ехиивьег) боапае геа1, боиЬ1е ввад1пагу ) ( Гкал.геа1 = гаа1; гпьз.упадаоагу = 1пао1оагу; ) раЬ11с Сопр1ехновпег ( боиЬ1е геа1 ) : ЬЬ1 () ( ЬЬ1э.геа1 = геа1; рг1чаге боаЬ1е геа1; ргугасе бооЬ1е 1вадупагу; ) Если бы в структуре. имеющей относительно немного полей, понадобилось бы инициализировать все поля нулями или пп11-ссылками, кроме одного, то это позволило бы несколько сэкономить на вводе кода.
Но, увы, компилятор подобное не позволяет. Финализаторы Типам значений не разрешено иметь финалиэатор. Концепция финализации, или недетерминированного уничтожения, зарезервирована для экземпляров классов, или объектов. Если бы структуры имели финализаторы, то исполняющая система должна была бы управлять вх вызовом при каждом выходе значения из области определения. Будьте осторожны с инициализацией ресурсов внутри конструкторов типов значений. Просто не выполняйте ее. Рассмотрим тип значения, имеющий поле — некоторый дескриптор низкоуровневого системного ресурса. Предположим, что этот низкоуровневый ресурс распределен, или затребован.
в специальном конструкторе, принимающем параметры. В результате возникает несколько проблем, которые понадобится решить. Поскольку создать конструктор по умолчанию, не имеющий параметров, не разрешено, каким образом затребовать ресурс, когда пользователь создает экземпляр этого значения, не используя специальных нонструкторову Ответ:никак! Вторая проблема связана с отсутствием автоматического триггера для очистки и освобождения ресурса, поскольку нет финализатора. Необходимо нак-то заставить потребителя значения вызвать специальный метод для очистки до того, нак значение выйдет за пределы контекста.
Наличие требования к потребителю не забыть сделать что-нибудь, чтобы избежать утечки ресурсов, свидетельствует о неправильном дизайне. Интерфейсы Хотя структуре не разрешено наследоваться от некоторого класса, она может реализовывать интерфейсы. Поддерживаемые интерфейсы перечисляются точно так же, как у классов, в списке базовых интерфейсов после идентификатора структуры. Обычно поддержка интерфейсов для структур — это то же самое, что поддержка интерфейсов для нлассов.
Тема интерфейсов подробно раскрывается в главе 5. Реализация интерфейсов структурами влияет на производительность, поскольку подразумевает выполнение Классы, структуры и объекты 103 операций упаковки при вызове методов через интерфейсную ссылку на экземплярах структур. Об этом подробно рассказывается в главе 13. Анонимные типы Насколько часто у вас возникала потребность в легковесном классе, хранящем несколько взаимосвязанных значений для использования внутри определенного метода, и вы стонали от необходимости вводить целое определение типа, с приватными полями и общедоступными свойствами7 Так обратитесь к анонимным типам! СВ позволяет вводить такие типы, исполъзуя неявно типизированные локальные переменные вместе с расширенным синтаксисом операции лен.
Давайте посмотрим, на что это похоже: пя1по Яуясеик рп)т11с с1яяя Ептгуротпк ( якят1с чоуб Мягп() чяг етр1оуее1пго = лен ( Кане = апое", 1г) = 42 ); чяг сияковег1пго = пен ( Кяве = " Юзпе", 1г) = "ЛВ123" ); Сопяо1е.ыгггеъупе( "Значение аяте: (0], значение 1сы (1)" евр1оуее1пго.няве, евр1оуее1пго.1о )) Сопяо1е.нггкеъупе( "Тнп евр1оуее1пго: (0)", еар1оуее1пго.оегтуре() ); Сопяо1е.нгукеввпе( "Тнп счятовег1пго: (0)", спясотег1пго.сеттурЕ() ); Обратите внимание на интересный синтаксис в скобках после ключевого слова пен внутри объявления еар1оуее1пЕо.
С помощью пар "имя/значение" объявляются имена свойств внутри анонимного типа, и эти свойства инициализируются заданными значениями. В данном случае создаются два анонимных типа. каждый из которых содержит два свойства. В первом анонимном типе первое свойство имеет имя маше и относится к типу Буясеа. Бсгупд, а второе — вмя 14) и относится к типу Бузсев.
1пс32. Важно отметить, что лежащий в основе тип экземпляра созданного подобным образом является строгим типом, но его генерирует компилятор, поэтому имя данного типа неизвестно. Но как видно из следующего вывода, полученного в результате выполнения приведенного выше кода, имя этого типа можно узнать: Значение Няне: Юое, значение 1сы 42 тнп евр1оуее1пео: <>е лпопувоиятуреО'2(яуясет.ягг1пз,яуясеа.1пс32) Тнп счятовег1пго: <>Е ЛпопуаспятуреО'2[Яуякев.зтг]па,зуясев.зтг1пс] На заметку! Имена сгенерированных компилятором типов специфичны для реализации, поэтому никогда не следует полагаться на них. Вдобавок, как вы заметили, они являются "недопустимыми" для компилятора; если вы попытаетесь объявить экземпляр, указав такое имя типа, то компилятор сообщит о синтаксической ошибке.












