Г. Шилдт - С# 3.0 Полное руководство. 2010 (1160798), страница 69
Текст из файла (страница 69)
Наследование 357 Объект $0 типа МуС1аяя Объект $1 типа МуС1аяя Объект $2 типа МуС1аяя Упаковка и распаковка Как пояснялось выше, все типы в С№, включая и простые типы значений, являются производными от класса был есс. Следовательно, ссылкой типа был асс можно воспользоваться для обращения к любому другому типу, в том числе и к типам значений. Когда ссылка на объект класса оь1есс используется для обращения к типу значения, то такой процесс называется упаковкой. Упаковка приводит к тому, что значение простого типа сохраняется в экземпляре объекта, т.е.
"упаковывается" в объекте, который затем используется как и любой другой объект. Но в любом случае упаковка происходит автоматически. Для этого достаточно присвоить значение переменной ссылочного типа оьпесс, а об остальном позаботится компилятор С№. Распаковка представляет собой процесс извлечения упакованного значения из объекта. Это делается с помощью явного приведения типа ссылки на объект класса оьбесс к соответствующему типу значения. Попытка распаковать объект в другой тип может привести к ошибке во время выполнения. Ниже приведен простой пример, демонстрирующий упаковку и распаковку.
// Простой пример упаковки и распаковки. няъод Яуясепи с1аяя Вохъодпеюо ( япап1с тоги Ма10() ( 1пс х; оьбесс оьзп х = 10; овп = х; // упаковать значение переменной х в объект 1ов у = (1пс)оЬЗп // расдаковать значение из // объекта, доступного по ссылке оьз, // в переменную типа ъпс Соляо1е.нгътеъъпе(у); ) ) В этом примере кода выводится значение НЬ Обратите внимание на то, что значение переменной х упаковывается в объект 'простым его присваиванием переменной оЬп, ссылающейся на этот объект. А затем это значение извлекается из объекта, доступного по его ссылке ОЬ1, и далее приводится к типу 1ЬС. Ниже приведен еше один интересный пример упаковки. В данном случае значение типа 1ЬС передается в качестве аргумента методу Пцг (), который, в свою очередь, принимает параметр типа ОЬп ест.
// Пример Упаковки при передаче значения методу. чяуод Яуясепп 358 часть ). язык Сз с1азз ВохгпдОеюо ( зааагс чогс Магп() ( 1пс хГ х = 10; Сопзо1е.иг1Сесгпе("Значение х равно: " + х)~ // значение переменной х автоматически упаковывается // когда оно передается методу ЯЧг(). х = Вох1попеюо.ЯЧг(х); Сопзо1е.иггсеъапе("Значение х в квадрате равно: " + х); ) зсасгс гпс ячг(оьЗесс о) ( гевпгп (ьпв)о * (1пв)о) ! Вот к какому результату приводит выполнение этого кода: Значение х равно: 10 Значение х в квадрате равно: 100 В данном примере значение перемеииой х автоматически упаковывается при передаче методу Яс(г () . Упаковка и распаковка позволяют полностью унифицировать систему типов в С№.
Благодаря тому что все типы являются производными от класса оЬ1есС, ссылка иа зиачеиие любого типа может быть просто присвоена переменной ссылочиого типа оЬ1есС, а все остальное возьмут иа себя упаковка и распаковка. Более того, методы класса оьбесс оказываются доступными всем типам, поскольку оии являются производными от этого класса.
В качестве примера рассмотрим довольно любопытную программу: // Благодаря упаковке становится возможным вызов методов по значению! изхпч Яузвею) с1азз Меапопуа1ие ( звапгс чоап Маап О ( Сопзо1е.Иггвесгпе(10.тояпггпЧ()); ) В результате выполнения этой программы выводится значение 10. Дело в том, что метод тоЯСгйпд () возвращает строковое представление объекта, для которого ои вызывается. В данном случае строковым представлением значения 10 как вызывающего объекта является само значение 10) Класс оЬ5ее~ как универсальный тип данных Если оЬбесс является базовым классом для всех остальиых типов и упаковка зиачеиий простых типов происходит автоматически, то класс оЬ1есс можно вполне использовать в качестве "универсального" типа данных.
Для примера рассмотрим программу, Глава (). Наследование 359 в которой сначала создается массив типа ОЬ3 есС, элементам которого затем присваива- ются значения различных типов данных; // Испольэовать класс оЬ]есС для создания массива "обобщенного" типа. цяхпд Буясещ; с1аяя пепегтспещо ( япаетс чотб Матп() ( оь)есС[] да = пен оЬ]есС[10]' // Сохранить целые значения. бог(тпС 1=ел 1 < 3; 1т+) да [1] = 1л // Сохранить значения типа боцЬ1е. тог(тпс 1=3; 1 < бл 1ьь) да [1] = (боцвте) 1 / 2; // Сохранить две строки, а также значения типа Ьоо1 и спас.
да[61 = "Привет"; да[71 = Сгце; да[81 = 'Х'; да[91 = "Конец"; бог(тпС 1 = О; 1 < да.ьепдСЬл 1++) Сопяо1е.иг1сеьспе("да(" + 1 е "1: " + да[1.1 + " "); ! ) Выполнение этой программы приводит к следующему результату: Как показывает данный пример, по ссылке иа объект класса оЬ] есС можно обращаться к данным любого типа, поскольку в переменной ссылбчиого типа оЬ3 есС допускается хранить ссылку иа данные всех остальных типов.
Следовательно, в массиве типа оь3 еос из рассматриваемого здесь примера можно сохранить данные практически любого типа. В развитие этой идеи можно было бы, например, без особого труда создать класс стека со ссылками иа объекты класса сь3 есС. Это позволило бы хранить в стеке данные любого типа. Несмотря иа то что универсальный характер класса оЬ] есс может быть довольно эффективно использован в некоторых ситуациях, было бы ошибкой думать, что с помощью этого класса стоит пытаться обойти строго соблюдаемый в С№ контроль типов. Вообще да[0): да [11: да [2]: да [31: да[4]: да[5]: да[в]: да (71: да [8]: да [91: О 1 2 1.5 2 2.5 Привет тгце Х конец 360 Часть 3.
Язык С№ говоря, целое значение следует хранить в переменной типа зпс, строку — в переменной ссылочного типа асгапд и т.д. А самое главное, что начиная с версии 2.0 для программирования на С№ стали доступными подлинно обобщенные типы данных — обобщения (более подробно оии рассматриваются в главе 18).
Внедрение обобщений позволило без труда определять классы и алгоритмы, автоматически обрабатывающие данные разных типов, соблюдая типовую безопасность. Благодаря обобщениям отпала необходимость пользоваться классом оттз ест как универсальным типом данных при создании нового кода. Универсальный характер этого класса лучше теперь оставить для применения в особых случаях. ГЛАВА Интерфейсы, структуры и перечисления в этой главе рассматривается одно из самых важных в С№ средств: интерфейс, определяющий ряд методов для реализации в классе. Но поскольку в самом интерфейсе ни один из методов не реализуется, интерфейс представляет собой чисто логическую конструкцию, описывающую функциональные возможности без конкретной их реализации.
Кроме того, в этой главе представлены еще два типа данных С№: структуры и перечисления. Структуры подобны классам, за исключением того, что они трактуются как типы значений, а не ссылочные типы. А перечисления представляют собой перечни целых констант. Структуры и перечисления расширяют богатый арсенал средств программирования на С№. Интерфейсы Иногда в объектно-ориентированном программировании полезно определить, что именно должен делать класс, но не как он должен это делать.
Примером тому может служит упоминавшийся ранее абстрактный метод. В абстрактном методе определяются возвращаемый тип и сигнатура метода, но не предоставляется его реализация. А в производном классе должна быть обеспечена своя собственная реализация каждого абстрактного метода, определенного в его базовом классе. Таким образом, абстрактный метод определяет интерфейс, но не реализацию метода. Конечно, абстрактные классы и методы приносят известную пользу, но положенный в их основу принцип может быть развит далее. В С№ предусмотрено разделение интерфейса класса и его реализации с помощью ключевого слова гоееггасе. С точки зрения синтаксиса интерфейсы подобны абстрактным классам. Но в интерфейсе ни у одного из методов не должно быть тела. Это означает, что в интерфейсе вообще не предоставляется никакой реализации.
В нем указывается только, что именно следует делать, но не как это делать. Как только интерфейс будет определен, он может быть реализован в любом количестве классов. Кроме того, в одном классе может быть реализовано любое количество интерфейсов. 362 часть). язык С» Для реализации интерфейса в классе должны быть предоставлены тела (т.е. конкретные реализации) методов, описанных в этом интерфейсе. Каждому классу предоставляется полная свобода для определения деталей своей собственной реализации интерфейса Следовательно, один и тот же интерфейс может быть реализован в двух классах по-разному Тем не менее в каждом из них должен поддерживаться один и тот же набор методов данного интерфейса. А в том коде, где известен такой интерфейс, могут использоваться объекты любого из этих двух классов, поскольку интерфейс для всех этих объектов остается одинаковым.
Благодаря поддержке интерфейсов в С() может быть в полной мере реализован главный принцип полиморфизма: один интерфейс — множество методов. Интерфейсы объявляются с помощью ключевого слова 1псеггасе. Ниже приведена упрощенная форма объявления интерфейса. 1псеггвсе нмя( вазврещеемъи~ тип имв метода)(список параметров) возврещаемьи~ тип ими методвй(список параметров) // вазврещвемьвт тип имя метсдаы(список параметров) ) где имя — это конкретное имя интерфейса.
В объявлении методов интерфейса используются только их возвращаемьвт тип и сигнатура. Они, по существу, являются абстрактными методами. Как пояснялось выше, в интерфейсе не может быть никакой реализации. Поэтому все методы интерфейса должны быть реализованы в каждом классе, включающем в себя этот интерфейс. В самом же интерфейсе методы неявно считаются открытыми, поэтому доступ к ним не нужно указывать явно. Ниже приведен пример объявления интерфейса для класса, генерирующего последовательный ряд чисел. рсЫ1с гпсегкасе тзекьев ( ьпк пекнехс()т // возвратить следующее по порядку число чофп Кевек()т // перезапустить чо1О Эекзсвгк(ьпк х)т // задать начальное значение ) Этому интерфейсу присваивается имя гяег1еэ.
Префикс Г в имени интерфейса указывать необязательно, но зто принято делать в практике программирования, чтобы как-то отличать интерфейсы от классов. Интерфейс гзе г1ев объявляется как рпЫ 1с и поэтому может быть реализован в любом классе какой угодно программы. Помимо методов, в интерфейсах можно также указывать свойства, индексаторы и события. Подробнее о событиях речь пойдет в главе (5, а в этой главе основное внимание будет уделено методам, свойствам и индексаторам. Интерфейсы не могут содержать члены данных. В них нельзя также определить конструкторы, деструкторы или операторные методы. Кроме того, ни один из членов интерфейса не может быть объявлен как зсас1с.