Вы находитесь на странице: 1из 689

Изучаем

2-е издание
Управляй
данными Познай секреты
с Ы № абстрагирования
и наследования /

Изучай С#
на забавных ^
примерах

Узнай,
как методы Научись
расширения эффективно
облегчают жизнь использовать
программиста обобщенные
коллекции
Изучаем С#
2-е издание

Э. Стиллмен
Дж. Грин

С ^ П П Т Е Р '
М о скв а ■С а н кт-П е т е р б у р г ■Н и ж н и й Н о вгород ■В о р о н еж
Р о с то в -н а -Д о н у • Е ка тер и н б ур г ■С а м а р а - Н о в о си б и р ск
К и ев • Х арь ков ■М и н с к
2012
ББК 32.973.2-018.1
УДК 004.43
С80

Стиллмен Э., ГринДж.


С80 Изучаем С#. 2-е изд. — СПб.: Питер, 2012. — 696 с.: ил.

ISB N 978-5-459-00422-9

В отличие от большинства книг по программированию, построенных на основе скучного изложения спе­


цификаций и примеров, с этой книгой читатель сможет сразу приступить к написанию собственного кода на
языке программирования C# с самого начала. Вы освоите минимальный набор инструментов, а далее приме­
те участие в забавных и интересных программных проектах: от разработки карточной игры до создания серь­
езного бизнес-приложения. Второе издание книги включает последние версии C# .NET 4.0 и Visual Studio
2010 и будет интересно всем изучающим язык программирования С#.
Особенностью данного издания является уникальный способ подачи материала, выделяющий серию «Head
First» издательства O’Reilly в ряду множества скучных книг, посвященных программированию.

ББК 32.973.2-018.1
УДК 004.43

Права на издание получены по соглашению с O'Reilly. Все права защищены. Никакая часть данной книги не может быть
воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.

Информация, содержащаяся в данной книге, получена из источников, рассматриваемых издательством как надежные.
Тем не менее, имея в виду возможные человеческие или технические ошибки, издательство не может гарантировать
абсолютную точность и полноту приводимых сведений и не несет ответственности за возможные ошибки, связанные
с использованием книги.

ISBN 978-1-4493-8034-2 англ. © Authorized Russian translation of the English edition Head First C# © O'Reilly
Media, Inc. This translation is published and sold by permission of O'Reilly Me­
dia, Inc., the owner of all rights to publish and sell the same
ISBN 978-5-459-00422-9 © Перевод на русский язык ООО Издательство «Питер», 2012
© Издание на русском языке, оформление
ООО Издательство «Питер», 2012
авторы

Спасибо, что купили нашу книгу!


М ы писали ее с удовольствием
и надеемся, что вы получите
кайф от ее Прочтения...

Эндрю

А в т о р эт о й ф о т о гр а ф и и (как
и с н и м ка канала Гованус) ~
Ниша Сондхе

Э ндрю С тиллм ен родился в Нью-Йорке,


дважды ему приходилось жить в Питтсбурге.
Сначала он закончил там университет Карне-
ги-Меллон, затем именно вэтом городе они
с Дженни начали свой консультационный
бизнес и работу над первой книгой для изда­ Д ж ен н и ф ер Грин изучала в колледже фило­
тельства O ’Reilly софию и, как и многие ее однокурсники, не смог­
ла найти работу по специальности. Но благодаря
Вернувшись после колледжа в родной город,
способностям к разработке программного обеспе­
Эндрю устроился программистом в фирму
чения она начала работать в онлайновой службе.
EMI-Capitol Records, что было не лишено
смысла, ведь в школьные годы он научился В 1998 году Дженни переехала в Нью-Йорк и
играть на виолончели и джазовой бас-гитаре. устроилась в фирму по разработке финансово­
И в той же компании, где Эндрю руководил го программного обеспечения. Она управляла
командой программистов, работала Джен­ командой разработчиков, занимавшихся искус­
ни. За прошедшие годы Эндрю сотрудничал ственным интеллектом и обработкой естествен­
с весьма квалифицированными программи­ ных языков.
стами и уверен, что многому от них научился. Затем она много путешествовала по миру с раз­
В свободное от написания книг время Эндрю личными командами разработчиков и реализова­
создает бесполезные (но забавные) програм­ ла целый ряд замечательных проектов.
мы, пишет музыку (для видеоигр и не толь­ Дженифер обожает путешествия, индийское
ко), изучает тайцзи и айкидо, встречается со кино, комиксы, компьютерные игры и свою гон­
своей девушкой Лизой и выраш;ивает шпица. чую.

с момента своей встречи в г я я г - м Дженни и Эндрю вместе разрабатывают программное обе­


спечение и пиш ут о нем. Их первая книга Applied Software Project ^
стве О'Рейли в ZOOS году. A первая книга в серии Head P in t Head R rst РМР поябылясь б Z 007-M .
В Z 0 0 3 году ребята основали ф ирму Stellm an & Qreene Consultine ^о разработке програм мно­
го обеспечения для ученых, исследующих воздействие гербицидов, примененных во время ^^ины во
Вьетнаме В свободное от написания программ и книг время Эндрю и Джени участ вую т в конфе
рент ах разработчиков программного обеспечения и руководителей проектов.
Их личный блог расположен по адресу: http://w w w .stellm an-greene.com
оглавление

О одерж ание (с Б о д к а )

Введение 23
1 Эффективность с с#. Визуальные приложения за 10 минут 35

2 Это всего лишь код. Под покровом 73


3 Объекты, по порядку стройся! Приемы программирования 115
4 Типы и ссылки. 10:00утра. Куда подевались наши данные? 153
5 Инкапсуляция. Пусть личное остается... личным 195
6 Наследование. Генеалогическое древо объектов 231
7 И нтерфейсы и абстрактные классы. Пусть классы держат обещания 283
8 Перечисления и коллекции. Большие объемы данных 339
9 Чтение и запись файлов. Сохрани массив байтов и спаси мир 395

10 Обработка исключений. Борьба с огнем надоедает 449


11 События и делегаты. Что делает ваш код, когда вы на него не смотрите 491
12 Обзор и предварительные результаты. Знания, сила и построение
приложений 525
13 Элементы управления и графические фрагменты. Наводим красоту 573
14 Капитан Великолепный. Смерть объекта 631
15 ЫЫЦ. Управляем данными 669

( ^ о д е |^ Ж а н и е ( н а с л іо й г Щ е е )

Введение
Ваш мозг и С # . Вы учитесь — готовитесь к экзамену. Или
пытаетесь освоить сложную техническую тему. Ваш мозг пытается
оказать вам услугу. Он старается сделать так, чтобы на эту оче­
видно несущественную информацию не тратились драгоценные
ресурсы. Их лучш е потратить на что-нибудь важное. Так как же за­
ставить его изучить С # ?
Для кого написана эта книга 24
Мы знаем, о чем вы думаете 25
Метапознание: наука о мышлении 27
Заставьте свой мозг повиноваться 29
Что вам потребуется 30
Информация 31
Технические рецензенты 32
Благодарности 33

7
оглавление

э ^ '^ е К т и Б н о с ш ь С

Визуальны е приложения з а 10 минут


Хотите программировать действительно быстро? с# — это
мощный язык программирования. Благодаря Visual Studio вам не потре­
буется писать непонятный код, чтобы заставить кнопку работать. Вместо
того чтобы запоминать параметры метода для имени и для ярлыка кнопки,
вы сможете сфокусироваться на достижении результата. Звучит заманчи­
в о ? Тогда переверните страницу и приступим к делу.

Зачем вам изучать C# 36


с#, ИСР Visual Studio многое упрощают 37
Избавьте директора от бумаг 38
Перед началом работы выясните, что именно нужно пользователю 39
Что мы собираемся сделать 40
Это вы делаете в Visual Studio 42
А это Visual Studio делает за вас 42
Создаем пользовательский интерфейс 46
Visual Studio, за сценой 48
Редактирование кода 49

Первый запуск приложения 50


Где же мои файлы? 50

Вот что уже сделано 51

Для хранения информации нужна база данных 52


База данных, созданная ИСР 53
Язык SQL 53

Создание таблицы для списка контактов 54


Поля в контактной карте —это столбцы таблицы People 56
Завершение таблицы 59

Перенос в базу данных с карточек 60


Источник данных соединит форму с базой 62
Добавление элементов, связанных с базой данных 64
.NET Framework lerigelsimd
solutione
Howeood? Ooood OtШ яг Швая Хорошие программы понятны интуитивно 66
Vrfua ...... 7
Тестирование 68

D
«П(« Как превратить ВАШЕ приложение в приложение для ВСЕХ 69
Передаем приложение пользователям 70
Oala access
Работа НЕ окончена: проверим процесс установки 71
Управляющее данными приложение готово 72
оглавление

э щ о Б сеГ о Л и Ш ь К оД

Под покровом
Вы — программист, а не просто пользователь
ИСР. И С Р может сделать за вас многое, но не всё. При на­
писании приложений часто приходится решать повторяющиеся
задачи. Пусть эту работу выполняет ИСР. Вы же будете в это
время думать над более глобальными вещами. Научившись
писать код, вы получите возможность решить любую задачу.

Когда вы делаете это... 74


...ИСР делает это. 75
Как рождаются программы 76
Писать код помогает ИСР 78
Любые действия ведут к изменению кода 80
Структура программы 82
Начало работы программы 84
Редактирование точки входа 86
Д°СЗЦр“Ц1Це ЬСлассы могут принадлежать одному пространству имен 91
ишкую f o J ^ 92
Что такое переменные

♦ Знакомые математические символы 94


Наблюдение за переменными в процессе отладки 95
Циклы 97
Перейдем к практике 98
О ператор выбора 99
П роверка условий 100

А л я т х д с й програм м ы вк

Р™ , « Ч Е Т

(Сля£;с имен
ф р а гм е н т бйиде«
(омен»
АМХА&неки&л^ограммы ^
могитCOC^v\oi:^A\t:>
и з й е г о о д н о го к л а с с а ;.

К л а с с в ю \к / ч а е у л о Зми и л и н с с к о л о к о
A^£Уv'.o^oв. M£^■vxoдo^всегё^а
какому-либо
'Л из о м р а ь у ^ о р о в ,
оглавление

о б ъ е к т ы , 310 ц о|> яд ку сшр>ойся

Приемы программирования
Каждая прогроАлма решает какую-либо проблему. Перед на­
писанием программы нужно четко сформулировать, какую задачу она будет
решать. Именно поэтому так полезны объекты. Ведь они позволяют струк­
турировать код наиболее удобным образом. Вы же можете сосредоточить­
ся на обдумывании путей решения, так как вам не нужно тратить время на
написание кода. Правильное использование объектов позволяет получить
интуитивно понятный код, который при необходимости можно легко отре­
дактировать.

Что думает Майк о своих проблемах 116


Проблема Майка с точки зрения навигационной системы 117
Методы прокладки и редактирования маршрутов 118
Построим программу с использованием классов 119
Идея Майка 121
Объекты как способ решения проблемы 122

Нау|да1ог Возьмите класс и постройте объект 123


8 е10е811па1юп() Экземпляры 124
ModifyRouteToAvoid()
Мо(11^Рои1еТо1п(:^ис1е() Простое решение! 125
6е1Нои1е()
ве1ПтеТоОе811паИоп() Поля 130
То1а101з1апсе()
Создаем экземпляры! 131
Спасибо за память 132
Что происходит в памяти программы 133
Значимые имена 134
Структура классов 136
Выбор структуры класса при помощи диаграммы 138
Определяй «лйсс, вы определяете Помогите парням 142
и его мскУ\оды, точно также как
чертеж 01лределяет внешний вид П роект «Парни» 143
дома.
Форма для взаимодействия с кодом 144
Более простые способы присвоения начальных значений 147

чН а основе одного чертежа


МОЖНО у^остроить сколько
угодно домов, а из одного
класса можно получить
сколько угодно объектов.
10
оглавление

гоипь! и ссылки

L
10:00 утра. Куда подевались наши данны е?
Без данных програллмы бесполезны. Взяв информацию от поль­
зователей, вы производите новую информацию, чтобы вернуть ее им же.
Практически все в программировании связано с обработкой данных тем
или иным способом, в этой главе вы познакомитесь с используемыми в С #
типами данных, узнаете методы работы с ними и даже ужасный секрет объ­
ектов (только т-с-с-с... объекты — это тоже данные).

Тип переменной определяет, какие данные она может сохранять 154


Наглядное представление переменных 156
10 литров в 5-литровой банке 157
Приведение типов 158
Автоматическая коррекция слишком больших значений 159
Иногда приведение типов происходит автоматически 160
Аргументы метода должны быть совместимы
с типами параметров 161
Комбинация с оператором = 166
Объекты тоже используют переменные 167
Переменные ссылочного типа 168
Ссылки подобны маркерам 169
П ри отсутствии ссылок объект превращается в мусор 170
Побочные эффекты множественных ссылок 171

Dog fldo; Две ссылки это ДВА способа редактировать данные объекта 176
Dog lucky new Do g (); Особый случай; массивы 177
Массив 177
Массив может состоять из ссылочных переменных 178
Добро пожаловать на распродажу сэндвичей от Джо! 179
fido = new D o g O ;
Ссылки позволяют объектам обращаться друг к другу 181
Сюда объекты еще не отправлялись 182
Играем в печатную машинку 187

lucky = null;
poof!-~
/ \ \

11
оглавление

инкапсуляция

Пусть личное остается... личным

Вы когда-нибудь мечтали о том, чтобы вашу личную


д
жизнь оставили в покое? Иногда объекты чувствуют то же самое.
Вы же не хотите, чтобы посторонние люди читали ваши записки или рас­
сматривали банковские выписки. Вот и объекты не хотят, чтобы другие
объекты имели доступ к их полям. В этой главе мы поговорим об инкапсу­
ляции. Вы научитесь закрывать объекты и добавлять методы защ ищ аю ­
щие данные от доступа.

Кэтлин профессиональный массовик-затейник 196


Как происходит оценка 197

Кэтлин тестирует программу 202


Каждый вариант нужно было считать отдельно 204
Неправильное использование объектов 206
Форма 206
Инкапсуляция как управление доступом к данным 207
Доступ к методам и полям класса 208
НА САМОМ ЛИ ДЕЛЕ защищено поле realName? 209
Закрытые поля и методы доступны только изнутри класса 210
Инкапсуляция сохраняет данные нетронутыми 218
Инкапсуляция при помощи свойств 219
Приложение для проверки класса Farmer 220
Автоматические свойства 221
Редактируем множитель feed 222
Конструктор 223

Ш еШ ю і». FuKy
(•їрсгріпм '' №oratloHi

WMbervf
peo^e.
ыт
AluikU«t
СтНїівв:
fSAieowitoH
total cut)
Pamv
raney
deeoratioH^
> І*і;рсггіпм
•VOdutrathig
ful
i=oo4»2$|i<r
(НГМЯ) NomuU
АМЫ, Picontlnu
(*20|ИГ №ЇОр(Г
liemid
s s s s a is i

12
оглавление

наследование

Генеалогическое древо объектов


Иногда люди хотят быть похожим на своих родителей.
Вы встречали объект, который действует почти так, как нуж но? Думали ли вы
о том, какое совершенство можно бы ло бы получить, изменив всего несколько
элем ентов? Именно по этой причине наследование является одним из самых
мощ ных инструментов с#, в этой главе вы узнаете, как производный класс
повторяет поведение родительского, сохраняя при этом гибкость редактиро­
вания. Вы научитесь избегать дублирования кода и облегчите последующее
редактирование своих программ.

Организация дней рождения —это тоже работа 1Сэтлин 232


Нам нужен класс BirthdayParty 233
Планировщик мероприятий, версия 2.0 234
Дополнительный взнос за мероприятия с большим
количеством гостей 241
Наследование 242
Модель классов; от общего к частному 243
Симулятор зоопарка 244
Иерархия классов 248
Производные классы расширяют базовый 249
Синтаксис наследования 250
П ри наследовании поля свойства и методы базового класса
добавляются к производному... 253
П ерекрытие методов 254
Вместо базового класса можно взять один из производных 255
Производный класс умеет скрывать методы 262
Ключевые слова override и virtual 264
Ключевое слово base 266
Если в базовом классе присутствует конструктор, он должен остаться
и в производном классе 267
Теперь мы готовы завершить программу для Кэтлин! 268
Система управления ульем 273
П остроение основ 274
Совершенствуем систему управления ульем
при помощи наследования 282

13
оглавление

интер^^ейсы и а^ аи р ак тн ы е классы

Пусть классы держат обещания


Действия значат больше, чем слова. Иногда возникает необходи­
мость сгруппировать объекты по выполняемым функциям, а не по классам,
от которых они наследуют. Здесь вам на помощь приходят интерфейсы,
они позволяют работать с любым классом, отвечающим вашим потреб­
ностям. Но чем больш е возможностей, тем выш е ответственность, и если
классы, реализующие интерфейс, не выполнят обязательств... программа
компилироваться не будет.

Наследование
Вернемся к нашим пчелам 284
Классы для различных типов пчел 285
И нтерфейсы 286
ДбсторакЦия
Классы, реализующие интерфейсы, должны включать ВСЕ
методы интерфейсов 289
^|нкаисуляЦия Ф Учимся работать с интерфейсами 290
Ссылки на интерфейс 292
Ссылка на интерфейс аналогична ссылке на объект 293
IJoiuMop'fuaM Интерфейсы и наследование 295
КоЬоВее 4000 функционирует без меда 296
Locsl&on Кофеварка относится к Приборам 298
Name
Exfts Восходящее приведение 299
Нисходящее приведение 300
Oescrip&mO
Нисходящее и восходящее приведение интерфейсов 301
Модификаторы доступа 305
R oom O utsid e
Decoration Hot Изменение видимости при помощи модификаторов доступа 306
Классы, для которых недопустимо создание экземпляров 309
Абстрактный класс. Перепутье между классом и интерфейсом 310
Как уже было сказано, недопустимо создавать экземпляры
некоторых классов 312

Абстрактный метод не имеет тела 313


Смертельным ромбом! 318

Различные формы объекта 32 1

14
оглавление

п е р е ч и с л е н и я U К о л л е к ц и и

Большие объемы данных


Приш ла беда — отворяй ворота, в реальном мире данные, как пра­
вило, не хранятся маленькими кусочками. Данные приходят вагонами, ш та­
белями и кучами. Для их систематизации нужны мощные инструменты, и тут
вам на помощь приходят коллекции. Они позволяют хранить, сортировать
и редактировать данные, которые обрабатывает программа. В результате вы
можете сосредоточиться на основной идее программирования, оставив за­
дачу отслеживания данных коллекциям.

Категории данных не всегда можно сохранять


в переменных типа string 340
Перечисления 341
Присвоим числам имена 342
Создать колоду карт можно было при помощи массива... 345
Проблемы работы с массивами 346
Коллекции 347
Коллекции List 348
Динамическое изменение размеров 351
Обобщенные коллекции 352
Инициализаторы коллекций 356
Коллекция уток 357
Сортировка элементов коллекции 358
И нтерфейс IComparable<Duck> 359
Способы сортировки 360
Создадим экземпляр объекта-компаратора 361
Сложные схемы сравнения 362
П ерекрытие метода ToStringO 365
Обновим цикл foreach 366
\ \ ^ Интерфейс1ЕпитегаЫ е<Т> 367
- ___ Восходящее приведение с помощью lEnum erable 368
* Создание перегруженных методов 369
/ ^ Словари 375
Дополнительные типы коллекций... 389
Звенья следуют в порядке их поступления 390
Звенья следуют в порядке, обратном порядку их поступления 391

15
оглавление

Ч тение U san u cb * ^ a u JIo B

Сохрани м ассив байтов и сп аси мир


Иногда настойчивость окупается.
Пока что все ваши программы жили недолго. Они запускались, некоторое вре­
мя работали и закрывались. Но этого недостаточно, когда имеешь дело с важ­
ной информацией. Вы должны уметь сохранять свою работу В этой главе мы
поговорим о том, как записать данные в файл, а затем о том, как прочитать эту
информацию. Вы познакомитесь с потоковыми классами .N ET и узнаете о тай­
нах шестнадцатеричной и двоичной систем счисления.

Для чтения и записи данных в .NET используются потоки 396


Различные потоки для различных данных 397
Объект FileStream 393

Трехшаговая процедура записи текста в файл 399


Чтение и запись при помощи двух объектов 403
Встроенные объекты для вызова стандартных окон диалога 407
Встроенные классы File и Directory 4 Ю
Открытие и сохранение файлов при помощи окон диалога 413
И нтерфейс IDisposable 415

Операторы using как средство избежать системных ошибок 416


Запись файлов сопровождается принятием решений 422
О ператор switch 423

Ч тение и запись информации о картах в файл 424


Чтение карт из файла 425

Сериализации подвергается не только сам объект... 429

69 117 114 101 107 97 33


Сериализация позволяет читать и записывать объект целиком 430
E u re k a ! Сериализуем и десериализуем колоду карт 432
о \ г г ^ ч i
.NET использует Unicode для хранения символов и текста 435
Перемещение данных внутри массива байтов 436
Класс BinaryWriter 437

Чтение и запись сериализованных файлов вручную 439


Найдите отличия и отредактируйте файлы 440
69 7^4 Сложности работы с двоичными файлами 441
10797' Программа для создания дампа 442

Достаточно StreamReader и Stream Writer 443


Чтение байтов из потока 444

16
оглавление

обр>абощ ка искЛ іоЧ ений

Борьба с огнем надоедает


Программисты не должны уподобляться пожарным.
Вы усердно работали, штудировали технические руководства и наконец до­
стигли вершины; теперь вы главный программист. Но вам до сих пор продол­
жают звонить с работы по ночам, потому что программа упала или работает
неправильно. Ничто так не выбивает из колеи, как необходимость устранять
странные ошибки... но благодаря обработке искпючений вы сможете напи­
сать код, который сам будет разбираться с возможными проблемами.

Брайану нужны мобильные оправдания 450


Объект Exception 454
Код Брайана работает не так, как предполагалось 456
Исключения наследуют от объекта Exception 458
Работа с отладчиком 459
Поиск ошибки в приложении Excuse Manager с помощью
отладчика 460
А код все равно не работает... 463
Ключевые слова try и catch 465
Вызов сомнительного метода 466
Результаты применения ключевых слов try/catch 468
Ключевое слово finally 470
Получения информации о проблеме 475
Обработка исключений разных типов 476
Один класс создает исключение, другой его обрабатывает 477
Исключение O utO ffloney для пчел 478
Оператор using как комбинация
операторов try и finally 481
Избегаем исключений при помощи интерфейса IDisposable 482
Наихудший вариант блока catch 484
Временные решения 485
Краткие принципы обработки исключений 486
Наконец Брайан получил свой отдых... 489

17
оглавление

собьдпия и ДеЛеГашь!

Что делает код, когда вы на него не смотрите


Невозможно все время контролировать созданные объекты.
Иногда что-то... происходит И хотелось бы, чтобы объекты умели реагиро­
вать на происходящее. Здесь вам на помощь приходят события. Один объ­
ект их публикует, другие объекты на них подписываются, и система работает.
А для контроля подписчиков вам пригодится метод обратного вызова.

Хотите, что объекты научились думать сами? 492


Как объекты узнают, что произошло? 492
События 493
Один объект инициирует событие, другой реагирует на него 494
Обработка события 495
Соединим все вместе 496
Автоматическое создание обработчиков событий 500
Обобщенный ЕуепСНап(11ег 506
Все формы используют события 507
Несколько обработчиков одного события 508
Связь между издателями и подписчиками 510
Делегат замещает методы 511
Делегаты в действии 512
Объект может подписаться на событие... 515
Обратный вызов 516
О братный вызов как способ работы с делегатами 518

Р‘^с с ^о я н и и 8 2 о т базы. Лодаюм,ий Эолжен


^ I б роси т о э т о т
Ва11 .О пВ аШ п Р 1ау (70, 82) МЯИ.
^ Подаю щ ий м ож ет

у г л о м и на
т а к о е расст оя н и е
дольш е, чем 28 32 .).

Р 1 Ь с Ь в г .С а Ь с Ь В а 1 1 (7 0 , 90)

18
оглавление

и т1реДБа|=пщ1еЛьНые р’езуЛ ьташ ы

Знания, си ла и построение приложений


Знания нужно применять на практике.
Пока вы не начнете писать работающий код, вряд ли можно быть уве­
ренным, что вы на самом деле усвоили сложные понятия С#. Сейчас мы
займемся применением на практике полученных ранее знаний. Кроме
того, вы найдете предварительные сведения о понятиях, которые будут
рассматриваться в следующих главах. Мы начнем построение действи­
тельно сложного приложения, чтобы закрепить ваши навыки.

Жизнь U смерть цветка Вы прошли долгий путь 526


Вы стали пчеловодом 527
Архитектура симулятора улья 528
jage = О Ife
П остроение симулятора улья 529
Ж изнь и смерть цветка 533
Класс Вее 534
Программисты против бездомных пчел 538
Для жизни улья нужен мед 538
П остроение класса Hive 542
Метод Go О для улья 543
Все готово для класса World 544
Система, основанная на кадрах 545
Код для класса World 546
Поведение пчел 552
Основная форма 554
Получение статистики 555
Обье^г^
Таймеры 556
Работа с группами пчел 564
Коллекция коллекционирует... ДАННЫЕ 565
LINQynpom;aeT работу с коллекциями и базами данных 567
Тестирование (вторая попытка) 568
Последние штрихи: O pen и Save 569

O&beV^

19
оглавление

элем енты уп|>аБления и Гра^^иЧеские “^ а Ш е н т ы

Наводим красоту

Иногда управление графикой приходится брать в свои руки.


До этого момента визуальный аспект наших приложений был отдан на откуп
элементам управления. Но иногда этого недостаточно, например, если вы
хотите анимировать изображение. Вам предстоит научиться создавать свои
элементы управления для .NET, применять двойную буферизацию и даже
рисовать непосредственно на формах.

Элементы управления как средство взаимодействия


с программой 574
Элементы управления - это тоже объекты 575
Анимируем симулятор улья 576
Визуализатор 578
Формы для визуализации 579
Первый анимированный элемент управления 583
Базовый класс PictureBox 584
Кнопка добавления элемента BeeControl 586
Удаление дочерних элементов управления 587
Класс UserControl 588
Поместим на форму анимированных пчел 590
Формы Hive и Field 592
П остроение визуализатора 593
Соединим основную форму с формами HiveForm и FieldForm 596
Проблемы с производительностью 600
Объект Graphics 602
Объект Bitmap 603
Пространство имен System.Drawing 604
Знакомство с GDI+ 605
Рисуем на форме 606
Решение проблемы с прозрачностью 611
Событие Paint 612
П ерерисовка форм и элементов управления 615
Двойная буферизация 618
Вывод на печать 624
Окно предварительного просмотра и окно диалога Print 625

20
оглавление

Метод завершения объекта 638


Когда запускает метод завершения объекта 639
Явное и неявное высвобождение ресурсов 640
Возможные проблемы 642
Сериализуем объект в методе Dispose () 643
Структура напоминает объект... 647
...но объектом не является 647
Значения копируются, а ссылки присваиваются 648
Структуры - это значимые, а объекты - ссылочные типы 649
Сравнение стека и кучи 651
Необязательные параметры 656
Типы, допускающие значение null 657
Типы, допускающие значение null, увеличивают робастность
программы 658
Что осталось от Великолепного 661
Сравнение 661
Методы расширения 662
Расширяем фундаментальный тип; string 664

21
оглавление

LINQ
Управляем данными
Этот мир управляется данными... вам лучше знать, как в нем жить.
Времена, когда можно бы ло программировать днями и даже неделями, не
касаясь множества данных, давно позади. В наши дни с данными связано
все. Часто приходится работать с данными из разных источников и даже раз­
ных форматов. Базы данных, XM L, коллекции из других программ... все это
давно стало частью работы программиста на С#. В этом ему помогает LINQ.
Э та функция не только упрощ ает запросы, но и умеет разбивать данные на
группы и, наоборот, соединять данные из различных источников.

Простой проект... 670


...но сначала нужно собрать данные 671
List<StarbuzzData> 671
Сбор данных из разных источников 672
Коллекции .NET уже настроены под LINQ 673
Простой способ сделать запрос 674
Сложные запросы 675
Универсальность LINQ 678
Группировка результатов запроса 683
Сгруппируем результаты Джимми 684
Предложение join 687
List<Comic> 687
LIN Q Sequence 687
List<Purchases> 687
Джимми изрядно сэкономил 688

LINQ Sequence 689


List<StarbuzzData> 689
Соединение LINQ с базой данных SQL 690
Ч Соединим Starbuzz и Objectville 694

22
к а к р а б о зза а ш ь с эш ^ й к н и Г о й

Введение

в э т о м розЭеле " т А 1^^0 6 ^ к ™ га ° С*?»


« т а к п ом ем у они бклю иил
как работать с этой книгой

Для кого написана эта книга?


Е сли н а воп росы :

Вы хотите изучать С#?

Вы предпочитаете учиться практикуясь, а не просто


читая текст?

Вы предпочитаете оживленную беседу сухим,


скучным академическим лекциям?

вы о тв е ч а е те п о л о ж и тел ьн о , то эта кн и га для вас.

Кому эта книга не подойдет?


Если вы ответите «да» на любой из следующих вопросов.

^ Навевает ли на вас скуку и нервозность мысль о том,


что придется часто и много писать код?

Вы отличный программист на C++ или Java,


которому нужен справочник?

^ Вы боитесь попробовать что-нибудь новое? Скорее


пойдете к зубному врачу, чем наденете полосатое
с клетчатым? Считаете, что техническая книга, в которой
концепции 0# изображены в виде человечков, серьез­
ной быть не может?

...эта книга не для вас.

[З а м е т к а о т о т д ела продаж-
в о о б щ е -т о эт а кныга для лю бого
У кого е с т ь деньги.]

24 введение
введение

Mbl знаем, о чем Вы думаете


«Разве серьезные книги по программированию на C# такие?»
«И почему здесь столько рисунков?»
«Можно ли так чему-нибудь научиться?» М озг ги,

^^Ж но. ^
U мы знаем, о чем думает Ваш мозг /
Мозг жаждет новых впечатлений. Он постоянно ищет,
анализирует, ожидает чего-то необычного. Он так устроен, и это
помогает нам выжить.
В наши дни вы вряд ли попадете на обед к тигру. Но наш мозг
постоянно остается настороже. Просто мы об этом не знаем.
Как же наш мозг поступает со всеми обычными, повседневными
вещами? Он всеми силами пытается оградиться от них, чтобы
они не мешали его настоящей работе —сохранению того, что
действительно важно. Мозг не считает нужным сохранять
скучную информацию. Она не проходит фильтр, отсекающий
«очевидно несущественное».
Но как же мозг узнает, что важно? Представьте, что вы выехали
на прогулку, и вдруг прямо перед вами появляется тигр. Что
происходит в вашей голове и в теле?
Активизируются нейроны. Вспыхивают эмоции. Происходят
химические реакции.
И тогда ваш мозг понимает...

Конечно, это важно! Не забывать!


А теперь представьте, что вы находитесь дома или в библи­
отеке, в теплом, уютном месте, где тигры не водятся. Вы
учитесь —готовитесь к экзамену. Или пытаетесь освоить
сложную техническую тему, на которую вам выделили не­
делю... максимум десять дней.
И тут возникает проблема: ваш мозг пытается оказать вам
услугу. Он старается сделать так, чтобы на эту очевидно несу­
щественную информацию не тратились драгоценные ресур­
сы. Их лучше потратить на что-нибудь важное. На тигров,
например. Или на то, что к огню лучше не прикасаться.
Или что ни в коем случае нельзя вывешивать фото с этой
вечеринки на своей страничке в Facebook.
Нет простого способа сказать своему мозгу: «Послушай,
мозг, я тебе, конечно, благодарен, но какой бы скучной ни была
эта книга, и пусть мой датчик эмоций сейчас на нуле, я хочу
запомнить то, что здесь написано».

д ал ьш е > 25
как работать с этой книгой

V a к н и г а ДЛЯ г о е - . Н™ £ 5

к а к « ы ч т о -т о "^ к о
Затолкать в голову поболь Ф g ^огии и психологии обучения, для

М ы знаем, как заставить ваш мозг работать.

О с н о в н ы е п р и н ц и п ы с е р и и « H e a d F ir s t » ;
наглядность. Графика запоминается гораздо лучше, чем о б ьн ны и

V o I

- »-»пож ения. Недавние исследования показали,


р а з г о в о р н ы й с т и л ь и з л о * о (вместо
« и я . » фор«льныхл«цш
™ „е
),лучш ей.»
.о п р .р а ,г о з о р „ о » с т и „ е _ ~ ^
результатов на итоговом тестиров относитесь к себе слишком


столом или лекция?
Ппка вы не начнете напрягать извилины,
А кти вное участие чит ‘ читатель должен быть заинтере-
в вашей голове ничего не п р о ф ормулировать выводы
сован в результате; он долж р g д^^^лы упраж нения и

= е Г п " ; „ : = ^ ^

мозга и разные чувства.

Привлечение (и ой странице». М озг обращ ает внимание на инте-


очень хочу изучить это, но зась. неожиданное. Изучение слож ной техниче-

А
•Х -= Е
Г = Г о , . т е ^ и ^

\ ;г :;г г г ::;« ч » .с е ,„ ,й к .5 о а

26 введение
введение

Метапознание: наука о мышлении


Если вы действительно хотите быстрее и глубже усваивать новые
знания —задумайтесь над тем, как вы задумываетесь. Учитесь учиться.
Мало кто из нас изучает теорию метапознания во время учебы. Нам
положеноучитъся, но нас редко этому учат.
Но раз вы читаете эту книгу, то, вероятно, вы хотите узнать, как
программировать на С#, и по возможности быстрее. Вы хотите
запомнить прочитанное и применять новую информацию на практике.
Чтобы извлечь максимум пользы из учебного процесса, нужно
заставить ваш мозг воспринимать новый материал как Нечто Важное.
Критичное для вашего суш;ествования. Такое же важное, как тигр.
Иначе вам предстоит бесконечная борьба с вашим мозгом,
который всеми силами уклоняется от запоминания новой
информации.

К а к ж е У Б Е Д И Т Ь м о зг, ч т о п р о г р а м м и р о в а н и е н а С #
та к ж е в аж но, как и ти гр ?
Есть способ медленный и скучный, а есть быстры й и
эфф ективны й. П ервы й основан на тупом повторении.
Всем известно, что даже самую скучную инф ормацию можно
запомнить, если повторять ее снова и снова. П ри достаточном
количестве повторений ваш мозг прикидывает: «Вроде бы несущественно,
но раз одно и то же повторяется столько раз... Ладно, уговорил».
Быстрый способ основан на повы ш ении активности мозга, и особенно
на сочетании разных ее видов. Доказано, что все факторы, перечисленные
на предыдущей странице, помогают вашему мозгу работать на вас.
Например, исседования показали, что размещение слов вмуте/ж рисунков
(а не в подписях, в основном тексте и т. д.) заставляет мозг анализировать
связи между текстом и графикой, а это приводит к активизации большего
количества нейронов. Больше нейронов = выше вероятность того, что
информация будет сочтена важной и достойной запоминания.
Разговорный стиль тоже важен: обычно люди проявляют больше внимания,
когда они участвуют в разговоре, так как им приходится следить за ходом
беседы и высказывать свое мнение. Причем мозг совершенно не интересует,
что вы «разговариваете» с книгой! С другой стороны, если текст сух
и формален, то мозг чувствует то же, что чувствуете вы на скучной лекции
в роли пассивного участника. Его клонит в сон.
Но рисунки и разговорный стиль —это только начало.

д ал ьш е > 27
как работать с этой книгой

Вот что сделали МЫ 0 >^.ре.деля» к/.а сс. вы о л р е -


Эеляете. £
зсляете. 20мA1е^>^v\0v1cлl^
его
л\акисекдкие^-тежслреЗв- >1, тс/ч«у
точно
ляг('л внешний вид рома.

Мы использовали рисунки, потому что мозг лучше приспособлен для


восприятия графики, чем текста. С точки зрения мозга рисунок стоит
тысячи слов. А когда текст комбинируется с графикой, мы внедряем
текст прямо в рисунки, потому что мозг при этом работает эффективнее.
. Н а о с н с ве о(Ь<ого
т и леждА^ожноло,
Мы используем избыточность: повторяем одно и то же несколько раз, приме­ \ и т ь сколько уг,
\ д о м о в, а !лз о ^ .о г о
^ к л а с с а м ож но »\ол.
няя разные средства передачи информации, обращаемся к разным чувствам —и ск о л ь к о иг, '

все для повышения вероятности того, что материал будет закодирован в не­
скольких областях вашего мозга.
Мы используем концепции и рисунки несколько неож иданным образом, пото­
му что мозг лучше воспринимает новую информацию. Кроме того, рисунки и
идеи обычно имеют эшщитшльное содержант, потому что мозг обращает внима­
ние на биохимию эмоций. То, что заставляет нас чувствовать, лучше запоминает­
ся —будь то гиутка, удивление или интерес.
Мы используем/»озгово/шый стиль, потому что мозг лучше воспринимает инфор­
мацию, когда вы участвуете в разговоре, а не пассивно слушаете лекцию. Это
происходит и при чтении.
В книгу включены многочисленные упражнения, потому что мозг лучше запо­
минает, когда вы работаете самостоятельно. Мы постарались сделать их непро­
стыми, но интересными —то, что предпочитает большинство читателей.
Мы совместили несколько стилей обучения, потому что одни читатели любят по­
шаговые описания, другие стремятся сначала представить «общую картину»,
а третьим хватает фрагмента кода. Независимо от ваших личных предпочтений КЛЮЧЕВЫЕ
полезно видеть несколько вариантов представления одного материала. МОМЕНТЫ
Мы постарались задействовать оба полуишрия вашего мозга; это повышает вероят­
ность усвоения материала. Пока одна сторона мозга работает, другая часто име­
ет возможность отдохнуть; это повышает эффективность обучения в течение
продолжительного времени.
А еще в книгу включены истории и упражнения, отражающие другие точки зре­
ния. Мозг качественнее усваивает информацию, когда ему приходится оцени­
Беседа У камина
вать и выносить суждения.
в книге часто встречаются вопросы, на которые не всегда можно дать простой
ответ, потому что мозг быстрее учится и запоминает, когда ему приходится что-
то делать. Невозможно накачать мышцы, наблюдая за тем, как занимаются дру­
гие. Однако мы позаботились о том, чтобы усилия читателей были приложены
в верном направлении. Вам не придется ломать голову над невразумительными
примерами или разбираться в сложном, перенасыщенном техническим жарго­
ном или слишком лаконичном тексте.
В историях, примерах, на картинках использованы антропоморфные образы.
Ведь вы человек, и ваш мозг уделяет больше внимания людям, а не вещам.

28 введение
введение

Что мо)кете сделать ВЫ, чтобы


заставить сбой мозг поВиноВаться
Мы свое дело сделали. Остальное за вами. Эти советы станут
отправной точкой; прислушайтесь к своему мозгу и определите,
что вам подходит, а что не подходит. Пробуйте новое.
Вырежьте ы прикрепите
НД холодильник.

^ Не торопитесь. Чем больше вы поймете, тем Говорите вслух.


меньше придется запоминать.
Речь активизирует другие участки мозга. Если
Просто читать недостаточно. Когда книга задает вы пытаетесь что-то понять или получше запом­
вам вопрос, не переходите к ответу. Представьте, нить, произнесите вслух. А еще лучше — попро­
что кто-то действительно задает вам вопрос. Чем буйте объяснить кому-нибудь другому. Вы будете
глубже ваш мозг будет мыслить, тем скорее вы быстрее усваивать материал и, возможно, от­
поймете и запомните материал. кроете для себя что-то новое.
Выполняйте упражнения, делайте заметки. Прислушивайтесь к своему мозгу.
Мы включили упражнения в книгу, но выпол­ Следите за тем, когда ваш мозг начинает уста­
нять их за вас не собираемся. И не разглядывайте вать. Если вы начинаете поверхностно воспри­
упражнения. Берите карандаш и пишите. Фи­ нимать материал или забываете только что про­
зические действия во времяучения повышают его читанное —пора сделать перерыв.
эффективность.

Читайте врезки. Чувствуйте!


Это значит: читайте всё. Врежи —часть основного Ваш мозг должен знать, что материал книги
материала! Не пропускайте их. действительно важен. Переживайте за героев
наших историй. Придумывайте собственные
подписи к фотографиям. Поморщиться над не­
^ Не читайте другие книги после этой перед сном. удачной шуткой все равно лучше, чем не почув­
ствовать ничего.
Часть обучения (особенно перенос информации
в долгосрочную память) происходит после того,
как вы откладываете книгу. Ваш мозг не сразу ус- Пишите программы!
ваивает информацию. Если во время обработки
поступит новая информация, часть того, что вы Научиться программировать можно только од­
узнали ранее, может быть потеряна. ним способом: писать код. Именно этим вам
предстоит заняться, читая книгу. Подобные на­
выки лучше всего закрепляются практикой. В
Пейте воду, и побольше. каждой главе вы найдете упражнения. Не пропу­
скайте их. Не бойтесь подсмотреть в решение
Мозг лучше всего работает в условиях высокой задачи, если не знаете, что делать дальше! (Ино­
влажности. Дегидратация (которая может на­ гда можно застрять на элементарном.) Но все
ступить еще до того, как вы почувствуете жажду) равно пытайтесь решать задачи самостоятельно.
снижает когнитивные функции. Пока ваш код не начнет работать, не стоит пере­
ходить к следующим страницам книги.

дал ьш е > 29
как работать с этой книгой

Ч то Вам потребуется
Эта книга написана при помощи Visual C# 2010 Express Edition, работающей с C# 4.0 и .NET Frame­
work 4.0. Именно в этом приложении были сняты все скриншоты, поэтому мы рекомендуем устано­
вить себе именно его. Если у вас установлена Visual Studio 2010 Professional, Premium, Ultimate или Test
Professional-версия, картинки будут отличаться. Где необходимо, это будет оговариваться. Бесплатно ска­
чать версию Express можно с сайта Microsoft.

Настройка VISUAL STUDIO 2010 EXPRESS EDITION --------------------------------------------------

Скачать и установить версию Visual C# 2010 Express довольно легко. Дистрибутив программы находит­
ся здесь:

h t t p : / /w w w .m ic r o s o f t . c o m /e x p r e s s /d o w n lo a d s /

Для выполнения упражнений из этой книги вам не потребуются никакие дополнительные параметры
установки, но вы можете воспользоваться ими, если хотите.

IfistaMatkm Options
O Q * ^ IO »-2010

Sefect iht Gp^on^ produst(s) you wouki ice to îTBîsi.' Если y вас уст ановле­
^HiCK»softS<^S«ver2IM »Ex|»«ssSenncePadkl(x«4)(0(W fa(H idStze; 104 на долее старая версия
SQL 5 e w Express integrates nith Visu^ Studio to {ïsvkie b e æ ami
Visual Studio, или .NET
a r v e r - d a tie s c capetàtes. Framework, некоторые
примеры из книги м огут
не сработать.

For итога ігАмтвїйіОп, see ftie-

Скачайте и установите Visual C# 2010 Express Edition. Убедитесь, что установка завершена. На вашем
компьютере окажутся интегрированная среда разработки (которую вам предстоит изучить),
NET Framework 4.0 и другие инструменты.

После установки в меню Start появится новая строка: Microsoft Visual C# 2010 Express Edition. Это
команда для вызова ИСР. Вы полностью готовы к работе.

30 введение
введение

Для наглядного пред­


информация оставления сложит
п о н я т и й б кн м ге
исТользун>тся дыа-
Это учебник, а не справочник. Мы намеренно убрали из книги все, что
грйммс>|- I
могло бы помешать изучению материала, над которым вы работаете. И
при первом чтении книги начинать следует с самого начала, потому что
книга предполагает наличие у читателя определенных знаний и опыта.

У пр аж нени я о б язател ь ны к вы пол нению .


Упражнения и задачи являются частью основного содержания книги,
а не дополнительным материалом. Некоторые помогают запомнить
новую информацию, некоторые —лучше понять ее, а некоторые — на­
учиться применять ее на практике. Необязательными являются только
«Ребусы в бассейне», но следует помнить, что они хорошо развивают ло­ ise rrt
гическое мышление.
В ы полняйте
все упражнения
П о в торени е при м еняется нам еренно. этого раздела-
У книг этой серии есть одна принципиальная особенность: мы хотим,
чтобы вы действительно хорошо усвоили материал. И чтобы вы запом­ Возьми в руку карандаш
нили все, что узнали. Большинство справочников не ставит своей целью
успешное запоминание, но это не справочник, а учебник, поэтому неко­
торые концепции излагаются в книге по несколько раз.

В ы пол няйте все уп раж нени я! Не >^ропускайте эти

Предполагается, что читатели этой книги хотят научиться программи­


хот ит е изучить С*.
ровать на С#. Что им не терпится приступить к написанию кода. И мы
дали им массу возможностей сделать это. В фрагментах, помеченных
значком Ут^ажнение!, демонстрируется пошаговое решение конкретных
К
задач. А вот картинка с кроссовками сигнализирует о необходимости са­
мостоятельного поиска решения. Не бойтесь подглядывать на страницу нение
с ответом! Просто помните, что информация лучше всего усваивается,
когда вы пытаетесь решать задачки без посторонней помощи. А э т и м значком
помечены до­
Код для упражнений из книги можно скачать здесь полнительные
h t t p ; / / w w w .h e a d f ir s t la b s . c o m /b o o k s /h f c s h a r p / упражнения для
любителей логи­
ческих задач.
У п р аж н ен и я «М озгов ой ш турм » не и м ею т ответов.
В некоторых из них правильного ответа вообще нет, в других вы должны
сами решить, насколько правильны ваши ответы (это является частью
процесса обучения). В некоторых упражнениях «Мозговой штурм» при­
водятся подсказки, которые помогут вам найти нужное направление.

д ал ьш е ► 31
обзор команды

Технические рецензенты

!<р,'UC
Лиза Кельнер ^»рроус

Здесь нет ф от огра­


фий Джо Албахари^
Джея Хилярда, Аяма
Синга, Теодоры, Пете­
ра Ричи, Вилла М е-
тельски, Энди Паркера,
Вейна Средни, Дэйва
J\a?!ud Стерлинг Мэрдока, Бриджит-
Жули Ландерс. Особая
Лаладцнс благодарность Джону
Скиту за его предло­
жения по улучшению
первого издания!

Дэвид действи­
тельно нам помог,
показав некоторые,
приемы работы
с ИСР.

При работе над этой книгой мы столкнулись с массой неточностей, вопросов, проблем, опечаток
и даже ужасных математических ошибок. Впрочем... все было не так ужасно, как можно подумать. Но
мы все равно очень благодарны за ту работу, которую выполнили наши технические рецензенты. Кни­
га вышла бы в свет с ошибками (парочка из которых довольно серьезны), если бы у нас не было самой
лучшей на свете команды...

Прежде всего хотелось бы сказать большое спасибо Крису Барроусу и Дэвиду Стерлингу за техни­
ческое сопровождение. Также хотелось бы поблагодарить Лизу Кельнер —это уже шестой обзор, ко­
торый она для нас делает, и именно она сделала конечный продукт настолько читабельным. Спасибо,
Лиза! И особая благодарность Нику Паладине.
Крис Барроус —разработчик компании Microsoft из команды компиляторов С#. Его специализация —
дизайн и реализация различных свойств языка C# 4.0.
Девид Стерлинг работает в команде компиляторов Visual C# последние три года.
Николас Паладине был одним из наиболее ценных специалистов Microsoft, занимавшихся .NET/С # . Он
имеет 13-летний опыт в программировании, особенно в позиционировании технологий Microsoft.

32 введение
введение

Благодарности
Редакторы
Мы хотим поблагодарить наших редакторов Б р е т т а
Маклафлина и Кортни Нэш за работу над этой книгой.
Бретт был автором большинства рассказов, а идея главы 14
принадлежит ему полностью. Спасибо!

Брет т Маклафлин

Кортни Нэш

Команда издателг>ства O’Reilly

Л у —замечательный дизайнер, постигший все секреты своей про­


фессии. Она провела незабываемые часы и сделала для книги
потрясающие графические фрагменты. Если вы видите в книге
нечто фантастическое — благодарите ее (и ее умение работать
с InDesign). Все комиксы являются ее творением. Спасибо огром­
ное, Лу! С тобой потрясающе приятно работать!

В O ’Reilly очень много сотрудников, которых мы хотели бы по­


благодарить, надеемся, что никого не забыли. Особое спасибо
выпускающему редактору Рашель Монаган и индексатору Люси
Хаскинс, Эмили Квил за отменную корректуру, Рону Билодо, кото­
рый не жалел своего времени на экспертные заключения, Сандер­
су Клейнфелду за предложение проверить все еще один раз —всем,
кто помог подготовить эту книгу к печати в рекордно короткие сро­
ки. Мы любим Мэри Треслер и ждем случая поработать с ней снова!
Жмем руку нашим друзьям и редакторам Энди Сраму и Майку Хен­
дриксону. И за то, что вы сейчас читаете эту книгу, нужно поблаго­
дарить лучшую команду рекламистов: Марси Хенон, Сару Пейтон,
Мэри Ротман, Джессику Бойд, Кетрин Барет и остальных сотруд­
ников из города Севастополь, штат Калифорния.
Т
Сандерс Клейнфелд
д ал ьш е > 33
1 =*екш иБ ность с

Визуальные приложения
за 10 минут

Хотите программировать действительно быстро? C # — это


м о щ н ы й я з ы к пр ограм м и ровани я. Благодаря V isu a l S tu d io вам
не потребуется писать непонятный код, чтобы заставить кнопку ра­
ботать. Вместо того чтобы запоминать параметры метода для имени
и для ярлыка кнопки, вы сможете с ф о к у с и р о в а т ь с я на д о ст и ж е­
н ии результ ат а. Звучит зам анчиво? Тогда переверните страницу
и приступим к делу.
с c# это легко

Зачем В ш изучать C #
lACPj или Интегрированная
C# и ИСР Visual Studio облегчают и ускоряют процесс Среда Разработки, --
написания кода. э т о программа для
редактирования кода,
управления файлами
_ , ^ и публикации проектов.
Задачи, которые за бас решает UCP
Чтобы поместить на форму кнопку, вам потре­
v o i d I n i t i a H .e C o m p o n e n tT ,
буются большие куски повторяющегося кода.
th is .SuspSi, |y“ “ , f ■W ndow B . P o rm a. B u tto n () ;
/ / b u tto n l

56) ;
in ,.s i.e „ s , .3 , ,

/ / Pom .1 "'“ ™ '‘^ ' " ’“ “ ‘* l « < t h i a . b u t t o n l _ c l i c k ) , .

‘ s t a t i c claB B p ro g ra m

'/ / S ” S S ’ e n try p o in t f o r th e a p p lic a tio n .


/// < / sum m ary>
S S ’^^f.MainO th is .R e s u m e L a y o u t(fa ls e );
' Wplic.tion.|ngleVi-al|tyl«^^^^

) f

Преимущества Visual Studio u C #


Язык С#, оптимизированный для программиро­
вания в Windows, вместе с Visual Studio позволяет
сфокусироваться на непосредственных задачах.
T ‘ТОЛЬКО личще,
f ‘глядит, но a
^<^'cmpee создается.
'I
i ÿ Your Program ЗЖз
Решения .NET
Framework

Встроенные в С*
j^g-f FrAmeworK
C =3I ИжЛ

How good"’ ©
)Let% get Parted!]

finort © Better # 8ей


i

i/ir p V iS M ^ !
УЛе
Studio с т р и ^ ' 0 - , - .................—
избавляют » f .
0^ рутинное paoom
Aft ritfht! ЬйИКПШ ■■••■■■•*■:

доступ К данным

36 глава 1
эффективность с C#

С#, UCP Visual Studio многое ynpoutaiom


Язык C# и Visual Studio позволят без дополни­
тельных усилий выполнять следующие задачи:

Быстро создавать прилож ения. Программировать на C# очень просто.


Это мощный, легко осваиваемый язык, а Visual Studio позволяет автома­
тизировать большинство процессов.

О Разрабатывать красивы й пользовательский интерд>ейс. Инструмент


Form Designer в Visual Studio превращает создание великолепного поль­
зовательского интерфейса в одну из самых увлекательных задач при раз­
работке приложений на С#. Вам больше не потребуется тратить часы на
написание графических элементов с нуля.

О Создавать базы данны х и взаимодействовать с н и м и . ИСР снабже­


на простым интерфейсом для создания баз данных, которые затем легко
интегрируются в SQL Server Compact Edition и другие популярные при­
ложения.

1окусироваться на реш ении Р Е А Л Ь Н Ы Х проблем . За конечный ре­


зультат работы, разумеется, отвечаете вы и только вы. Но ИСР позволя­
ет концентрироваться на глобальных вещах, взяв на себя:
★ слежение за всеми проектами;
★ упрощенное редактирование кода;
★ отслеживание графики, аудиофайлов, значков и прочих ресурсов;
★ управление базами данных и взаимодействие с ними.
Теперь вместо рутинного написания кода вы можете потратить время на
создание потрясающих программ.
Скоро бы поймет е,
что мы имеем в виду.

дальше > 37
помогите начальнику

11зба6ьте директора о т бумаг


в объектвильской фирме по производству бумаги появился новый исполни­
тельный директор. Он любит пешие прогулки, кофе и природу... и с целью
сохранения лесов решил перейти на безбумажный документооборот. На вы­
ходные он уехал кататься на лыжах в Аспен, а вам приказал к понедельни­
ку написать программу для хранения контактной информации. Если этого
не сделать... хм... вы составите компанию предыдущему директору вашей
фирмы, который ищет новую работу.

Найдите способ
Имя: Laverne Smith быстро перекинуть
эти данные на
Фирма: XYZ Industries ноутбук директора.
Телефон; ( z i z ) s s s - s i z ‘i
Email: LavemeSmith^XyZindustriescoM
Клиент; Yes Поел, звонок: 05/я& /07

“ " Т “", .Г'

..,- 1 -W « * ,

■Muirtiiiiln'ir чш —

38 глава 1
эффективность с С#

Перед началом работы быясните, что именно


ну)кно пользователю
Перед началом работы над любым приложением нужно понять,
кто будет его использовать и каким ожиданиям оно должно со­
ответствовать.

Директор хочет, чтобы программа работала не толь­


ко на его офисном компьютере, но и на его ноутбуке.
Значит, вам потребуется инсталлятор, который по­
зволит поместить нужные файлы на любой компью­
тер.
Так как приложение должно
работам.^ не молько в офисе,
но и на ноутбуке директора,
без установщика не обойтись.

Перед началом работы


над программой всегда
Другим сотрудникам фирмы также понадобится до­ думайте о нуждах
ступ к контактной информации. Как же в против­
ном случае они сформируют списки рассылки, что­ конечных пользователей,
бы сделать фирму лидером по продаже бумаги.
только так можно
создать по-настоящему
качественный продукт!

дальше ► 39
ваша цель

Ч то мы собираемся сделать
Нам потребуется приложение с графическим интерфейсом
пользователя, объекты, взаимодействующие с базой дан­
ных, собственно база данных и программа установки. Вся эта
огромная с виду работа будет проделана к концу этой главы.
Вот структура нашей будущей программы:

Ве>|
элем еи ^б
упрлбдения.

Визуальные объекты .НЕТ Объекты б»ы данных .N£T | к о „ ,к д а u p d a te |

Команда DELETE ^

ОЭьектВ«'*''

Э т и обьекты являются
элементами управления
адресной книги. 'Ш ^ обгр

\гШ у -
„ „ „ Z I

40 глава 1
эффективность с С#

Готовая программа
поме-щаетсй внутрь
инсталлятора \лЛпйо^5.

Хранилище данных инструм енты Внедрения

.е х е

Ол
б а з а данны х
I Файл
программы

Отде -
лу продаж
'ч останется
4 л >^олько за ~
1л уст ит ь
эт от ин­
сталлятор.

дальше > 41
приступим

Э то Вы делаете 6 Visual Studio


Запустите Visual Studio, если вы еще это не сделали. Пропустите начальную страницу и выберите в меню
File команду New Project. В открывшемся окне диалога New Project выберите тип проекта W indow s Form s
Application, а в текстовое поле Name в нижней части окна введите название проекта Contacts.

....
iSrai^hbsSailedTempiatti
Im t^ T e m p t^ e s
№tdov«sFt»msAf)p<»catK3n Visuet С *
УвиЫС»
А pro^ect fof <feetjng an äpplicebon with »
Windev« Forms user tnteiface
Q a isLibn ry Visual С *

; WPFApplicrtksn \ftsuel С *

! WFBfovwerA4>p»icatk>n Visual C# о С Ш о |= > о Ж Н ь 1


Consol« Application VtsuelC#

; : Empty Project Visu»! С *


В вашей ИСР все может
выглядеть по-другому.
На рисунке показан вид
окна New Project в Visual
Studio 2010 Express Edition.
В Professional или Team
Foundation edition оно мо­
жет выглядеть по-другому.
Но суть его от этого
не меняется.

Команда Save All


А это Visual Studio делает за Вас ■из меню File сохраняет
все открытые файлы,
в момент сохранения проекта ИСР создает файлы F o rm l. c s, в то время как команда
F o rm l. D e s i g n e r . c s и P rogram , cs. Они добавляются в окно Save — только файл,
Solution Explorer и по умолчанию сохраняются в папке Му активный в данный
D o c u m e n ts\V isu a l S tu d io 2 0 1 0 \ P r o j e c t s \ C o n t a c t s \ . момент.
Эт от файл содержит
код, определяющий
поведение формы.
^Од, определяющий
форму и ее объекты.

at

Forml.cs Forml.Deslgner.cs
ßce эт и файлы Visual Studio
создает автоматически.
42 глава 1
эффективность с C#
Возьми в руку карандаш
« .В О З І
Ниже показан возможный вид экрана. Вы должны понимать назначение большинства окон
ч и файлов. Убедитесь в наличии панелей Toolbox и Error List, вызываемых одноименными
командами меню View » Other Windows. В пустые строки впишите назначение каждой части
ИСР, как показано в примере.

Если ваша ИСР выглядит


по-другому, воспользуйтесь Это окно было
^^.^■й^.выбираются командой W indow »R eset увеличено,, чтобы
дать вам больше
работ ы ................. пространства.

дальше > 43
изучи И СР

Возьми в руку карандаш


6Ш6НИ6 Итак, вы описали назначение различных частей ИСР Visual Studio С#.
Здесь вы можете сравнить написанное с правильными вариантами
ответа.
Здесь выдираются

ЗЭесь собраны
визуальные
элементы
управления, ^
которые \ ®
можно
перетащить
на форму.
здесь
\^оказаны
свойства
выбранного
эдеменилд
управления
суормы-

В окне E rror List Щ ел чок на этой


отображаются
вкравшиеся в коо кнопке вклю чает
оилибки. и отклю чает
ф ункци ю
ав то м атического
сворачивания
окна. У панели
Toolbox она
вклю чена
Form i.cs и по ум олчанию .
•появляются

44 глава 1
эффективность с C#

_ Часзцо
ЧадаБаеМые
Б оЦ Р о Сь !

Если код создается автоматически, я загрузил и установил бесплат­ Окно ИСР отличается от показан­
не сводится ли изучение C# к изучению ную версию Visual Studio Express. ного на картинке в книге. Что делать?
функциональности ИСР? Достаточно ли этого для выполнения
упражнений из данной книги или мне ; Для перехода к настройкам по умол­
I Нет. ИСР поможет вам в выборе
потребуется купить другую версию чанию выберите команду Reset Window
начальных точек или измении свойств
приложения? Layout в меню Window, затем восполь­
элементов управления форм, но понять, зуйтесь командами меню View » Other
какую работу должна выполнять про­ ! Все упражнения из этой книги вы­ Windows, чтобы открыть недостающие
грамма и как достичь поставленной цели, полняются в бесплатной версии Visual окна.
можете только вы. Studio (которую можно получить на сайте
Microsoft). Различия между версиями
Express, Professional и Team Foundation
Я создал новый проект, но не никак не повлияют на процесс написания
нашел его в папке Projects, вложенной
в папку Му Documents. Почему?
профамм на C# и создания полнофункцио­
нальных приложений.
Visual Studio
Q ; Новые проекты Visual Studio 2010 генерирует код,
Express помещает в папку L o c a l
Mojy ли я переименовывать фай­
S e ttin g s \A p p lic a tio n
D a t a \ T e m p o r a r y P r o j e c t s . При
лы, созданный ИСР?
который можно
первом сохранении вам предлагается Q ; Конечно. Новому проекту ИСР
задать новое имя и поместить в папку по умолчанию присваивает имя F o r m l использовать
Му D o c u m e n t s \ V i s u a l S t u d i o (F o r m l. c s , F o r m l. D e s i g n e r . c s
2 0 l 0 \ P r o j e c t s . При открытии нового
проекта или закрытии временного, вам
и F o r m l . r e s x ) . Ho его можно поменять
в окне Solution Explorer. По умолчанию как основу для
предлагается сохранить или удалить имена файлов совпадают с именем
последний. (ПРИМЕЧАНИЕ: Отличные формы. Как можно видеть в окне Properties,
их изменение не влияет на имя формы.
программы.
от Express версии Visual Studio не ис­
пользуют папку временных проектов, Последнее меняется изменением поля
а помещают файлы непосредственно (Name) в окне Properties. Имя файла при
в папку Projects!) этом остается тем же.
Но только вы
Для файлов, форм (и других частей про­
Что делать с ненужным кодом,
автоматически созданным ИСР?
граммы) можно выбирать произвольные
имена. Выбор значимых имен облегчает
отвечаете за
работу с программой, но об этом пока
; Его можно отредактировать. Если можно не думать. корректную работу
по умолчанию создан не тот код, который
вам требуется, достаточно внести в него
необходимые изменения — вручную или
это11 программы.
средствами пользовательского интерфей­
са ИСР.

дальше > 45
лучше один раз увидеть

Создаем пользовательский интерфейс


Добавление элементов управления в ИСР Visual Studio
осуществляется методом Drag&Drop (перетащить и бро­
сить). Рассмотрим процесс добавления логотипа к форме:

Q Э лем ент управления PictureB ox.


Перетащите на форму элемент управления PictureBox
из окна Toolbox. ИСР при этом добавит в файл
F o rm l. D e s i g n e r . c s код нового изображения.

Чтобы увидеть ^ ^ Start Рзд


список инстру­ & All W indows Forms
ментов, наведите л C om m on Controfs
указатель мыши Pointer
на слово Toolbox @ Button
в верхнем левом S Checkeox
углу ИСР. Если §3 CheckedLtstBox
оно отсутствует, Э ComboBox
выберите команду DateTtmePjcfcer
Toolbox в меню A Labet
View. A ImkLabel
51 ListBox
ListVtew
Й M askedlextBox
^ M onthC stendar
Tt! Notr^kon

^ PictureBox —

l‘' W ¥ \ \ .
0 RaAfcbt\toi
RkhTextBox
Щ. TextBox

0 файле Forml.Designer.es.

вы не им еете представления
разработке пользовательских ин­
терф ей сов, ничего страшного.

Forml.Designer.cs О разработке интерфейсов мы поговорим позднее. На данном


этапе сосредоточьтесь на поведении создаваемых элементов
управления.

46 глава 1
эффективность с C#

Визуальные Kj}3KI]AU^e Иншрумейгпы


объекты данных
.NET

здесь

Режим Zoom для элем ента P ictureB ox.


О Доступ к свойствам элемента управления осу­
ществляется щелчком на маленькой черной
стрелке. Для свойства Size элемента PictureBox
выберите тип Zoom: (Дедчок на
м аленькой
С Pormi шш
Задать разАА&р S P « г»'“
эл ем ен та @ W i e r ^ 1r«d«s 9 o c m y i^ /
можно б окне свойст оам
I Choose Im sge. эл е м е н т а
prope-rtie-s-
Щелчок управления.
Size M o d e
на черной
Dock in Par
стр ел ке datm
бы ст рш
доступ к общим
свойствам
элементов.

Щелчок на строчке Choose Imaa^

e Зогрузка л о го ти п а фирмы по производству б у м аги .


Архив с логотипом находится на сайте ( h t t p ; / /w w w . h e a d f I r s t l a b s . c o m / b o o k s / h f c s h a r p /
H F C s h a r p _ g r a p h i c s _ c h 0 1 .z i p ) . Сохраните его на жестком диске своего компьютера. Щел­
кните на черной стрелке элемента PictureBox и выберите в появившемся меню команду Choose
Image. Откроется окно диалога Select Resources. Установите переключатель в положение Local
Resource и щелкните на кнопке Import... Остается только указать путь к файлу с картинкой.

дальше > 47
экономим ресурсы

Visual Studio, за сценой



Кажое ваше действие Visual Studio сопровождается написанием
кода. В момент загрузки логотипа Visual Studio создала ресурс
и связала его с вашим приложением. Ресурсом называется лю­
бой графический или звуковой файл, значок или другие данные, Q -
подгружаемые к приложению. Логотип интегрировался в про­
грамму, и при открытии на другом компьютере элемент управле­
ния PictureBox сможет его использовать.
В момент перетаскивания элемента управления PictureBox на
форму для хранения данного ресурса и его связи с проектом ав­
Логотип стал
/
томатически был создан файл F o rm l. r e s x . Двойной щелчок на
ресурсом приложения
имени этого файла позволит увидеть импортированное изобра­ Contact List.
жение.

S o lu tr o n Explorer в окне Solution Explorer щелкните на квадратике


со знаком «плюс» слева от имени файла F o rm l. cs.
Это откроет доступ к файлам: Form l .D e s ig n e r , c s
Solution 'C on ta cts' CI project) и F o r m l.r e s x . Дважды щелкните на файле F orm l.
d C m te c te r e s x , затем на стрелке рядом со словом Strings и вы­
берите в меню вариант Images (или нажмите Ctrl-2),
|> i l i Properties
чтобы увидеть импортированный логотип.
b 9 References
Л Ц ] F o rm l.cs
^ Form l.Designer.es Вторая кнопка
Form l.resx X I
Im port в окне
Form l.resc* Select Resource
i ü Im ages » J| A d d Resource
Program .cs помещает
логот ип в папку
Resources окна
Solution Explorer.
Чтобы исправить
ситуацию,
установите
переключатель
Local Resource
и повторно
импорт ируйт е
файл.

Этот файл ИСР


эт о
создала при импорте
созЗанные - изображения. Сюда
рйНйв'
помещаются любые
Forml.resx ресурсы, связанные
Program.cs
с формой.

48 глава 1
эффективность с C#

Редактирование кода
Иногда в автоматически созданный код требуется внести из­
менения. Рассмотрим эту процедуру на примере добавления
к логотипу всплывающего текста.
При редактировании формы двойной щелчок на любом эле­
менте управления автоматически добавляет в проект код.
Дважды щелкните на фрейме PictureBox и вы увидите код, ко­
торый выполняется при каждом щелчке на данном элементе
управления. Этот код должен выглядеть примерно так:

p u b lic p a r t ia l c la s s F orm l : F orm


{
p u b lic F o r m l {)
при каждом щелчке
пользователя на логотипе.
I n itia liz e C o m p o n e n tO ;
1 Название метода
^ , п о к а з ы б а ^ ^ 1^ л ч к о М
p r iv a te v o id p ic tu r e B o x l_ C lic k (o b je c t sen d er, E v e n tA r g s e) ^ а п у с к а е т ^ ^ ^ p ic tu r e B o X .
---- ------------------. на
U/2 элементе
{
M e ssa g e B o x .S h o w ("C o n ta o t L i s t 1 . 0 . \ n W r i t t e n bys Zour Name". "A b o u t");

}
Введите эт от код Пи введя код, щелкните
V
После двойного щелчка на кнопке Save панели
инструментов ИСР
на элементе РкЫгеВок выберите команду Save
курсор окажется
здесь. При вводе в меню File.
кода игнорируйте
всплывающие окна.
Ч асзц о

^аД аБаеМ ы е

Что такое метод? Зачем нужен символ \п ?

О ! Методом называется именованный блок


кода. Более подробную информацию вы по­
О ! Это знак переноса строки. Он показыва­
ет, что Contact List 1.0 будет написан на стро­
лучите в главе 2. ке, а Written by: записывается с новой строки.

дальше k 49
запуск приложения (наконец-то!)

Пербый запуск прило)кения


Нажмите кнопку F5 или щелкните на кнопке с зеленой
стрелкой I (^ ) на панели инструментов, чтобы про­
тестировать результат своей работы. (Это называется
«отладкой», то есть запуском программы в ИСР.) Для
остановки процесса выберите Stop Debugging в меню
Debug или щелкните на кнопш j на панели инстру­ Все три кнопки
ментов. работают, и вам
не ^^отребовало,^
сг, ; IS : а J^ucamb для этого код.

About
Щелчок на логотипе
вызывает только
что созданное окно
C o n tact List 1,0. About.
W ritten b f. Your N am e

OK

_ Часвдо

--------- ^ а Д а В а е М ы е ------------
Где )ke Mou файлы? B onj=*oc:bi
П ри запуске программы Visual в моей ИСР кнопка с зеленой стрелкой
Studio копирует файлы в папку С* превращает называется Debug (Начать отладку). Она
Му D o c u m e n ts \V is u a l S t u d io вашу программу запустит программу?
20 1 0 \P ro je c ts \C o n ta c ts \ в исполняемый
айл, находящийся
C o n t a c t s \ b i n \ d e b u g . Дважды щелк-
ните на созданном ИСР файле .ехе, Г папке debug. • Конечно. Щелчок на ней приведет к вы­
полнению программы внутри ИСР, что в дан­

/
и программа начнет работать. ный момент вам, собственно, и требуется.
О процедуре отладки мы поговорим позднее.

с»
Я не вижу на панели инструментов
сй 1=orm1 Ш] кнопку Stop Debugging. Что делать?
Designer.cs шти
Form1.cs Forml.resx Properties
Q l Эта кнопка появляется только при запу­
Это не ошибка. Существуют два щенной на выполнение программе. Запустите
уровня папок, файлы с кодом С # приложение, и вы увидите, как она появится.
находятся во внутренних папках.

50 глава 1
эффективность с C#

Bom что у)ке сделано


Итак, мы создали форму и объект PictureBox, щелчок на ко­
тором вызывает диалоговое окно с текстом. Теперь нужно
добавить поля с информацией; имена и телефоны из списка
контактов.
Эту информацию мы сохраним в базе данных. Visual Studio со­
единит с ней поля, то есть нам не потребуется вручную писать
код для доступа к этой базе. Сейчас мы займемся ее написани­
ем, то есть перейдем от раздела Визуальные объекты .NET пря­
мо к разделу Запись данных.

Визуальные
объекты .NET
Объекты Хранилище иис1л|»умеиты
в)<ез{^ения

П оэтому снячйЛй
Мы не можем нцжно написать
соединить форму базу Wзаполнить
с базой данных, так ее данными.
как база данных пока
от сут ст вует .
Э т о мы уже
сделали... которое УД

с 9анн1?|Мм м3 олз

Visual Studio автоматически сгенерирует для вас код,


соединяющи!! форму С базой данных, но СНАЧАЛА
нужно создать саму базу.
дальше ► 51
сохраните, чтобы потом использовать

Для хранения информации ну>кна база данных


Код, связывающий форму с данными ИСР, гене­
рируется автоматически, так что нам нужно все­ УЗеЗцилесь, иило
го лишь:

О Добавить к проекту базу данны х S Q L .


В окне Solution Explorer щелкните правой
кнопкой мыши на проекте Contacts, выбе­
рите команду A d d » N ew Item. В открывшемся Э т о ф айл SQ L
нашей новой
окне выделите строчку Local database, а в поле базы данных-
Name укажите имя файла ContactDB.sdf.
ContactDB.sdf
AebSNew№
em-C&ftacts ТШШ

SetBigsR Tfpt: Vbu»IC* Herns

Выбрав Local ResouKUFite VeoalC#Bemi


Database,
вы созда­ ё etFile
T V'«uatC#Kems
дите файл Interfec* Vi«ualC»items
SQ L Server Vt5ue!C«Rems
Compact Й Cod«F><« VutulC*(terns файле»! базы
Edition, в ко­ Oess Visua«C«tte<T.s данных SQL
тором и будет
3 W fndowsForm S e r v e r C o p v ip a c t
храниться E d itio n и м е ю т
наша база. & Det«Set Visu*JC*hemi расш ирение SUi'-
About8ox VoulC«Items
■й-
-Э"
, rtsuatOfltenjs -
Visual C * hems

0 Щ елкните на кнопке Add в ни ж ней части


окна A dd N ew It e m .

О З акройте м астер D a ta Source C onfiguration.


Рудыпе
Щ елкните на кнопке Cancel, так как в данный
момент мы не будем останавливаться на конфи­ оС Ш ор»оЖ Н ь1
гурации источника данных.
В версиях приложения, отличных от
Express, вместо окна Database Explorer
Q П о см о тр и те на полученны й результат. фигурирует окно Server Explorer
В списке файлов окна Solution Explorer должен Окно Server Explorer в версиях
появиться файл ContactDB.sdf. Дважды щелкни­ Professional и Team Foundation editions
те на его имени и посмотрите на левую часть сохранив всю функциональность окна
экрана. Вместо окна Toolbox там окно Database Database Explorer, позволяет просматри­
Explorer. вать сетевые данные.

52 глава 1
эффективность с C#

Визуальные Объекты ХранцАище инструменты


База данных, созданная UCP бнвзрвнця

База данных SQL — это способ систематизиро­


ванного хранения информации. ИСР дает вам Uv
все инструменты для управления как данными,
так и самой базой.
Данные в базе хранятся в виде таблиц. Столбцы
разделяют данные по категориям (например,
«имя» или «номер телефона»), а строки содержат
информацию о каждом контакте.

База SQL содержит


оаши данные,
информацию о5 их
ст рукт уре и код
доступа к ним.
Вами Злин^!е
хранятся 6 виде
Эикаммиеском
илаблице'- процедуры

Язык SQ L
SQL расшифровывается как Structured Query
Language —язык структурированных запросов.
Этот файл содержит базу
Он предназначен для доступа к данным в базах SQL. Именно 3dect> будут
и имеет собственный синтаксис и структуру. Код сохранены наши таблицы
SQL представлен в виде операторов (statements) SQ L и данные.
и запросов (queries), а также хранимых проце­
дур (stored procedures). ИСР генерирует за вас
операторы SQL и хранимые процедуры, позво­ ContactDB.sdf
ляя создаваемому вами приложению осущест­
влять доступ к данным базы.

дальше ► 53
хранить данные легко

Создание таблицы для списка контактов


Теперь базу нужно наполнить данными. Обычно использу­
ется представление в виде таблицы, поэтому сначала соз­ Ч асто

дадим таблицу People, в которую и поместим контактную ,аДаБаеМые


информацию: B o lïp o C jjI

• Что же такое столбец?


Д обавление таблицы
Щелкните правой кнопкой мыши на строчке Q * Столбец — это одно из полей та­
Tables в окне Database Explorer и выберите ко­ блицы. В таблице People можно создать
манду Create Table. столбцы FirstName и LastName, относящи­
еся к типу String или Date или Bool.
Database Exptorer - X

• Зачем нам столбец Соп1ас1Ю?


Dffta Connections
M ySj ContactDB.sdf
^ ! Он позволит присвоить уникальный
i> ______,___ номер каждой записи в таблицах базы. Так
!> iJJ R- Create Table как мы сохраняем контактные данные от­
New Query дельных людей, имеет смысл их пронуме­
ровать.
i^ Refresh
i S Properties Alt+Enter

Что такое тип данных Int?


T
Теперь к таблице нужно добавить столбцы. Нач­
Ql Int — это сокращенние от Integer
(целое число). То есть в столбце ContactID
нем со столбца ContactID, чтобы присвоить каж­ могут содержаться только целые числа.
дому контакту уникальный номер.

Информации слишком много, дол­


О Создание столбца C o n ta c tID
жен ли я во всем этом разбираться?
Введите в поле Column Name название
ContactID, а в раскрываюш;емся списке Data
^ ! Ничего страшного, если на данном
Туре выберите вариант Int. В списке Allow Nulls
этапе что-то остается непонятным. Пока
должен быть выбран вариант No.
что ваша основная задача — получение
В списке Primary Key выберите вариант Yes. То навыков работы с ИСР Visual Studio. До­
есть именно это поле будет использоваться в ка­ статочно научиться подготавливать фор­
мы и запускать программу (Если же вы
честве уникального идентификатора записи.
хотите узнать больше о базах данных, по­
читайте, например, книгу «Изучаем SQL».
I Column Name Data Type Nüils Unique Primary
ContactiD Yes
Ji8L

T
А о б а в ^ е столбец ContactID с т ипом данных int. В списке Allow Nulls
М е р и т е вариант No, а в списках Unique и Primary Key вариант Yes.

54 глава 1
эффективность с C#

Визу»л11ные Объекты Храимлище 1!яетрумвншы


объекты 6«Ь1 jasBWx данных Внез(;ения
■NET .NET

О Генерация идентиф икационны х номеров


Информация в столбце ContactID будет использо­
ваться только базой данных, нам она не требует­
ся, ее имеет смысл генерировать автоматически.
I
В окне свойств присвойте свойству Identity зна­
чение True, чтобы сделать столбец ContactID
идентификатором для всей таблицы. ^ Ь 1 здесь
В поле Name в верхней части окна введите зна­
чение People.

в о кк. з о г « " «
U

с ■3 E^i^tTriЯe-^*eop^e

ш
Generei
iJ Rdresh
Istame
ColumnName
М
е}р
Peopie
Datatype length AllowNutts Uniqu« PrtmaryKey
Первичный ключ
является ун и ­
кальным иден­
тификатором
записи, поэтому
4 Mo Yes ¥e он обязательно
должен быть за ­
дан.

1
Ы«-
MicfosoftSQLServerCompact
ContactD6.sdf
Det^Vah«'............
is WmisiffisdsniKOEHtiss Ddaultvafoefor^iiscoSumrv.
добавлении
»овои записи поле
OK Cftncet ^о>л^лсИО будет
обновляться
‘^°<^0Алатически.
8 раскрывающемся списке справа от поля
Identity нужно указат ь значение True,
чтобы сделать столдец Contact!Р
идентификатором записей таблицы.

дальше ► 55
оформим в таблицу

Поля 6 контактной карте — это


столбцы таблицы People
Теперь, когда у нас есть первичный ключ таблицы,
нужно определить все остальные поля. Каждое поле
должно стать столбцом в таблице People.

р е о р '®
Имя: Laverne Smith p.«.
Фирма: XYZ Industries
Телефон: ( z i z ) s s s ~ s x z ‘i
Email; Laverne.Smith@XyZindustries.com
Клиент: Yes Поел, звонок: o s /z & /o i

JLJI
о каждом человеке мы

^оследтго звонт ^'''^'^^

ШТУРМ
Какие проблемы могут возникнуть, если одному
человеку сопоставить несколько записей?

56 глава 1
эффективность с C#

КТ9 И Н Т<ЛеЛ#^Т?
Таблица People готова и для нее уже задан первичный ключ, осталось добавить столбцы для всех
полей с данными. Укажите, к каком типу должны принадлежать данные, и выберите для них вер­
ное описание.

Н азван и е стол бц а Тип дан н ы х О п и сан и е

Последний звонок mt Дата и время

Имя Lit Логический тип


true/false

Идентификатор nVarcliarQQQ) Строка из букв, цифр


и других символов
максимальной длиной
100 знаков

Является ли Jateti:лае Целое число


клиентом?

дальше * 57
это именно мой тип

КТО
Проверьте, правильно ли вы сопоставили типы данных и описания таблицы People.

Название столбца Тип данных Описание

58 глава 1
эффективность с C#

Ввзуальямв Объекты хранилище инструменты


Забершение таблицы объекты 6»зь15«в»ых данных Зне^рения
.NET .NET
...
По аналогии со столбцом ContactID добавьте Г.
еще пять столбцов. Вот как должно выглядеть
окно Edit Table после завершения работы: XV 0
])ы здесь

, Если
, парамет р
i Allow Nulls
имеет
' значение
NOj столбец
не может
оставаться
•пустым.
Поля типа
Bit при -
нимают
значения True ^^ф орм ация
или False и о клиенте
м огут быть может, быть
предст ав­ , неполной,
лены в виде .^ о э т о м у
переключа - o f io u p a c M
теля. значение Yes
Щ елкните на кнопке ОК для сохранения таблицы. Она будет добавлена к ва­
шей базе данных.

ЕМ.?»« •

Щелчок на кнопке^
ОК добавит
таблиц!^ People реор'в
к базе Эанных.

Новая таблица пот


дАя вв% 7 д а н н ь !Т ° ^ "

дальше ► 59
добавляем данные

Перенос 6 базу данных с карточек


Все готово для ввода данных в базу. Для начала
возьмем шесть карточек с контактной информа­
цией от вашего босса. Нужно внести
D atabase Estploret - X
данные с этих
илести карточек
s [ p Data C o n n ec tio n s в таблицу People.
j ContectD B .s(if
Л l J T ables
■ Peop‘-
Ф Щ елкните на квадратике со зна­ h 1 л P.ephcati Drop Tabte
ком «плюс» слева от строчки Table Properties
Tables в окне Database Explorer ttfit Table Schem#
(или Server Explorer), затем NevSfQueiy
щелкните правой кнопкой мыши Q- S how T able Data j
на имени таблицы People и вы­
! Copy Ctrl-i-C
берите команду Show Table Data
Rrfrah
в появившемся меню.
Properties Ait+Er.ter

В центральном окне появится Имя: Ыг Nelson


сетка таблицы, куда нужно вве­
сти данные с карточек. (Изна­ Фирма: JTP
чально во всех ячейках стоит телефон: (40--^)?^^-^^
значение null, введите вместо Erna«-. L.feMelsoneJTP.ORa
него свою информацию.) Стол­
бец ContactID будет заполнен ЗН»' Клиент;
автоматически.

Имя: Luanda Ericson О Paper свич>в(^

Фирма: Ericson Events


Имя: L lo y d J o n e s
Телефон; ( 2 .iz ) s s s - ^ s z s
Фирма: Black Box Inc.
Email: LucySEricsonEvents.info
Телефон: (7 iS )S S S -S & 3 8
Клиент: No Поел, звонок: £35/17/10
Email: LJones@Xblackboxmc-com
Клиент: Yes Поел, звонок: o s /z & /to

60 глава 1
эффективность с C#

Имя: Sarak Kalter ^


Имя: Matt Franks. ( Фирма: Kalter. Riddle and Stoft
Фирма: XYZ Industries Телефон: (6 3 -4 )5 5 5 --5 6 4 1 .
Телефон: (Z X Z )S S S -8 i-Z S Email: Sarah^KRS.org
Email: Matt.Franks&XyZindustries.com Клиент: No Поел, звонок: i z / i o / o s
1Слиент: Yes Поел, звонок: 0 5 /2 6 /1 0

1мя: Laverne Smth.


Фирма находится в США, Фирма: XYZ Industries
поэтому директор записывает
даты как 0 S /Z & /1 0 , что Телефон; ( z i - z ) s s s - s i z ^
означает 2.6 мая ЯОЮ. Вы
можете вводить их в другом Email: Laverne.Smith@XyZindustries.com
формате.
Клиент: Yes Поел, звонок: 04 / 1 1 /1 0

^ После ввода всех данных .


снова выберите в меню File с Г
команду Save All. Записи будут Команда Save Al! сохраняет бее
сохранены в базе данных. открытые файлы, а команда Save
Z Z k o файл, над которым вы
сейчас работаете.

Частно
Л аД аБаеМ ы е
БоЛ роС ь!

Б: Куда попадают введеные мной данные? Становятся ли введенные записи частью моей про­
граммы?
грам
О ; ИСР автоматически сохраняет данные в таблице People
вашей базы данных. При этом таблица, ее столбцы, типы 0: Да, данные — такая же часть программы, как код или
данных и сами данные хранятся в файле ContactDB.sdf, кото­ созданные формы. Файл ContactDB.sdf копируется и хранит­
рый является частью вашего проекта, поэтому ИСР автомати­ ся вместе с исполняемым файлом. Для доступа к данным
чески обновляет его при внесении любых изменений. приложение читает файл ContactDB.sdf и вносит в него новые
записи.

^ в«™ S<?L .____

3
ContaetDB.sdf

дальше k 61
все данные в одном месте

источник данных соединит форму с базой


Все готово для создания объекта базы данных .NET, который будет использовать для взаимодействия
с базой. Нам потребуется источник данных. Он представляет собой всего лишь набор операторов SQL,
при помощи которых ваша программа будет обращаться к базе ContactDB

Завершив ввод данных,


О Возвращение к ф орм е. ^
Закройте таблицу People и схему базы данных ContactDB к форме.
для возвращения на вкладку Form l.cs [Design].

People Query(OW...cts\ContactDB.sdf)^X j
ContactID Name Company Telephone Email Client LasCail
1 Lloyd Jones Black Box Inc. (Л8)555-5638 Uones@xbtack.„ True 5/26/201012:00..
2 Lucinda Ericson Ericson Events C212)555-9523 lucy@ericsonev... False 5 Л 7 /Ж 0 12:00..
3 Liz Nelson JTP (419)555-2578 liznelson@JTP.... True 3/4/200S12;00:...
4 Matt Franks XYZ Industries (Л2}555-8125 M8tt.Franks@x... True 5/26/201012:00..
5 Sarah Kalter Kalter, Riddle a„ (614)555-5641 sarah@krs.,org False 12,^0/200812:0..
§ Laverne Smith X¥Z Industries (22)555-8129 Laverne.Smith... True 4/ll/201012iOO..
NULL NULL NULL NULL NULL NULL

О Д обавление к при л ож ени ю нового источника данны х.


Выберите в меню Data команду Add New Data Source.

Создаваемый исмочник
данных будет отвечать
за все взаимодействия
формы с базой.
3

62 глава 1
эффективность с C#

Визуальные Объекты Хранилище Инетруменшы


объекты данных данных ЗН1§^еНЦЯ
-NET „.NET

к л

О Конф игурация нового источника данны х


Теперь нужно указать параметры взаимодействия источни­
ка данных с базой ContactDB. Вот как это сделать: 1ы здесь
★ В поле Data Source Type выберите вариант Database
и щелкните на кнопке Next.
★ В поле Database Model выберите вариант Dataset
и щелкните на кнопке Next.
★ Раскрывающийся список Choose Your Data
Connection содержит только один вариант, соответ­ ТйКйМ способом вы со-
ствующий вашей базе Contact. Щ елкните на кнопке
Next. Л л People
& базе данных ContactDB.
★ В поле Choose Your Database Objects установите
флажок Tables.
Express, предлагается
Убедитесь, что в поле Dataset Name введено значе­ сохранить строки под­
ние ContactDBDataSet и щелкните на кнопке Finish. ключения в ф 1йлТкТн -
^Р’^^ожения.
^т о е т ьт е «Yes».

Form!
- взаимодействия с базой ContactPB-

A bout "► C ontactD B D ataS et.xsd

C ontact List 1.0.


W ritten byi Vour N am e C ontactD B .sd f

OK
Файл с базой
C ontactD B D ataS et. данных.
1L Это ваша
D esigner.cs

форма. Эти файлы созданы


данных, параметры которого вы
только что выдрали.

дальше > 63
соединим все вместе

Добабление элементов, обязанных с базой данных


Добавим на форму другие элементы управления. Все они
должны быть связаны с базой данных и со столбцами табли­ Мы возвращаемся к созданию
цы People. объектов формы, которые
будут взаимодействовать
То есть изменение данных в любом элементе управления с хранилищем данных.
формы должно отображаться в соответствующем столбце
таблицы.
Если вы не видите эту
вкладку, 6 меню Pata
Выбор источника данны х. выберите команду
Show Pata Sources.
В меню Data выберите команду Show Data Sources. От­
кроется окно Data Sources с перечнем настроенных
источников данных.

I
Database Exptorer Можно щелкнуть
на корешке
вкладки Data
Sources, распо­
ложенном внизу
окна Database
Explore.

О Вы деление таблицы People.


Под пунктом ContactDBDataSet вы увидите таблицу People со всеми столбцами. Если выделить
строчку People в окне Data Sources и затем перетащить мышью на форму, автоматически по­
явится еще один элемент, при помощи которого можно просматривать и вводить данные. По
умолчанию это будет DataGridView, то есть еще одна таблица. Щ елкните на расположенной
слева от имени таблицы People и выберите команду Details, чтобы получить возможность до­
бавлять индивидуальные элементы управления для каждого столбца.

D ata S o u rc e s - Д X Щ елкните нй стрелке


::
и выберите в меню команоу
л [дЗ C o n ta ctD B D ataS etl
PetlЯ^(sJ чтобы добавить
к форме несколько разных
- ^
элементов управления.
1|Ы| C o n ta ctID

С И N am e
Jgii C o m p a n y

Здесь должны Q T e le p h o n e

быть показаны jabi! Em ail

все созданные 0 C lien t


столбцы. LastCatt

64 глава 1
эффективность с C#

Bu3ys«Mbie О бъекты Храаидище ингт^умені


объекты б н ы данных данных Вне;{»»іія
.NET .NET l= = l

- cS

Щ ^ в щ

здесь

о Создание элементов управления связанных с таб л и ц ей People


Перетащите таблицу People на форму. Появятся элементы управле­
ния, соответствующие столбцам базы данных. Волноваться о том, как
именно они выглядят, пока не нужно, важно, чтобы они просто по­
явились.
Для перевода формы в активное состояние, достаточно
перейти на вкладку Forml .cs [Design] или открыть файл
Form l. es из окна Solution Explorer.
Э т у панель
управления
Зля навигации
по таблице
создала ИСР- Элементы
управления,
созданные
после
перескивания
3таблицы
0)шрапу: People нл
Тв1е«йэсяте:
форму.
ВШ:
Э т и ком понента dent: IdieckB oxI
не видны на самой
форме, они AUUAt? Ы яМ ; Thuredäy . December 24,2(ЮЗ
Этот адаптер
указывают на коо, отвечает
который 5ыл за взаимодействие
создан ИСР для —ч с командами SQL.
взаимодействия / ^ äcortSKtDBDaföSeÜ peepteBirntingSource ■^ 'p e o p ie T a H e ftd sp te -

с таблицей People ^ ta b le A ita p le f M s n s s e r .i! ^ !J*peo(ilei(«äin 3 l« » ig ato f


и базой данных
ContactPB-

Этот объект
соединяет форму
с і^адлицеи People.
^лнель

дальше ► 65
сделаем красиво

Хорошие программы понятны интуитибно


Имя: taveme Smith
Теперь наша форма работает. Но при этом она Фирма: XYZ Industries
имеет не самый презентабельный вид, а ведь при­ форма с т а ­
н е т 5олее ^o m ф o н :(z^z)sss-8 tzя
ложение желательно делать не только функцио­
нальным. Им должно быть приятно пользоваться. понятной, Email: Laverne.Smith@XijZindustries.com
Сделаем его похожим на карточки, с которых мы если п р и ­ Клиент: Yes Поел, звонок: 0S/Z&/07
дать ей вот
переписывали контактную информацию. такой вид.
Ж
Выравнивание п о л е й и и х названий.
Выровняем поля и их названия по левому краю фор­
мы. В таком виде ею будет удобней пользоваться.

•i* Forml
И << О

Cratad ID:
П ш леретаскибйнмы N«ne:
эм м ент ов управления
бцдцт появляться Согаржиу:

линии синего цвета, Td^hone;


упрощающие процедуру
выравнивания.
-fflo
Cfer*; Cjl chsckSratl 6
о .......... ... -jQ
Thunsday , Decenrt>er 24.2009

Уд ал ени е п од пи си к q)лaж кy C lient.


После перетаскивания на форму флажка Client справа
от него оказалась ненужная подпись. В окне Properties,
расположенном под окном Solution Explorer найдите
свойство Text и удалите слово checkboxl.
Properties ' a X Для удаления м е т к и
cKentCheckBox System.Wmdows.Porms..Check » очистите это поле.
• )1 .
TsbStop True

{cbedcBoxl 4 m _
TextAlign Middieleft
TexUmageRetation Overiay
ThreeState False
UseCompetibteTect( Fat»
UseMnemonk True
UseVtsualStyleBeckC Tnie
UseWaitCursoF FaEse
___ iOsible--___ ____ Тше_ ............
Tert
The text associated with the cofrtiel

66 глава 1
эффективность с C#

В и зуиьны е О бъекты Х^вшище иист(»умвктьг


объекты 6вЬ1 gsHHW* данных
.NET .NET

^Ь 1 здесь

@ П р и д ан и е процзессионального вида.
Щелкните в произвольном месте формы в окне Properties При раскрытии формы
и найдите свойство Text. Введите в это поле название на полный экран положение
Objectville Paper Company Contact List. элементов управления
не меняется, форма
Присвоив свойствам MaximizeBox и MinimizeBox знау выглядит некрасиво.
чение False, можно убрать возможность разворачивать Поэтому мы просто
и сворачивать форму. уберем эт у возможность.

Propertws
F orm l Sy5tem.Windows.Fortns.Form

........:
^owbTaskbar True
должно „одд p- Size 408, 261 Свойство Text
SizeGripS'^te Auto
StartPositior» WindowsDefauttlocati.
задает название
Tag
формы.

Если окно Properties отсутствует, выберите


одноименную команду в меню View.

Хорошим считается приложение, с которым


легко работать. Его функциональность должна
соответствовать ожиданиям среднестатистического
пользователя.
дальше > 67
напоследок

Тестирование
Осталось запустить программу и убедиться, что она работа­
ет правильно! Вы уже знаете, как это сделать, поэтому про­
сто нажмите F5 или щелкните на кнопке с зеленой стрелкой
на панели инструментов (также можно выбрать команду
Run из меню Debug).
Запускать можно даже не полностью готовые программы.
Если в коде ИСР есть ошибки, программа не запустится.
Objectville Paper Company Contact List

И < n У a
Э т и элем енты
у прав л е н и я
Contad 10: 0
Переходить от одной Name: Lloyd Jones
записи базы к Эругом.
Смтрапу: 0ad< Bax inc.

Tel^hone: (7181555-Ж8
Компиляция БпаЙ; LJones@xbiad<bo3dnc.com

профаммы (Sent:
UrtM: Wednesday. May 26,2Й10
переписывает
данные в базе.
Этот аспект
будет подробно
рассмотрен
в следующей главе
РР у д ,ьш
ь ш ее
о С Ш о |э о Ж Н ь 1 !
UCP сначала компилирует, При каж дой ком пиляции програм ­
мы в папку bin пом ещ ается свежая
потом запускает на Выполнение копия базы данны х. Все д анны е, н а­
При запуске программы ИСР выполняет две операции. Сна­ копленны е с м ом ента последней от­
чала она строит программу, затем вы п олн яет ее. Эта работа л адки, при этом уничтож аю тся.
состоит из нескольких этапов. Код компилируется, то есть В процессе отладки, если код был
превращается в исполняемый файл. Этот файл вместе с дру­ изменен, ИСР перестраивает про­
гими ресурсами помещается в подпапку папки bin. грамму — это означает, что база
данных может оказаться перепи­
Свой исполняемый файл вместе с базой SQL вы найдете санной. Этой проблемы можно избе­
в папке b in /d e b u g . Его запуск позволяет сохранить все вне­ жать, запуская программу из папки
сенные в процессе пользования приложением изменения. b i n / d e b u g или b i n / r e l e a s e или
В то время как при запуске из ИСР база заменяется копией предварительно воспользовавшись
с данными из окна database Explorer. установщиком.

68 глава 1
эффективность с C#

Визуальные Обмкты Х{;айиАище инструменты


Как превратить ВАШ Е прило)кение обьвкты
■NET
базы данных
•НЕТ
Ззяных внедрения

В прило)кение для ВСЕХ


И и "
Пока что приложение работает только на вашем компью­ j
тере. Никто не может им воспользоваться, купить его у вас,
W
увидеть, какой вы замечательный программист и нанять
на работу... даже начальник и клиенты не видят сообщения
из вашей базы данных.
.1 здесь
C# позволяет легко внедрить созданное вами приложе­
ние. Внедрением называется установка программы на дру­
гие компьютеры. В ИСР Visual C# эта процедура осущест­
вляется в два этапа.
При посмюоении
'СктШН- решения файлы
» Wew Project „ 0^ копируются на
'2 3 'H / M A dd W indows Form... ваш компьютер.
^ jj ''J Add Class... Процедура п у ­
бликации созда­
M d M e» item ...
ет выполняемый
1' ^ A dd Existing Item.,.
конфигурационный
Выберите команду Publish файл, при п о ­
MA Ref«teiKe...
Contacts в меню Project. M i Seiw ce Reference.,, мощи которого
SelasaaitUpPmject программу можно
установить на
@ C o n to rts Properties.«
любой компьютер.
Publish C ontacts k

О Щелкните на кнопке Finish


в окне Publish Wizard. Ваше
Publish Wo:a*ti

Where d o you w ant to publish tfw application?


приложение будет обра­ #
ботано, и вы увидите пап­
^ c % t h e location to pubfehthK si^iltcattore
ку в которую сохраняется
ШШ
файл setup.exe. Шp i^ .
You may pubfish the appftcetton to a web site, FTP s«ver, or
bamptes:

Fife share WarveAmyappBcatton

Web 5йе htt(K//www. mkroEt^conv'myappfic*rtion

В Visual Studio Express


Команда Publish
находится в меню Project,
в то время как в других
версиях Visual Studio
ее можно обнаружить
в меню Build.

1 ^ Previ&us j| ... ] ( Imish J j. ^ ^ 1

дальше > 69
поделитесь любовью

Передаем прило}кение пользователям


После внедрения у вас появится папка с именем p u b l i s h / .
В ней находятся все компоненты, необходимые для установки.
Самым важным из них является файл s e tu p . Именно он позво­
ляет установить приложение на любой компьютер.

цНСтДЛАЯЦи^.

руды пе
оС Щ о|Э оЖ Н Ь 1
Д л я ин стал л яц ии вам
м огут пона д об итьс я права
а д м и н и стратора. э т о т файл говорит С помощью этого
файла осуществляется
Если у вас еще нет базы инсталляция.
SQL Server Compact, ин­ в инстйлдмру^ллу
сталлятор автоматически прогряллму-
скачает и установит ее.
На некоторых компьюте­
рах это возможно только
при наличии у вас прав
администратора. Щелкни­
те правой кнопкой мыши Секретарь сказала, что у нас уже
на файле setup и выберите работает новая база данных с контактами.
команду Run as administrator. Вы хорошо поработали и заслужили
Если у вас нет такой воз­ небольшой отдых...
можности, ничего страш­
ного. Это не помешает
вам выполнять следующие
задания.
А
1Саже,тся, начальник доволен.
V Но перед т е м кяк собирать
^ i o / a H b b -Р о т е с т и р у ш
резулЕ?тдтЬ 1 своего тру

70 глава 1
эффективность с C#

Визуадьные Объекты XfsBu/iim« ІІнетрумвиі


Работа НЕ окончена: проверим объекты
•NET
базы ааявы*
. .NET
данных внедрения

процесс установки It
Перед тем как открыть бутылку шампанского и отпразд­
новать победу, нужно протестировать внедрение и уста­ В ; Щ
новку
Закройте ИСР Visual Studio. Запустите файл setup и ука­
жите, куда следует установить ваше приложение. Убе­
дитесь, что все работает так, как вы ожидали, — можно Б:ы здесь
добавлять и редактировать записи, и все изменения со­
храняются в базе данных.

Objectville Paper Com pany Contact List


Стрелки
и текстовое la ill of§ I ^ И !Ф К a
поле позволяют
переходить Coftad ID; I '

от одной записи
к другой, ч ^ Name:

Company: Black Bax Inc.

Tdephone: (718)555-5638

Bnail: Uones@d3lackbo3dnc .com

Oert: a <-
Попробуйте,
вн&сти изменения LaS Ы1: edi sda , fv:3>‘ 26. 201С Q »"
б данные. Все контакты
на месте. Ведь
они являются
частью
базы данных
ContactDB.sdf,
которая была
установлена
вместе
ТЕСТИРУЙТЕ ВСЁ! с приложением.

Тестируйте вашу программу,


процедуру внедрения, данные
в вашем приложении.

дальше ► 71
быстрее не бывает

Управляющее данными Визуальные


объекты
О бм кш ы
базы данных
Х|1ана«още
данных
инструменты
Внедрения
■NET .NET

приложение готово
Visual Studio позволяет с легкостью разработать
Windows-приложение, написать базу данных и свя­
зать эти два объекта вместе. Вы даже смогли по­
строить программу-инсталлятор всего нескольки­
ми дополнительным щелчками мыши.

Q m б у м а ж н о й к а ]= ^ о Ч к и
«Ц: Objectville Paper Company Contact list
Hi 4 \1 crf6 : ► И ! Я
Имя: LloydJones О — Cortad ID: 1 ............ }
Фирма: Black Box Inc. Name; Ltoyd Jones
Телефон: (718)555-5638 До эЛ еК- Csmpanjr: Hack Box Inc.
ЗРроННой Tdeptone; (718)555^638
Email: Uones@Xblackboxinc.com
finaS: U o n e s # > d 3 l a c k b o * i n c .c o n i
Клиент; Yes Поел, звонок: 05/26/07
a « : В
LaSCd: Wednesday, May Ж 2010

наМноГо бьхсгорее,
Чем к а з а л о с ь .

Средства Visual C # позволяют сфокусироваться на


выполнении задания, ведь вам уже не требуется
писать код для окон, кнопок и доступа к базе SQ,L.

72 глава 1
2 ЭЗЦО Б с е Г о Л и Ш ь К о д

Под покровом

Вы — программист, а не просто пользователь ИСР.


И С Р может сделать за вас многое, но не всё. При написании приложений
часто приходится решать п о в т о р я ю щ и е с я задачи. Пусть эту работу вы­
полняет ИСР. Вы же будете в это время думать над более глобальными ве­
щами. Научившись п и са т ь код, вы получите возможность решить любую
задачу.
к вашим услугам

Когда ßbi делаете это...


Все эт и задачи являются
ИСР — это мощный инструмент. Но это всего лишь инструмент. При стандартными
каждом вашем действии он автоматически создает код. Но это хорошо и требую т шаблонного
только при выполнении ш аблонны х задач или в случае, когда код мо­ кода. Поэтому их можно
жет быть использован без дополнительных изменений. отдать на от куп ИСР.

Посмотрим на действия ИСР при разработке типичного приложения.

Создание формы в W indows


ИСР позволяет строить различные приложе­
!:т
ния, но сейчас мы рассмотрим вариант Windows
Forms — программу, имеющую визуальные эле­ '9 =
менты, например, формы и кнопки.

При выборе варианта


W indow s Form s A p p lic a tio n
ИСР создает пуст ую ф ор­
м у и добавляет ее к новому
проекту.

О П ер етаски в ани е кнопки с п анел и инстру­


ментов на qx>pMy и двойной щ елчок на н е й .
Именно кнопки заставляют форму работать.
На их примере мы будем рассматривать различ­
ные аспекты языка С#. Кроме того, они явля­
ются частью практически любого создаваемого
приложения.

Properties X
о Задание свойств qjopMU Form! Sy5tem.Window$.Ferms.Form

Окно P ro p e rtie s позволяет легко поменять .


SbowlnTasfcbar "frt«“
атрибуты практически любого компонента I' Size 4 0 a 261
программы. StzeGnpStyte
StartPosJtron
Auto
WirvdowsOefaultlocatt.
Tag
1O t^ e c t^ ^ Cons
TopMost False

Окно Properties ^
TransparencvKey
UieWaitCursor
□Fafs« .'3
‘простой способ самый / WindowState Normet

Text
><ода формы F orm i Th« text essocmed wrtb the control.

£сли это окно


>^мите клавишу

74 глава 2
это всего лишь код

...UCP делает это.


Любые ваши действия приводят к изменениям
кода, а значит, и файлов, которые содержат этот
код. Иногда редактируются всего несколько стро­
чек, а иногда система создает новые файлы. Эти файлы были
созданы на базе шаблона

.ИСР создает ф айлы и п а п ки .

— '1 ^ Ь' ijs


cU» Fm S
М - Г»«**-«
; J ^ 1 J
W in d o w sA p p licatio n I F o rm l .cs F o rm l .D esigner.cs Program .cs Properties
.csproj

О ...И С Р добавляет код в qэaйл F o r m l . D e s i g n e r . c s , отвеча­


ю щ и й за появление кнопки на <рорме. А затем добавляет
в ф айл F o r m l.C S - код. определяю и^ий результат щ елчка «1ам P»e{
fkblit...
на кн о п ке. }.
p r iv a te v o id b u tto n l_ C lic k (o b je c t sender, E v e n t A r g s e)
F o rm l .Designer.cs

1АСР м о ж е т добавить пустой


метод для обработки щелчка
на кнопке- Но выбор параметров
этого метода — ваша работа.
Form l.C S

о ...И С Р открывает ф айл F o r m l . D e s i g n e r . c s


и обновляет строчку кода.
ИСР открывает ф а й л-

p a r t i a l c l a s s Forml

t h i s . T e x t = " O b j e c t v i l l e P a p e r Company C o n t a c t L i s t " ;

F o rm l .Designer.cs

„u обновляет эт у cvnpomy.

дальше > 75
средства коммуникации

Как ро)кдаются программы


Программа на С# может начинаться как набор операторов в различных файлах, но в конце должна по­
лучиться программа, работающая на вашем компьютере. Вот как это происходит.

Любая программа начинается с файлов, те кста кода


Как вы уже видели, ИСР сохраняет вашу программу в файлы. Эти фай­
лы можно открыть и найти все добавленные к проекту элементы: фор­
мы, ресурсы, код и прочее.
ИСР можно представить как хороший редактор файлов. Он автома­
тически делает отступы, выделяет ключевые слова цветом, закрывает
скобки и даже предполагает, что вы напишете в следующую секунду.
Другими словами, именно ИСР редактирует файлы, содержащие вашу
программу.
ИСР связывает все файлы программы в реш ение путем создания фай­
ла (. s in ) и папки, в которой оказываются все материалы. Решение Программы
содержит список всех входящих в проект файлов (оно имеет расшире­ можно писать
ние . c s p r o j ) , последние же в свою очередь включают в себя список даже в Блокноте
(Notepad), но это
всех файлов, связанных с программой. В этой книге мы будем строить очень долго.
решения на основе всего одного проекта, но окно Solution Explorer по­
зволяет работать и с другими проектами.

инструм енты om.NET Framework


C# — это всего лишь язык, и сам по себе он не может ничего делать.
Но здесь вам на помощь приходит технология .NET Fram ework. При
щелчке на кнопке развертки окна на весь экран срабатывает код, ука­
зывающий, как именно должна выполняться эта операция. Этот код
является частью .NET Framework, как и другие кнопки, флажки, спи­
ски и даже механизмы связи с базой данных. Это хороший инструмент
для создания графики, чтения и записи файлов, управления наборами
объектов и прочих рутинных операций.
Инструменты .NET Framework находятся в пространствах имен. Вы уже
видели их ранее, в верхней части кода. Это было пространство System .
W indows. Forms —именно здесь содержатся кнопки, флажки и формы.
При создании любого проекта Windows Forms в верхней части кода вы
увидите строчку u s in g System . Windows. Forms.

76 глава 2
это всего лишь код

Создание исполняемого файла


Выбор команды Build Solution (меню Build) начинает компи­
ляцию вашей программы. Запускается компилятор, то есть
инструмент, читающий код программы и преобразующий его
в исполняемый файл. Этот файл сохраняется на диск компью­
тера с расширением . е х е . Для запуска программы достаточно
дважды щелкнуть на его имени. По умолчанию этот файл поме­
щается в папку bin, вложенную в папку project. При публикации
решения он (вместе с другими нужными файлами) копируется
в выбранную вами папку.
В ответ на команду Start Debugging (меню Debug) ИСР компи­
лирует программу и запускает полученный файл на выполне­
ние. Существуют и более совершенные инструменты отладки,
то есть запуска программы с возможностью прерывания, чтобы
вы могли понять механизм происходящего.

Программы работают В CLR


Двойной щелчок на имени исполняемого файла запускает
программы. Но между операционной системой и программой
существует дополнительный «слой», называемый Common
Language Runtime, или CLR (общеязыковая исполняющая
среда). Раньше (еще до изобретения С#) писать программы
было намного сложнее, так как приходилось иметь дело с аппа­
ратным обеспечением, заниматься низкоуровневым програм
мированием. CLR —часто называемая также виртуальной ма­
шиной —является своего рода «переводчиком» между вашей
программой и компьютером, на котором она работает. На данном э т а п е
достаточно запомнить,
Функции CLR будут рассмотрены позднее. Пока упомянем
что с и я автоматически
только, что она отвечает за работу с памятью компьютера, запускает ваши
определяя, что программа закончила работать с конкретными программы- Более
данными, и избавляясь от них. Некоторые программисты при­ подробно она будет
выкли самостоятельно работать с памятью, но без этого мож­ рассмотрена позднее.
но обойтись, переложив заботу на CLR.

дальше * 77
ваш маленький помощник

Писать код помогает UCP


с частью возможностей ИСР вы уже познакомились.
Здесь же мы более детально рассмотрим некоторые
инструменты.

о О кно Solution E xp lo rer содержит сп и со к всех частей проекта


Вам предстоит переключаться с одного класса на другой и проще всего
это делать, используя окно Solution Explorer. Вот вид этого окна после
создания программы обмена контактными данными:

Solution Explorer » П X

Ä ij S t . ; .
)
*33 Solution 'Contacts' (1 projectj
^ I^ ^ C o n ta ctsi В окне Solution Explorer
Можно посмот реть,
^ ^ Properties о каких папках на -
l> References ходятся те или иные
appxonfig файлы.
ContactDB,sdf
Ej . ContactDBDataSet.xsd
i> Э Forml .cs
Progrann.cs

Здесь находится
Вкладки позволяю т л е гко перейти о т одного ф айл а к д ругом у файл с ресур­
Так как типичная программа состоит из множества файлов, приходится сами формы,
к которому вы
работать с несколькими файлами одновременно. Каждый из них имеет добавляли лого­
т ип компании.

открыты две
вторая с кодом,
пользуйтесь

78 глава 2
это всего лишь код

ИСР п о м о гае т писать код


Замечали ли вы в процессе набора кода маленькое всплывающее окошко? Это
крайне полезная функция 1п1е1И8еп8е. Она показывает возможные способы завер­
шения строчки кода. Например, если вы набрали слово М еззадеВох и поставили
точку, дальше может следовать только:
ИСР знает, что MessageBox имеет три
MessageBox. метода: Equals,, ReferenceEquals и Show.
Ф Eq ua ls Паи вводе буквы S будет выбран вариант
Show. Вам останется нажать Enter.
# ReferenceEquals Иногда эта функция очень экономит
,ф | время.

Если после выбора варианта Show напечатать (, функция IntelliSense предложит


вам варианты заполнения строки:

Суще-ствует H essageB ox.Show d ______ __________________________________ _________________________ ____________


ЯЗ- способ «essageBox.ShOM(string text, string caption)
вызова метода
Displays 3 message box with specified text and caption.
Show для
MessageBox. tart; The text to display in the message box

В ИСР существуют сокращения, называемые ф рагм ентами кода. Для их вставки


достаточно напечатать нужную аббревиатуру. К примеру, если напечатать ttibox
и дважды нажать клавишу Tab, появится строка для метода M essageB ox. Show:

MessageBox. Show! J
В окне Error
List бк>! найдете
список всех
О кно E rro r L ist показы вает ош ибки ко м п и л яц и и ошибок,
мешаьош,1^Х
Вы даже не представляете, как легко сделать опечатку при наборе кода на С#! компиляции
К счастью, существует инструмент, позволяющий бороться с этой проблемой. iA.pozpaMMt><-
При построении решения всё, мешающее компиляции, будет перечислено
в окне Error List, расположенном в нижней части экрана:

О 2 Errors 1 О W a rn in g s M i ) О M e s sa g e ^
О т сут ст вие tin e C o lu m n P roject
D escription Fite
точки с запятой
после оператора
является одной
из самых частых
ошибок.

Для исправления некорректного кода дважды щелкните на ошибке:


p r i v a t e v o id p ic tu r e B o x l_ C lic k (o b je c t s e n d e r , EventArgs e )
{ Некорректный код
Mess age Box. X>g( h i ) подчеркивается
} красным цветом.

дальше ► 79
подробности

Любые действия Ведут к изменению кода Значок «Упражнение!»


является сигналом
Посмотрим, каким образом ИСР пишет код. Откройте к запуску Visual Studio, л
Visual Studio и создайте новый проект Windows Forms
Application.
^ ^ і/
Уп]^ажнение!

П р осм о тр кода і
Откройте файл F o r m l.D e s ig n e r .c s , но не в конструкторе форм. Для этого Ж
щелкните правой кнопкой мыши на имени файла в окне Solution Explorer и вы­
берите команду View Code. Посмотрите на объявление класса Forml:
p a r t i a l c l a s s Form l

м. .olTopZZZ.
О Д обавление к срорме эл ем ента PictureB ox
Привыкайте работать с множеством вкладок. Вернитесь в окно Solution Explorer
и откройте конструктор форм двойным щелчком на имени файла F o rm l. cs. П е­
ретащите на форму элемент PictureBox.

О П р осм о тр созданного конструктором для элем ента P ictureB ox кода


Вернитесь на вкладку Form LDesigner.es. Найдите вот эту строчку:
Щ елкните на квадратике
Ґ со знаком «плю с».
+ Windows Form D e s ig n e r g e n e r a te d code

Щ елчок на этом квадратике раскроет код. Найдите в нем следующие строчки:

//
// p ictu reB o x l
//
Ваши значения
th is .p ic tu r e B o x l.L o c a tio n = new S y s t e m . D r a w i n g . P o i n t ( 2 7 6 , 2 8); параметров
Location
th is.p ic tu r e B o x l.N a m e = " p ic tu r eB o x l" ; и Size будут
отличать>ся
t h is .p ic t u r e B o x l. S iz e = new S y s t e m .D r a w in g . S i z e (1 0 0 , 50); от указанных
th is.p ic tu r e B o x l.T a b ln d e x = 0;
в книге.

th is .p ic tu r e B o x l.T a b S to p = fa lse ;

80 глава 2
это всего лишь код

Ч то >ке Все это значит? Чаще всего комментарии


помечаются двумя косыми
в верхней части кода вы увидите строки: слешами (//)■ Но ИСР
иногда м о ж е т поставить
три слеша.
/// <sum m ary>
/// О бязательны й м е т о д для поддерж ки к о н с т р у к т о р а - не и зм ен я й т е
/// с о д е р ж и м о е д а н н о г о м е т о д а п р и пом ощ и р е д а к т о р а к о д а .
/// < / sum m ary> Это комментарии XML,
о которых вы узнаете
в разделе # 1 Приложения.

Для детей нет ничего более притягательного, чем табличка с надпи­


сью «Не трогать!» Не отрицайте, вы же знаете, что вам тоже хочет­
ся... поэтому отредактируем содержимое данного метода, воспользо­
вавшись редактором кода! Добавьте к форме кнопку и выполните
следующие действия:

О О тредактируйте свойство T e x t кнопки b u tto n l. К а к вы


д ум аете, что изм енится в окне P roperties?
Сделайте и посмотрите, что получится! Затем вернитесь в кон­
структор форм и проверьте свойство Text. Оно изменилось?

О Воспользуйтесь окном P rop erties для редактирования


Для просмотра
свойства N am e како го -н и б уд ь объекта. результ ат а
В окне Properties это свойство находится вверху и называется
“(Name)». Какие изменения произойдут с кодом? Обратите внима­
ние на комментарии. U запускать
программу.
О п р и с в о й те свойству Location значение ( 0 ,0 ) . И зм ените
парам етр S iz e , увеличив размер кно пки . Form l.cs [Реядп].
У вас получилось?

0 Вернитесь в конструктор кода и произвольным образом


пом еняйте свойство BackColor.
Внимательно посмотрите на код Form l .D e s i g n e r . c s. Появи­
лись ли в нем новые строчки?

Поменять код проще всего средствами ИСР.


дальше > 81
программа делает заявление

Структура программы
имен, отделяя ^о/^® ^^Я-янстбо
■n e t F r a m e Z r L
Код любой программы на C# структурируется
одинаково. Все программы используют про­
странства имен, классы и методы.

Класс содержит^
ф рагм ент вашей
программы (очень
маленькие програм ­
мы м огут состоять
из всего одного класса)

Класс включает один или


несколько методов. Методы всегда
^ и н а д л е ж а м какомц-лиЗп классы
методы. 6 сбою очередь, состоят
из операторов.

Внимательно рассмотрим код


Откройте код проекта Contacts F o rm l. c s , что­
бы рассмотреть его фрагменты.

О *айл кода начинается с перечисления инструментов .NET Framework


в верхней части любого файла программы вы обнаружите набор строк, начинающихся
с оператора u s in g . Они указывают, с какой частью .NET Framework будет работать про­
грамма. Чтобы использовать классы из других пространств имен, нужно указать их при
помощи директивы u s in g . Для построения форм применяется множество инструментов
.NET Framework, поэтому в начале программы так много строк с директивой u s in g .

using System;
using System.Collections.Generic;
Такие строчки
using System.ComponentModel; находятся в верхней
части любого кода.
using System.Data; 1^ажаая из них
показывает, какое
using System.Drawing;
using System.Linq; из .NET Framework
использует т от
using System.Text; или инои файл .CS.

using System.Windows.Forms;
В принципе без оператора u s in g можно обойтись, если пользоваться полными именами.
Если строчка u s in g System .W indow s. Forms отсутствует, окно сообщений можно создать,
написав S y s te m . W indows. F o rm s. M essageB ox. Show(), и компилятор все равно поймет,
какое пространство имен вы имеете в виду.

82 глава 2
это всего лишь код

0 ТТрогроААМЫ н а C # и с п о л ь з у ю т кл а с с ы
Программы на C# используют классы. Каждый класс выполняет свою задачу. Когда вы соз­
давали свою первую программу, ИСР добавила класс Form l, отображающий формы.

Для программы Contacts, ИСР создает


tr одноименное пространство имен. Именно в этом
namespace Contacts пространстве находится все содержимое скобок.

ргоЬИс partial class Forml Form


^ Класс F o rm l содержит код создания как самой
{ формы, так и ее элементов управления. ИСР
добавляет эт от класс при создании проекта
Windows Form Application.
Классы содержат методы
Для выполнения различных действий классы используют метод. Метод берет входные
данные и производит некоторое действие. Данные передаются при помощи параметров.
Именно от них зависит поведение метода. Слово v o id перед названием метода означает,
Обращайте
внимание что он не возвращает никаких параметров.
на пары
скобок. Среди Здесь вызывается мет од
них м огут piiblic Forml О tnitializeComponent().
попадаться
вложенные. {
InitializeComponentO;
}
Каждый оператор выполняет только одно действие
Строкой M essageB ox. Show {) вы добавляете оператор. Именно из операторов составле­
ны методы. При вызове метода выполняется сначала первый оператор, потом следующий
и т. д. Достигнув конца списка операторов или оператора r e t u r n , метод завершает работу.

МетоЭ pictureBoxi-^ChckO Этот метод имеет


вызывается два параметра:
графическом фрагменте. sender и е.

private void pictureBoxlClick(object sender, EventArgs e)


{
MessageBox.Show("Contact List 1.0", "About");
Оператор вызывает метод Show(),
} принадлежащий классу MessageBox в
Это операт ор, вызывающий пространстве имен System.Wlndows.Forms.
диалоговое окно.

Оператор передает методу Show Q два пара­


метра: строку, которая будет отображаться
в окне диалога и заголовок этого окна.

дальше > 83
еще более подробно

Начало работы программы Нй С*


оозможна илолько
При создании нового решения ИСР добавляет файл Program .cs. Най­ одна точка входа
‘^ о мет од MainQ
дите его в окне Solution Explorer и дважды щгелкните на имени. Внутри
класса P rogram вы обнаружите метод M ain {). Этот метод является точ ­
кой входа, то есть именно отсюда программа начинает работу.

Этот код, автоматически созданный


6 упражнении из предыдущей главы.
бы найдете в файле Program.cs.
|( о д Лод уВеЛиЧгадеЛьНьхМ сзиеКЛоМ
а
using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows.Forms;
О \Cod находится
6 пространстве
namespace Contacts
имен Contacts.
{ о
static class Program и более косые черты
{ I/
/ в начале строки обозначают
комментарии.
/// <s\jmmary>
/// Точка входа в приложение
I I I </summary>
[STAThread] Программа начинает
работу с точка входа.
static void Main О
{ О
Application.EnableVisualStyles();
QApplication.SetCompatibleTextRenderingDefault(false);
Application. Run (new FormlO);^ Этот оператор создает
и отображает форму
} Contacts, а также
завершает программу
} при закрытии формы.

, част ь им ени
1<ласса и л и м е т о В а н а ­
Сь~лча^, на стадии начального
з ы в а е т с я о б ъ яв л е н и ем . ^ знакомства с кодом, вам требуется
только понять, на что следует
обращать внимание.

84 глава 2
это всего лишь код

Постепенно вами
программы 5удцт
содержать все дольше
пространств имен.
О Встроенные функции C # и .NET.
Подобные строчки находятся в верхней части почти
всех файлов классов С#. S ystem .W indow s.F orm s Без строчки using вам
это пространство имен. Строка u s in g S y stem . придется в явном виде вводить
Windows . Forms дает программе доступ ко всем объ­ Sustem.Windows. Forms при
ектам этого пространства. В данном случае к визу­ обращении к объекту из этого
альным элементам —кнопкам и формам. пространства имен.

Q Выбор пространства имен для кода.


ИСР назвала созданное пространство имен
Пространства имен
C o n ta c ts (в соответствии с именем проекта).
позволяют использовать одни
Именно этому пространству относится весь код. и те же имена в различных
программах, при условии, что
программы не принадлежат
к одному пространству.
О Код принадлежит к конкретному классу.
В вашей программе этот класс называется
Program . Он содержит код запуска программы
и код вызова формы Contacts. В
несколько классоб.

Технически програм м а
Наш код содержит один метод, состоя­ ^ может имет ь несколько
щий из нескольких операторов.
Внутри любого метода может находиться произ­ \ только у к а за т ь , кякои из
вольное количество операторов. В нашей про­ -V них будет точкой входа.
грамме именно операторы вызывают форму
Contacts. Любая программа на C#
должна иметь единственный
Точка входа. метод Main. Он является
Каждая программа на C# долж на иметь один ме­
тод с названием Main. Именно он выполняется точкой входа дая вашего
первым. C# проверяет классы на его наличие,
пока не находит строчку s t a t i c v o id M ain (). кода.
После этого выполняется первый и все следую­
щие за ним операторы.
При запуске кода метод
M ain() выполняется
ПЕРВЫМ.

дальше > 85
элегантные решения

Редактирование точки Входа


в программе главное —наличие точки входа. При
этом не имеет значение, к какому классу принад­
лежит содержащий ее метод и какие действия
Упражнение! Яагуцщцте. ч т о
производит. Откройте программу из главы 1
и удалите метод Main, чтобы создать его заново. и-ромзоияло п р и
изменении имени
метода, и ваши
соображения
о причинах этого
В файле P ro g ra m . c s присвойте методу M ain имя NotM ain и попро­ явления.
буйте построить и запустить программу. Что произойдет?

О Создадим новую точку входа. Добавьте класс с именем A n o th e rC la s s .e s . Для этого


щелкните правой кнопкой мыши на имени файла в окне Solution Explorer и выбери­
те команду A dd»C lass... ИСР добавит в программу класс A n o th e rC la s s . После этого
код примет вид:

u s in g System ; В файл были добавлены


u sin g S y stem .L in q ; четыре стандартные строчки
u sin g S y ste m .C o lle c tio n s.G e n e r ic ; с оператором using.
u sin g S y stem .T ex t;
Этот класс тоже находится
nam espace C o n ta c ts в пространстве имен
{ Contacts.
c la ss A n o th e r C la ss

} имени файла). основе


Q Добавьте в верхнюю часть строку u s i n g S y s t e m . W in d o w s . F o r m s ;
He забудьте, что в конце строки должна стоять точка с запятой!

О Добавьте этот метод к классу А п оЬ Ь егС Х а в з, написав его внутри фигурных скобок:
Класс MessatjeBox
принадлежит c l a s s A n o th e r C la s s
пространству имен {
System.Windows. p u b lic s t a t i c v oid MainO
Forms, поэтому
на шаге # 3 вы {
и добавили оператор M e ssa g e B o x . Show ( "Fow l" ) 7
using. Метод Show О }
является частью
класса MessageBox.

86 глава 2
это всего лишь код

Ч то )ке произошло?
Теперь вместо приложения Contacts программа вызыва­
ет вот такое окно диалога. Переопределив метод M ain (),
вы указали новую точ 1су входа. Поэтому программа пер­
вым делом выполняет оператор M essageB ox.Show O .
Данный метод больше ничего не содержит, поэтому
после щелчка на кнопке ОК программа завершит свою
работу.

Подсказка: Д о с т а т о ч н о
о Верните программу в исходное состояние. измеш т ь^пару строк
в двух файлах.

_ |Jo3bMH в руку карандаш


Опишите назначение различных строк кода, как показано
в примере.

u s in g S y stem ; Оператор. .«SIM


u s in g S y ste m .L in q ;
u s in g S y s te m .T e x t;
........
u sIn g S y s tern. W indow s. F orm s;

n am esp ace SomeNam espace

{
c l a s s M yC lass

p u b l i c s t a t i c v o id D o S o m e th ln g () {

M e ssa g e B o x .S h o w (" З д ес ь б у д е т соо б щ ен и е" );

дальше > 87
время получить ответы

Часто
^аД аБ аеМ ы е
B o lIp o C b i

Какую роль играют фигурные 3 * Объясните, пожалуйста, еще раз, Почему при запуске программы
скобки? что такое точка входа. в окне Error List появляется сообщение
об ошибках? Я думал, что подобное
О ? Скобки группируют операторы С I Программа состоит из множества возможно только при выполнении
в блоки и используются только попарно. операторов, но они не могут выполняться команды Build Solution.
Открывающей скобке всегда должна соот­ одновременно. Операторы принадлежат
ветствовать закрывающая. разным классам. Как же при запуске С I Первое, что происходит при запуске
программы определить, какой оператор программы на выполнение, это сохране­
Для выделения пары скобок достаточно выполнять первым? ние всех файлов в виде решения и по­
щелкнуть на любой из них. пытка их компиляции. А при компиляции
Компилятор просто не будет работать при кода — неважно, запускаете вы при этом
отсутствии метода M a i n (), который и на­ программу или строите ее решение — все
зывается точкой входа. Первый оператор имеющиеся ошибки отображаются в окне
этого метода и будет выполняться самым Error List. f
первым.
В тексте кода ошибки
выделяются красным
цветом.
Возьми в руку карандаш
Вот правильные варианты ответа на вопрос о строках.

О перат ор losing добавляет


u s in g S y stem ; в класс из Э р уги х
u s in g S y ste m .L in q ; прост ранст в иМеи.
u s in g S y s te m .T e x t;
u s in g S y stem .W in d ow s.F orm s;

n a m esp ace SomeNam espace


Мы указываем, к какому
классу принадлежит
{ эт от фрагмент кода. Внутри этого
класса находится
c l a s s M yC lass { метод DoSometking,
вызывающий объект
p u b l i c s t a t i c v o id D o S o m eth in g lT ”^
MessageBox.
M e ssa g e B o x .S h o w (" З д есь б у д е т собщ ение")

Этот оператор
вТет о к н о с т екст овы м
сообщением.

88 глава 2
это всего лишь код

Определите соответствие описаний и фрагментов кода. (Некоторые


из них вам незнакомы, попробуйте угадать их функцию!)

partial class Forml

Задает свойства объекта label.


this.BackColor = Color.DarkViolet;

Это всего лишь комментарий, до­


бавленный программистом, чтобы
// Цикл выполняется три раза объяснить назначение кода тем,
кто будет читать его программу.

partial class Forml

private void InitializeComponent( Убирает кнопку сворачивания


{ окна (^ S ) окна F o r m l из строки
заголовка.

number_of_pit_stopsLabel.Name
= "nuraber_of_pit_stopsLabel";
number_of_pit_stopsLabel.Size
Подсказки системы, объясняющие
= new System.Drawing.Size(135, 17) назначение различных блоков
number_of_pit_stopsLabel.Text кода.
= "Number of pit st o p s :";

III <summary> Меняет фоновый цвет окна F o rm l.


III При щелчке на кнопке появляется
III графический фрагмент Rover
III </summary>

partial class Forml


Блок кода, выполняемый при от­
крытии программой окна F o r m l .
this.MaximizeBox = false;

дальше ► 89
решение упражнения

КТ9 И H W A E A a I t ?
Вот правильные ответы на вопрос о назначении различных бло­
ков кода. Сравните со своими вариантами!

partial class Forml

this.BackColor = Color.DarkViolet;
Задает свойства объекта label.

Это всего лишь комментарий, до­


бавленный программистом, чтобы
// Цикл выполняется три раза объяснить назначение кода тем,
кто будет читать его программу.

partial class Forml


{
private void InitializeComponentO
( Убирает кнопку сворачивания
окна ( ) окна Form l из строки за­
головка.

number_of_pit_stopsLabel.Name
= "number_of_pit_stopsLabel";
number_of_pit_stopsLabel.Size
■Подсказки системы, объясняюш:ие
= new System.Drawing.Size (135, 17)
number_of_pit_stopsLabel.Text назначение различных блоков
= "Number of pit st o p s :"; кода.

I I I <summary>
III При щелчке на кнопке появляется Меняет фоновый цвет окна Form l.
III графический фрагмент Rover
III </summary>

partial class Forml


{
Блок кода, выполняемый при от­
this.MaximizeBox = false;
крытии программой окна Form l.

90 глава 2
это всего лишь код

Классы м огут принадле)кать одному


пространству имен S o m eC lasses.cs
Рассмотрим два файла с классами из програм­
мы P e t F i l e r 2 (Домашний любимец) В них со­
держатся три класса: D o g (Собака), C a t (Кошка)
и F i s h (Рыбка). Так как все они принадлежат namespace PetFiler2 {
пространству имен P e t F i l e r 2 , операторы в
методе D o g . B a r k () (Собака лает) могут вызы­
вать операторы C a t . M e o w ( ) (Кошка мяукает) class Dog {
и F i s h . S w i m О (Рыбка плавает). Распределе­
pviblic void BarkO {
ние различных имен пространств и классов по
файлам не влияет на действия, выполняемые/ // Здесь операторы
после запуска. }

class Cat {
public void MeowO {
M oreC lasses.cs

Классы из одного пространства


имен Могут «видет ь» друг друга,
даже находясь 8 разных файлах.
А

Для помещения класса в разные


файлы используйте ключевое слово
partial. Именно его использует
ИСР, создавая файлы Forml.cs
и Forml.Pesigner.cs.

дальше ¥ 91
параметры могут варьироват ься

Ч то такое переменные

Все программы работают с данными. Данные могут быть пред­


ставлены в виде документа, графического фрагмента, видео­
игры или мгновенного сообщения. Для их хранения программа
использует переменны е.
)уды ие I
о а о а о р 'о ж н ы !
Объявление переменных
З наете ли вы д р уги е язы ки
О б ъ яви ть (declare) переменную, значит, указать программе прогр ам м и р о в ани я?
тип и ее имя. Благодаря указанию типов невозможно скомпи­ Если да, то вам, скорее
лировать программы в случае, если вы сделали ошибку и пыта­ всего, уже известна большая
етесь сделать нечто, лишенное смысла, например вычесть Fido часть этого материала. Но
из 48353. выполнить упражнения все
равно имеет смысл, так как
не исключено, что С# чем-то
отличается от известных
Э1ЛЛ0 Эило вам языков.
перем енно.
C ^ in t m a x W e ig h t; ^ \\
s t r in g m e ssage j
bool boxCheckedr

м огут храниться. переменнь 1Х.

Переменные меняются
Для работы
В процессе работы программы любой переменной может быть с числами, тек­
присвоено произвольное значение. То есть значения перемен­
ных меняются. Это ключевая идея любой программы. К приме­ стом, булевы­
ру, если переменной m yH eight было присвоено значение 63:
in t m yH eigh t = 63;
ми значениями
как только имя m y H e i g h t появится в коде, C# заменит его зна­ и любым другим
чением 63. Представим, что позднее ему было присвоено зна­
чение 12: видом данных
m yH eigh t = 12;
используйте
Теперь C# будет заменять параметр m yH eight на число 12, не­
смотря на то что имя переменной не изменилось. переменные.
92 глава 2
это всего лишь код

Присвоение значений
Поместите в программу эти операторы:
int Z ;
Отсутствие у пере­
M e s s a g e B o x . S h o w ("О твет " + z ) ;
менных значения
При попытке запустить программу, ИСР откажет­
ся компилировать код. Компилятор проверил ваши
переменные и обнаружил, что им не присвоено ни­
является препят­
какого значения. Чтобы избежать подобных ошибок,
- <
имеет смысл комбинировать оператор объявления

Присвоенные ствием для компи­
переменной с оператором присвоения значения: значения. ляции. Этой ошиб­
ки легко избежать,
xWeight объединив в один
message оператор объяв­
bxChecked ление переменной
и присвоение ей
ß объявлении переменной, как
и раньше, указывается ее тип.
значения.

Некоторые типы переменных


Тип переменной определяет, какие именно данные в ней можно хра­ верш енной
нить. Подробно типы будут рассматриваться в главе 4, а пока запом­ ^ом еняте.
ните три наиболее используемых типа. Переменные типа i n t сохра­
няют целые числа, переменные типа s t r i n g —текст, а переменные
типа b o o l —логические значения true/false.

пе-ре-мен-ный, пррш.
умеющий меняться или приспосабливаться.
Переменная скорость дрели позволяет Бобу свер­
лить то быстро, то медленно, в зависимости от
текущих задач.

дальше > 93
операторы наготове

Д л я пр о гр ам м и ста сл о во string
Знакомые математические симболы почти всегда о значает «строка
текста», а со кр ащ ен и е int указы ­
Числа, хранящиеся в переменных можно складывать, вы­ в ает на целое число.
читать, умножать и делить. В этом вам помогут о п е р а т о р ы
(o p e r a to r s). Часть из них вам уже знакома. Рассмотрим код,
решающий несложную математическую задачку:
Третий оператор присваивает
переменной num ber значение
Мі?і об-ьябилы пере 3 6 * 1 S = S40. Следующий
М бнкую num ber int number = оператор присваивает новое
целочисленного значение 12. - (42. / 7) = 6.
U присбои nvunber = number
ли ей знйченме
з а т е м .puö^6«A« number = 36
m 6 резул і^тате
О п ератор « :S ^ Im b e r
чего ока іА,олучилй number = 12 - к значению перем
зняченме Я-?-
number += 10; г » « “«
вы получите %■&>■
Оператор *= number *= 3;
означает, что
вы должны ум н о­ number = 71 / 3;
жить текущее л Если71 разделить на 3 получится 2 3 .6 6 6 6 6 6 ...
значение перем ен­ \ __ _ МО результ ат деления целых чисел также должен
ной на 3^ в итоге быть целым, поэт ому 23.666... округляется до 23.
получаем 48. int count =
count ++; Целочисленные переменные используются в счетчике
в сочетании с операторами +-+ и Инкремент ++-
Класс MessageBox count --; увеличивает значения на
вызывает окно а декремент — ум еньш ает на 1.
с текстом «hello
again kello».
String result = "hello"; Оператор + в данном
случае соединяет вместе
result += " again " + result; две строки. При этом
числа авт омат иче­
MessageBox.Show(result); ски преобразовываются
Пустые кавычки к т ипу string.
обозначают строку. result = "the value is: + count;
О
не содержащун:>
символов. result =
Переменные типа
bool принимают
значения true или
false. Оператор ! bool yesNo = false;
обозначает от рица­
ние и меняет зна- bool anotherBool = true; Н е в о л н у й т е с ь , е с л и в ы н е см о гл и
чение с true на false зап ом н и ть в сё.
и обратно. y e ^ o = !anotherBool;
Эта информация будет часто повто­
ряться в книге.
94 глава 2
это всего лишь код

Наблюдение за переменными 6 процессе отладки


Отладчик позволяет понять, как работает программа. Рассмотрим код
с предыдущей страницы.
О Новы й проект W indows Forms Application
Перетащите на форму кнопку и щелкните на ней два раза. Введите код с пре­
дыдущей страницы.
Forrrd.cs D X
ИСР откроет
’^Chepter_2_Code.Formi »I ‘j g ^ b y t t o n X s e n d e r , ЕуепЫ г^ ej
новый проект
■ p riv a te v o id b u t t o n l_ C lic k (o b je c t sen d er, E v e n tA rg s e )
с пустой формой
{ и точкой входа.
I // H ere’ s a g r e a t w a y t o u s if t h e ID E t o see how t h i s c o d e ¥ io r k sl
I U Вы можете дать
\ // F ir st, c re ate a new p r o j e c t in t h e lO E a d d a b u t t o n . I 4 e x t , d o u b l e - c l i c k o n t h e ему любое имя.
! // b u tto n 50 th e lO E a d d s a b u t t o n l _ C l i c k s ^ t h o d t o y o u r p r o g r a m *. F i l l in t h a t
I // ise th o d w ith a i l o f th e cod e, sta rtin g w ith " i n t num ber = 1 5 ; " .
i //
: // p u t a b r e a k p o in t on th e f i r s t lin e by r i g h t - c ii c k in g on i t an d c h o o s in g
; // "B re a k p o in t » I n s e r t B r e a k p o in t" . The l in e sh o u ld tu rn red ,
//
// № e x tj s t a r t d e b u g g in g y o u r p ro g ra *a . Y o u 'l l se e it b r e a k on th e l i n e w h ere you
// in s e r te d th e b r e a k p o in t. Y our p ro g ram ’ s j u s t p a u se d t I f you c li c k t h e Run
/ / t o o lb a r b u tto n (o r h it F 5 )j it 4 # iil c o n t in u e . R ig h t- c lic k on "n u re b e r" an d
// ch o o se ''E x p r e s s i o n : ’ n u fsb e r ' » A dd i ^ a t c h " frcffli t h e ise n u - T h e b o t t o r e p a n e l i n
// t h e lO E s h o u l d ch an ge t o t h e W a t c h e s w in d o w ^ a n d t h e r e s h o u l d be a lin e in th at
/ / w in d o t« f o r " fiu f iib e r ” . S t e p th ro u g h t h e p ro g ra is l i n e by lin e u s in g S te p O ver (F I® )
// You c a n se e th e v a lu e o f th e ’’ n u m b e r’' v a r i a b l e ch an ge e s you g o !
//
// Do t h e sam e f o r t h e c o u n t , r e su lt, y e sfto , an d a n o t h e r B o o l v a r i a b l e s .
Комментарии
выделяются
n u is b e r » n ufflber -f 1@ ; < :r зеленым
num ber = 3 6 * IS ; цветом
n um ber = 1 2 - (4 2 / 7>; и м огут
n u m b e r + = l@ j
содержать
num ber * = 3i
любой т екст ,
num ber = 7 1 / 3 ;
компилятор
in t count »
их не замечает.
c o u n t+ + ;
c o u n t--;
Д ост игнув точки останови
strin g r e s u lt = "h e llo ” ; программа п р е к ^ а щ Т е Т ^
re su lt + * " a g a in ” + re su lt;
H e s s a g e B o K ,S h o w ( r e s u lt ) ; w eS m » “
r e su lt * ” th e v a lu e iis j “ + c o u n t;
re su lt =

b ool yesH o = f a ls e ;
b o o l a n o t h e r B o o l =» t r u e ;
y e sfto = ta n o th e r B o o l;

}
lioo%~

О Вставка точки останова в первую строчку кода


Щ елкните правой кнопкой мыши на строке ( i n t num ber = 1 5 ;) и выберите команду Insert
Breakpoint из меню Breakpoint. (Можно воспользоваться Toggle Breakpoint из меню Debug или
нажать клавишу F9.) .
------------------- ► ЦереВернише страницу и проДоЛжим!
прекратим ошибки!

О О тл ад ка программы
Щ елкните на кнопке Start Debugging (или нажмите клавишу F5) (Можно использовать команду
Start Debugging, меню Debug.) Программа начнет работу и откроет форму.

П е р е х о д к точке останова
Как только программа дойдет до точки останова, ИСР автоматически вызовет редактор кода
и выделит нужную строчку желтым цветом.

О in t пияЬег « I5j
num ber = numiaer + 1 0 j
num ber = 3 6 * I S ; В процессе отладки
; num ber = 12 - (4 2 / 7 ) достаточно навести на
: num ber + = l e j переменную указатель
num ber * = 3 ;
мыши,, чтобы появилось
всплывающее окно
: num ber = 7 1 / 3 j
со значением этой
переменной.

Контрольное значение перем енной number /


Щ елкните правой кнопкой мыши на переменной num ber и выбе­
рите в появившемся меню команду Expression: ‘number’ » Add
Watch. Внизу экрана появится окно Watch:
I
Watch Пx |
1М ате j Velue jType
Ц
і
••• ядЛ ег , :0 ■" ,, /
....... ■■r ..........
Функция Watch
_1
помогает отсле­
_______________[^iWatchJ
живать значе­
О бход кода
Нажмите F10, чтобы обойти эту строку. (Можно также выбрать в ния переменных
меню Debug команду Step Over или щелкнуть на кнопке Step Over
панели инструментов Debug.) Выделенная строка будет выполнена, на каждом эта­
и значение переменной num ber станет равным 15. Теперь желтым
будет выделена следующая строка кода. пе выполнения
Присвоенное
кода. Вы оце­
переменной
значение т ут ните ее удобство
же появилось
в окне Watch. но мере услож­
П родолж ение роботы прогроАлмы
нения ваших
Для возвращения в обычный режим выполнения программы на­
жмите F5 или выберите в меню Debug команду Continue. программ.
96 глава 2
это всего лишь код

Циклы
СОБЕТ^
в большинстве сложных программ одни и те же действия повто­ Наличие непарных скобок, явля­
ряются больше одного раза. В такой ситуации на помощь прихо­ ется препятствием к построению
дят циклы (loops) —они заставляют программу выполнять набор программы. К счастью, достаточно
операторов, до тех пор пока указанное вами условие не примет навести указатель мыши на скобку,
значениёС ^^е (или false])). и ИСР тут же выделит ее «вторую
половину»:
bo o l te st^
w h ile == t r u e )
Вот почему іл'як бйжны
переменные логического {
типа. // C on ten ts o f t h e lo o p

8 цикле while операторы


внутри фигурных скобок
выполняются до тех
пор. пока условие имеет Цикл for состоит из т рех операторов. Первый задает
значение true. начало цикла. Третий оператор цикла будет выполняться,
пока соблюдается условие, заданное вторым оператором.

for (int i = 0 ; i < 8 ; 1 = 1 + 2 )


{
MessageBox. Show (''Я появлюсь 4 раза");
}
Написание простого цикла при noMouiu фрагментов кода Количество «прогонов»
цикла зависит от значения
Через минуту вам предстоит написать программу. Эту задачу мож­ ^ р а м е т р а length (длина),
но ускорить средствами ИСР. Если набрав ключевое слово f o r , dm om парамет р может
дважды нажать кнопку Tab, нужный код наберется сам. При вводе быть как константой.
имени переменной оно автоматически появится во всем фраг­ 1^ а к и переменной.
менте. Повторное нажатие кнопки Tab перемещает курсор на
переменную length.

fo r (in t § = if
{
Достаточно изменить имя
переменной здесь, и оно
автоматически изменится
во всем фрагменте кода.

дальше ► 97
приготовились, настроились, кодируем!

еС К о Л ь К о С оБ ехооБ
н
Перейдем к практике
★ Не забывайте, что в конце оператора
Работа программы определяется операторами. Но должна стоять точка с запятой:
операторы существуют не в вакууме. Впрочем, про­ name = " J o e " ;
ще всего это понять на примере. Создайте проект
W indows Form s A pplication. ★ За двумя косыми чертами идут
комментарии:
// этот тек ст игнорируется

★ Объявляя переменную, укажите ее


им я и ти п (типы будут подробно
рассматриваться в главе 4):
i n t w e ig h t;
/ / w e ig h t — ц ел о е число

★ Код для классов и методов заключается


в фигурные скобки:
p u b l i c v o i d Go О {
/ / з д е с ь ваш к о д
}
★ В большинстве случаев количество
пробелов не имеет значения:
in t j = 1234 ;
Оператор, Выводящий сооби^ение это то же самое, что и:
Дважды щелкните на первой кнопке и добавь­ in t j = 1234;
те к методу b u t t o n l _ C l i c k () эти операторы.
Внимательно изучите код и результат его вы­
полнения.

. э то п е р е м е н н а я , in t p r iv a te v o id b u tto n l_ C lic k (o b je c t send er, E v en tA rg s e)

{
м елое им ело, остальная О Л е к т PI относится
часть оператора ^ // э т о ком ментарий
Присваивает п е р е м е н н о й strin g name = " Q u e n t i n " ; Math из пространства
знам ени е 3 . I ^м ен System , поэтами
in t X ^ 3;

X = X * 17; долж нТ''


d o u b le d = M a th .P I / 2; usm gsystem .
M essa g eB o x .S h o w (" n a m e is
n a m e is Q u e n tin + " \n x is " + X
x is 5 1
d is l.5 7 0 7 » 3 2 6 7 9 4 9 + " \n d i s " + d );

^х~о 1^оследовательность,
OK добавляющая в окно сообщения символ
переноса строки.

98 глава 2
это всего лишь иод

О ператоры if/e ls e инициируют действия при выполнении или


невыполнении заданны х условий. В большинстве случаев речь
идет о проверке равенства. Для этого используется оператор ==.
Не путайте его с оператором (=), который присваивает перемен­
ным определенное значение.

о п е р а т о р -Т
с проверки условия.
Л
if (someValue == 24)
{
О „ер а« о р в ф м а Р " » "
MessageBox.S h o w ("Значение 24 ."); . сковах выполняется
со1ли>д‘ ш и
> д л я сравнения аввх » « S e ®
условия.
и сш А И и етея о « ер « ~ о р «
з н » « р «венет6«^

// Количество операторов в скобках


// может быть произвольным
Если заданное
условие соблюдено, MessageBox.Show("Значение 24.");
Выполняется т г
первый оператор, / exse
в противном
случае — второй. — 2^ MessageBox.S h o w ("Значение отлично от 24.");

f i С л ед и те за количеством знаков рав енств а в операторе!


руды не
I Оператор, состоящ ий из одного знака (=) присваивает пере-
о с ш о | - 'о ^ jji, значение, а из двух знаков (==) — сравнивает две пе­
ременные. Эт о очень распрост раненная ошибка. Есл и вы видите сообщение
«невозможно преобразовать тип ‘int’ в тип ЪооГ», скорее всего, именно эта
ошибка является его причиной.

дальше > 99
то, что вы можете сделать

ПроберЬ условий
Рассмотрим работу условного оператора i f / e l s e .
Использование
Проверка при помои^и операторов условного опера
Вы уже познакомились с оператором ==, проверяющим, равны ли
друг другу две переменные. Существуют и другие операторы срав-
тора для сравне
нения:
ния двух чисел
Оператор != в отличие от оператора == принимает значе­
ние true, если переменные н е р а в н ы . называется про­
★ Операторы > и < выявляют, какая переменная больше.

веркой условия.
Операторы ==, ! = , > и < называются о п е р а т о р а м и с р а в н е ­
н и я . С их помощью осуществляется п р о в е р к а у с л о в и й .

Операторы сравнения можно комбинировать при помощи


оператора && (И) и оператора | | (ИЛИ). К примеру, усло­
Для возвращения в режим
вие i равно 3 или j меньше 5 записывается как ( i == 3) II редактирования кода
(j < 5). остановите отладку
программы. Это можно
сделать при помощи
команды Stop Debugging из
меню Debug.
Задание переменной и проверка ее значения
Ниже вы видите код для второй кнопки. Он содержит оператор i f /
else, проверяющий, равна ли целочисленная п е р е м е н н а я х десяти.

p r iv a te v o id b u tto n 2 _ C lick (o b je ct send er, E v e n tA r g s e)


{
^ in t X = 5;
Объявим (if (X == 1 0 )
!^рем енную 'I
XU присвоим M e s s a g e B o x . S h o w (" X m u s t b e 10" );
ей значение s. }
Зат ем п ро- e lse
верим, равна {
ли она 1о. M e s s a g e B o x . S h o w ( "X i s n ' t 10" );
}
}

Это результат работы програм м ы . О тр ед акти р уй те код таким


образом , чтобы со о б щ е н и е и зм ени лось на х m ust be 10.

100 глава 2
это всего лишь код

Проверка еще одного условия


Щ елчок на третьей кнопке должен приводить к появлению В этой строке проверя­
ется, равна ли переменная
окна. Самостоятельно отредактируйте код, чтобы в окне появ­ someValue т рем , и содер­
лялся этот текст. жит ли переменная пате
значение «Joe».
p r iv a te v o id b u tto n 3 _ C lick (o b je ct sen d er, E v en tA rg s e)

{
in t som eV alu e = 4;
str in g name = "Bobbo J r ." ;
if ((so m eV a lu e == 3 ) && (na m e == " Joe" ))

{
M e s sa g e B o x .S h o w (" x i s 3 a n d t h e name i s Joe" );

}
M e s sa g e B o x .S h o w ( " t h i s lin e ru n s no m a tte r w h at" );

Добавление цикла
Ниже показан код для последней кнопки. Он содержит два цикла. Цикл
w h ile , который реализует операторы в скобках, пока выпоняется заданное
условие. И цикл f o r . Посмотрим, как это работает.

p r iv a te v o id b u tto n 4 _ C lick (o b je ct sen d er, E ven tA rgs e)


{
in t count = о;

Цикл работает, Это условие проверяется


пока значение A w h ile ( c o u n t < 10)
переменной { перед началом работы
цикля. Цикл запускается
count меньше 1 0 . count = count + 1;
при соблюдении услобия.

Здесь переменной
используемой Оператор, при
для подсчета каждом проходе
проходов цикла цикла увеличиваю-
присваивается щый значение I на х.
’ . . . - л значение.
н а ч а л ь н о е M e s sa g e B o x .sh o w (" T h e a n s w e r is " + cou n t)
}

П ер ед тем как щ ел кнуть на кнопке, п опы тайтесь по в и д у кода понять,


какое зн ачен и е будет в окне. П роверьте пр ав и л ь но сть св ои х вы водов!

дальше ► 101
выше и выше и выше и...

возьми в руку карандаш


Попрактикуйтесь в проверке условий циклов. Посмотрите на код
и напишите пояснения к каждой его строке.
Заполните
остальные
i n t r e s u l t = 0 ; / / п ер ем ен н ая , в которую б у д е т за п и с а н р е з у л ь т а т строки по
------------ - аналогии.
I n t X = 6; I I объявим переменную х и присвоим значение 6

w h ile (х > 3) {

/ / операторы б у д у т вы полняться, пока

r e s u l t = r e s u l t + х ; / / прибавим х

X = X - 1; / / вычтем

}
for (in t z = l ; z < 3 ; 2 = z + l ) {

/ / начнем цикл с .....................................

/ / 191КЛ р а б о т а е т пока

//на каждом э т а п е , .................................

r e s u lt = r e s u lt + z; / / ....................

}
/ / Следующий о п ер атор вызывает окно д и а л о г а с т ек ст о м

//
M e ssa g e B o x .S h o w (" Р езу л ь т а т р а в ен " + r e s u l t ) ;

Еще о проверке условий


п р о в е р к а условии о су щ е ст в л я е м ся п р и п о м о щ и
о п е р а т о р о в ср а в н е н и я :
X < у (меньше чем)
X > у (больше чем)
X == у (равно)

Э т и о п е р а т о р ы и с п о л ь з у ю т с я ч а щ е всего.

102 глава 2
это всего лишь код

Подождите! А что случится с


циклом, если я напишу условие,
которое никогда не выполняется?

Значит, цикл никогда не закончится!


Результатом каждой проверки условия является
значение t r u e или f a l s e . В первом случае цикл
выполняется еще раз. Рано или поздно вы долж­
ны получить другой результат, и тогда цикл за­
кончится.

цикдізі-

возьми в руку карандаш


Вот несколько циклов. Какие из них являются бесконечными?
Сколько раз выполняются остальные циклы?

Ц и кл # 1 Ц и кл # 3 Ц и кл # 5
in t count = 5; in t 3 = 2 ; in t р = 2;
w h ile (count >0) { fo r (in t і = 1; і < 100; fo r ( in t q = 2; q < 32;
count = count * 3; І = І * 2) q = q * 2)
count = count * -1; { {
‘И = j - І; w h ile (p < q )
} Сколько раз w h ile (j < 25)
будет выполнен {
эт от опера­ { P = P * 2;
тор? j = j + 5;
Ц и кл # 2
in t i = 0; Сколько раз kq = p - q;
}
in t count = 2; будет выполнен ■
э т о т оператор?
w h ile (i == 0 ) {
count = count * 3; Ц и кл # 4
При повторении
count = count * -1 w h ile (tru e) { in t і = 1;} \ 1^тератора ^ = а * 2
помните, что начальное
} значение ^ равно 2..

\
8 цикле for сначала
выполняется проверка
услобия, а потом
итератор.
^ Ш ТУРМ
Подумайте, в какой ситуации может понадобиться
бесконечный цикл? (Один из вариантов ответа вы
найдете в главе 13...)

дальше ► 103
если только, но только если

- 1^озьми в руку карандаш


Решение Вот, как следовало закончить строчки комментариев к програм­
ме, иллюстрирующей работу циклов и проверку условий.

i n t r e s u l t = 0; / / п ер ем ен н ая , в которую б у д е т за п и с а н р е зу л ь т а т

i n t X = 6 ; / / объявим переменную х и ..............

w h i le

/ / операторы б у д у т вы полняться, п ок а X больше 3

r e s u l t = r e s u l t + х ; / / прибавим х К переменной result

X = X - 1; / / вычтем i из пер ем енн о й X

Этот цикл выполняется дважды: сначала при г


} равном 1, зат ем при г равном а . Как только
fo r (in t 2 = Z + 1) {
Z получит значение 3 , условие перест анет со­
блюдаться, и цикл остановится.
11 начнем 191КЛ с обьявления переменной г и присвоения ей значения 1
/ / цикл р а б о т а е т пока Z меньше 3

//на каждом э т а п е значение переменной z увеличивается на 1

r e s u lt = r e s u lt + z; / / п е р е м е н н а я Z п р и б а в л я е т с я к п е р е м е н н о й result

}
/ / Следующий о п ер атор вызывает окно ди а л о га с тек ст о м

M e ssa g e B o x .S h o w (" Р езу л ь т а т равен" + r e s u l t ) ;

Г в руку карандаш
Сравните свои ответы на вопрос, сколько раз будет выполнен
'ешение тот или иной цикл с правильными ответами.

Ц и кл # 1 Ц и кл # 3 Ц и кл # 5
Будет выполнен один раз. Будет выполнен семь раз.
Будет выполнен
восемь раз.
Ц и кл # 2 Ц и кл # 4
Бесконечный цикл Еще один бесконечный цикл.

Попробуйте реш ить задачу номер пять. Это отличная возможность поработать с о т ­
ладчиком! Сделайте оператор с) = р ~ точкой останова и проверьте, как изменяются
значения переменных р и q с помошрю окна Watches■

104 глава 2
это всего лишь код
часвдо
Задаваем ы е
БоЦ|эос;ь1

Всегда ли оператор принадлежит к какому-нибудь клас­ Насколько внимательно нужно относится к автоматиче­
су? ски создаваемому коду?

Г ! Да. Все операторы принадлежат к определенным кпассам,


точно также как все классы, в свою очередь, принадлежат про­
! Относитесь к нему достаточно внимательно. Нужно понимать
соответствие между кодом и вашими действиями, чтобы при не­
странствам имен. Когда вы используете конструктор для задания обходимости иметь возможность отредактировать его вручную.
свойств объектов формы, может показаться, что некоторые опе­ Впрочем, в подавляющем большинстве случаев все необходимые
раторы находятся вне классов. Но внимательное рассмотрение изменения можно проделать с помощью ИСР.
кода показывает, что на самом деле это не так.

Существуют ли пространства имен, которые я не могу КЛЮЧЕВЫЕ


использовать? А как насчет тех, которые я обязан исполь­
зовать?
МОМЕНТЫ
■ Вашу программу заставляют работать операто­
Q l Да, некоторые пространства имен использовать не реко­ ры, которые всегда принадлежат к какому-либо
мендуется, например, пространство имен S y s t e m . Именно там
классу. Класс же, в свою очередь, находится
находятся S y s t e m . D a t a , позволяющий работать с таблица­
в каком-то пространстве имен.
ми и базами данных, и S y s t e m , ю , обеспечивающий работу
с файлами и потоками данных. Но по большей части вы можете ■ В конце операторов стоит знак (;).
называть пространства имен, как вам нравится. Или отдать это на
откуп ИСР, которая будет автоматически создавать имена, взяв за ■ В ответ на ваши действия Visual Studio автома­
основу имя программы. тически добавляет в программу код.

■ Блоки кода, такие как классы, цикл w h i l e , опе­


! А все-таки, зачем нужно ключевое слово partial? раторы if/else и многие другие виды операторов
Б
заключаются в фигурные скобки { }.
( 1 ; Оно позволяет распределять код одного класса между разны­
ми файлами. При создании формы, ИСР сохраняет редактируемый ■ Результатом проверки условия является значе­
вами код в файл (к примеру, F o r m l . c s ) , а автоматически моди­ ние t r u e или f a l s e . С его помощью опреде­
фицируемый — в файл ( F o r m l . D e s i g n e r . c s ) . Но оба они на­ ляется, будет ли закончен цикл, а также какой
ходятся в одном пространстве имен. Достаточно объявить простран­ именно блок кода будет реализован в операторе
ство имен в верхней части файла, и ему будет принадлежать все, что if/else.
попадает в фигурные скобки, расположенные ниже. При этом в од­
ном файле могут находится объекгы из разных пространств имен ■ Для хранения данных используются перемен­
и разных классов. Но об этом мы поговорим в следующей главе. ные. Оператор = присваивает переменной зна­
чение, а оператор == сравнивает значения двух
Что происходит с кодом, который был автоматически переменных.
создан ИСР, если воспользоваться командой Undo? ■ Цикл w h i l e выполняет все операторы в фигур­
ных скобках, пока результатом проверки усло­
^ ; Попробуйте, и вы сами найдете ответ на этот вопрос! Пере­ вия является значение t r u e .
тащите на форму кнопку и поменяйте ее свойства. А затем вос­
пользуйтесь командой отмены. Вы увидите, что в простых случаях ■ Как только проверка условия дает значение
ИСР просто возвращает вас в предьщущую точку. Но при попытке f a l s e , цикл w h i l e прекращает работу, и про­
отменить вставку в базу данных SQ L появится окно с предупреж­ грамма переходит к оператору, расположенному
дением, что после отмены операции вы не сможете отменить свое сразу после цикла.
решение командой Redo.

дальше > 105


ваш код... теперь в виде магнитов

^ а Г н и ш ы С К одам и
Мы поместили блоки с фрагментами кода на С# на магниты для холодильника. Со­
ставьте из них работающую программу, результатом которой является окно с сообще­
нием. Некоторые магнитики упали на пол, и они слишком малы, чтобы их поднимать,
поэтому просто впишите недостающий код от руки! (Подсказка: вам понадобится все­
го пара фрагментов!)

Пустыми кавычками обозначена


пустая строка. Это означает,
что переменная Result еще не со­
держит текста. j

Этот магнит if (X == 2) {
s t r in g R esu lt = упал на пол..
R esu lt = R esu lt + " b e

}
--------------
if (X > 2) {
R e s u l t = R e s u l t + "a";

>
i n t X = 3; 1

Результат:

M essageB ox.Sho w (R e eult);

О ш Б е ш ы н а с. I 3 -

106 глава 2
это всего лишь код
Вам придется выполнить много В процессе чтения книги вам предстоит соз­
подобных упражнений. О т ве­ дать много разных приложений, и все их нужно
ты можно найти на следуюш,ей оудет кйк~т .0 млдснобйте?, Мь>/ совстуем присво-
странице. Если вы не знаете, как umt? эт ому приложению имя «Z Fun with if-else
реш ит ь задачу, не стесняйтесь statem ents» ( z Забава с операторами if-else ), п о ­
подсмотреть. лученное в результ ат е комбинации номера главы
и текста 8 строке заголовка формы.

Попрактикуемся в использовании оператора if/else. Сможете создать программу?


аж нш е
Ф орм а, которую нуж но получить. Д обавь те ф лаж ок.
Перетащите флажок с панели инструмен­
тов на форму и отредактируйте свойство
T e x t . Аналогичным способом нужно будет
отредактировать текст кнопки и метки.
a-J Fun with if/else statements!

Change the color if the


® Бпййе color d ianghg Э то метка.
bexisdiecked
В окне Properties поменяйте размер
Press the шрифта и сделайте его полужирным.
С помощью свойства B a c k C o lo r сде­
лайте фон красным. Выберите вари­
ант R ed на вкладке web colors.

Если поль зов атель забы л постав и ть ф л аж ок, д о л ж н о


вы водится окно, и нф о р м и р ую щ ее его об этом.
Имя флажка c h e c k B o x l . Если флажок установлен, должно соблю­
даться условие:
c h e c k B o x l. C heck ed == t r u e

При установ л енно м ф л аж ке щ ел чо к на кнопке д ол ж ен м енять ф оновы й


цвет метки.
Красный фоновый цвет метки должен меняться на синий. И наоборот. Вот опера­
тор, задающий фоновый цвет метки с именем l a b e l l :
l a b e l l .B a c k C o l o r = C o lo r .R e d ;

(Подсказка: Проверка условия, является ли фоновый цвет метки красным, выгля­


дит практически так же, но с одним маленьким отличием!)

дальше * 107
симпатично!

Построим что-то яркое! Начните с создания нового проекта Windows Forms Application.
п'ражнение
Вот ф орм а, которую
нуж но получить

П ерем енная, э при наличии


cm6yew только внy^v^P^* переменнук>, нужно
ЭвйХ циклов h r '» » « ■ “ " “ “ Г е « “ Й рем е^ная с а * е оЙ.»в-

о С делайте цюновый цвет броским!


Пусть результатом щелчка на кнопке станет ци­
кличное изменение фонового цвета! Создайте
цикл, в котором значение переменной с меняется
от Одо 253. Вот как выглядит код:
th is.B a c k C o lo r = C o lo r .F r o tn A r g b (c , 255 - с, с) ;
A p p lic a tio n .D o E v e n tsО ; ^ ^

перес^анет о Л о в л К м т а к кок уЭмби меня ц б е то м !

\ „ в» MOW-“ зага««»» “
Метод РоЕуепЬв () принудительно на о д g варианты п т
обновляет форму на каждом витке свои собственные р
цикла, но это «обходной» пут ь,
который не следует использовать
в серьезных программах.

о З ам ед л и те процесс
Чтобы цвета менялись медленнее, после стро­
ки A p p l i c a t i o n . D oE vents () напишите:

S y stem . T h rea d in g . T h read . S le e p


ммен system .Threading.

108 глава 2
это всего лишь код

Сглаж ивание процесса


Пусть цветовая гамма возвращается к изначальному цвету. Для этого до­
бавьте еще один цикл, в котором переменная с будет меняться уже от 254
до 0. В фигурные скобки поставьте блок кода из предыдущего цикла.

Сделайте процесс непрерывным находящийся


Поместите два цикла внутрь третьего, который будет выполняться бес- внутри другого
конечно. В итоге щелчок на кнопке будет приводить к непрерывному цикла, называется
изменению фонового цвета. (Подсказка: цикл w h i l e является бесконеч- "-^в^оженным».
ным, если проверка условия дает результат ( t r u e ) ! )

Ой-е-ей! Программа не останавливается!


После запуска этой программы остановить ее работу просто за­
крыв окно уже невозможно! Для прекращения ее работы восполь­
зуйтесь, к примеру, командой Stop Debugging из меню Debug.

О О становите ее!
Сделаем так, чтобы цикл, добавленный на предыдущем
шаге, останавливался при закрытии формы. Замените пер­
вую строчку на:
w h ile (V isib le )

пустите программу и щелкните на крестике в правом


верхнем углу формы. Окно закроется, и программа остано­ Подсказка: && означает
«И ». Именно эт от опера­
вится. Пусть и с задержкой в несколько миллисекунд.
т ор позволяет соединить
Ш есте несколько условий.
Проверку видимости объекта можно Результат будет ист ин­
не писать в форме — Visible == true. ным только при одновре­
Достаточно первой половины выражения. менном выполнении эт их
условии.
/
tn e .
!/
М ож ете ли вы о б ъ я сн и ть эту зад ерж ку и пер еп и сать код
та ки м образом , чтобы в о звр ащ ени е в реж им р ед акти р о ­
вания прои сходи ло сразу после закры ти я ф орм ы ?

дальше у 109
решение упражнения

ажнение Вот как должен выглядеть текст программы, циклично изменяющей фоновый цвет формы.

еш ш е
u s in g System ;
u sin g S y s te m .C o lle c tio n s.G e n e r ic ,■
u sin g S ystem .C om p on en tM od el,•
u sin g S y stem .D a ta ;
u sin g S y stem .D r a w in g ;
u s in g S y stem .L in q ; Mb/ присвоили решению имя
Fun with if Else, поэтому
u sin g S y stem .T ex t;
ИСР назвала пространство
u sin g S y stem .W in d o w s.F o r m s; имен т аким ооразом.

nam espace F u n _ w ith _ If_ E lse


{ После двойного ш,елчка на
p u b lic p a r tia l c la ss Form l : F orm кнопке ИСР добавила к ф ор­
ме метод ЬиШ т^сИскП.
{ Этот мет од запускает ­
p u b lic F o rm l0 ся при каждом ш,елчке на
{ кнопке.
In itia liz e C o m p o n e n t();
}

private void buttonl_Click(object sender, EventArgs e) Внутренний


{ оператор if
if (checkBoxl.Checked == true) проверяет
{ цвет метки.
Если мет ка
Внешний if (labell BackColor == Color.Red) красная,
оператор if { выполняется
проверяет, оператор,
labell.BackColor = Color.Blue; изменяющий
установлен
ли флажок. ' } цвет на синий.
else
{
labell.BackColor = Color.Red; Если мет ка
} не красная,
} оператор
изменяет цвет
e ls e на красный.
{
MessageBox.Show("The box is not checked")
}

} Это окно диалога появляется


при от сутст вии флажка.
С качать код с реш ени ям и всех уп р а ж н ен и й из книги
м ож но зд есь w w w .h ead firs tlab s.co m /b o o k s/h fcs h arp /

110 глава 2
это всего лишь код

Добавляя эт от мет од, ИСР поставила допол­


Построим что-то яркое!
;ненУ1е нительные пробелы перед фигурными скобками.
Иногда для экономии мест а эти скобки м огут
реш ение располагаться н й одной строке с операт о­
ром — в с * такая форма записи вполне до­
пустима.
Иногда в разделе «Решение» 1г
приводится не весь код про­ Крайне важно, чтобы ваш код могли легко чит ать
граммы^ а только те ф раг­ другие пользователи. Но мы намеренно показываем
менты, которые требовалось вам разные варианты, так как вы должны привы­
отредактировать. кнуть чит ат ь код, написанный в различном стиле.

p r iv a t e v o id b u tto n l_ C lic k (o b je c t s e n d e r, E v e n tA r g s e),

/ '" '^ w h i l e (V is ib le ) {

с = 0; с < 254 № V i s i b l e ; C++)


пока открыта
форма. t h i s . B a c k C o l o r = C o l o r . F ro m A rg b ( с , 255 - с , с );

A p p l i c a t i o n . D o E v e n ts ( ) ; ^ „г
V Ч Чиклй меняют
S y ste m .T h re a d in g .T h re a d .S le e p O );
^ в разные стороны.
}
fo r (in t с = 254; С >= О && V i s i b l e ; с--) {

t h i s . B a c k C o l o r = C o l o r . F ro m A rg b ( с , 255 - с , с );

A p p lic a tio n .D o E v e n ts О ; О п е р а т о р && п о з в о л я е т


пю ерВат ь цикл for, как
S y s t e m . T h r e a d i n g . T h r e a d . S l e e p (3) ; J^omko п а р а м е т р ViSime
^ прим ет значение false.

} П о м ни те вопрос, как уб р ать зад ер ж ку с возвращ ением


^ в реж им р ед акти ров ани я после закры тия окна?

Задержка возникает из-за невозможности проверить зна­


чение параметра Visible до завершения цикла for. Поэтому
к проверке условия добавили код && V i s i b l e .

Любую задачу программирования можно решить более чем


одним способом, так что попробуйте написать свой вариант
кода, взяв за основу циклы while вместо циклов for.
дальше ► 111
это не такой простой ребус, как кажется

Б бассейне

Возьмите фрагменты кода из бассейна


и поместите их на пустые строчки. int X = 0;
Каждый фрагмент можно исполь­ String Poem
зовать один раз. В бассейне есть
и лишние фрагменты. Нужно while (
получить программу, которая мо­
жет быть скомпилирована и за­
пущена. Имейте в виду, что задача if ( X < 1
не так проста, как кажется на первый
взгляд! }
Результат:
i f ) {

i f ( X == 1 ) {

if ( ) {
Такие задачки иногда будут встречаться
в книге. Любители логических загадок
должны их оценить. Но даже если вы
не относитесь к их числу, попробуйте
найти решение.

К а ж д ы й ф р а гм е н т
ко д а м о ж н о и с п о л ь ­
зо в а т ь то л ь ко од и н
раз!
X>0
X< 1 X=X
X> 1 X=X
X>3 X= X Poem = Poem + “noys
Poem = Poem +“ x<4 X= X Poem = Poem + “oise
Poem = Poem + “a Poem = Poem + “ oyster'
Poem = Poem + “n“; Poem = Poem + “annoys”;
MessageBox.Show(Poem);
Poem = Poem + “an“; ^ Poem = Poem + “noise”;

112 глава 2 QinBem на с. 114


это всего лишь код

аГниш ь! с КодаМи

Ну что ж, пришло время посмотреть, как же правиль­


но расположить магниты с фрагментами кода, упав­
шие на пол с холодильника.
решение ребуса

е Ш е н и е ]= » е ^ с :а Б б а с с е й н е

Требовалось расположить представленные


в бассейне фрагменты кода на пустых
строчках таким образом, чтобы в итоге
получилась работающая программа.

in t X = 0;
S t r i n g P o em =

w h ile ( X < 4 ) {

Poem = Poem + "a";


if ( X < 1 ) {
Poem = Poem + " Результат:
}
Poem = Poem + "n";

if ( X > 1 ) {
3 noise зппо^fs ап oyster
Poem = Poem + ” oyster";

X = X + 2;
ОК 1
}
if ( X == 1 ) {

Poem = Poem + "noys

if ( X < 1 ) {
В и д и те д р у го е р е ш е н и е ?
Poem = Poem + "olse П р о в ер ь те его в И С Р
} и п о с м о тр и те , как о но
р аб о та ет!
X = X + 1;
}
MessageBox.Show(Poem);
Подсказка: Существует еще
одно решение.

114 глава 2
3 обьекш ы , 310 порядку ст|^ойс:я
%

Каждая програллма решает какую-либо проблему.


Перед написанием программы нужно четко сформулировать, какую задачу
она будет решать. Именно поэтому так полезны объект ы . Ведь они позво­
ляю т структурировать код наиболее удобным образом. Вы же можете сосре­
доточиться на обдумывании пут ей решения, так как вам не нужно тратить
время на написание кода. Правильное использование объектов позволяет
получить интуитивно понят ный код, который при необходимости можно
легко отредактировать.
майк ищет работу

Ч то думает Майк о сбоих проблемах


Майк —программист в поисках новой работы. Ему не терпится показать,
как хорошо он пишет программы на С#, но сначала ему нужно попасть на
собеседование. А он опаздывает!

О АЛайк обдумы вает, каки м марш рутом лучш е поехать.

Через мост на 31-й улице,


вперед по Либерти-авеню,
а потом через Блумфилд.
Майк выбираем,
маршрут.

О Благодаря радио ДДайк узнает о большой


пробке, и з -з а которой он м о ж ет опоздать.

Это Френк Лауди с


информацией о пробках. Из-за трех
каким столкнувшихся на Либерти-авеню
ну>кио ехаиА!?- машин остановилось движение
вплоть до 32-й улицы.

О М а й к придум ы вает, как объехать пробку


и попасть на собеседование вовредля.

8 результ ат е появ­
ляется совсем другой О
Ничего, я успею
марш рут .
вовремя, если сверну
на 28-е шоссе!

116 глава 3
объекты, по порядну стройся!

Проблема Майка с точки зрения набигационной системы


Майк использует навигационную систему GPS, Диаграмма класса N avigator
которая помогает ему в перемещениях по городу. в программе Мййкй.
Вверху имя класса, SetCurrentLocationO
внизу перечислены
методы. ^ SetDestinationO
ModifyRouteToAvoidO
SetDestination("Fifth Ave & Penn Ave"); ModifyRouteTolncludeO
Резцльтат работы м е- GetRouteO
string route;
тоЪа GetRouteO - строка GetTimeToDestinationO
route = GetRoute TotalDistanceO
с марилрутом.

Навигационная
система генерирует
V
"Take 31st Street Bridge to Liberty Avenue to Bloomfield"
м арш рут , исходя из
пункта назначения.
Навигационная система
узнает, каких улиц
следует избегать. ^

\1/
ModifyRouteToAvoid("Liberty Ave");

string route;
route = GetRoute();

"Take Route 28 to the Highland Park Bridge to Washington Blvd"

^ Метод GetRouteQ прокладывает


м арш рут , исключив улицы,
цлииы.
которые Майк хочет объехать.

Навигационная система Майка решает про­


блему с прокладкой маршрута так же, как это
сделал бы он сам.
дальше ► 117
создаем методы и редактируем маршруты

Методы прокладки и редактирования маршрутов


Класс Navigator содержит методы, которые выполняют все действия. В отли­
чие от уже знакомых вам методов button_Click () они решают другую задачу:
прокладывают маршрут по городу. Именно поэтому Майк поместил эти методы
в единый класс и присвоил ему имя Navigator.
Для определения маршрута сначала вызывается метод SetDestination (), ука­
зывающий конечную точку, затем применяется метод GetRoute (), выводящий
маршрут в виде символьной строки. Если маршрут требуется изменить, на по­
мощь приходит метод ModifyRouteToAvoidO , позволяющий избежать опре­ Мйык выбирает
деленных улиц. Затем метод GetRoute {) выводит новый вариант маршрута. для методов
значимые имена.
class Navigator {

public void SetCurrentLocation(string locationName) {

ptiblic void SetDestination (string destinationName) { , };


public void ModifyRouteToAvoid(string streetName) { ... };

public (string GetRoute 0 { ... };

string route =
GetRoute {);
вм вровде« 3 „ „ e"L ,

Методы, возвращающие значение Вот пример метода,


возвращающего ^
Методы состоят из операторов. Некоторые из них выполняют все входя­ значение типа int.
Метод использует
щие операторы и заканчивают работу. Другие же в о з в р а щ а ю т к а к о е-т о два параметра
з н а ч е н и е . Это значение принадлежит к определенному типу (например, для вычисления
s t r i n g или i n t ) . результ ат а,
а зат ем при помощи
О ператор r e t u r n прерывает работу метода. Если метод не возвращает оператора return
значения, тип возвращаемого значения объявляется как v o id , присут­ передает значение
ствие этого оператора является необязательным. Но если метод возвра- X вызвавилему его
щает значение, без оператора r e t u r n не обойтись. i / оператору

p u b lic in t M u ltip ly T w o N u m b er s(in t firstN u m b er, in t secondN um ber) {


in t r esu lt = firstN u m b er * secondN um ber;
retu rn r e su lt;

Оператор вызывает метод, перемножающий два числа. Возвращаемое


значение принадлежит к типу int: В м етоЭь! м ож но
п о д с т а в л я т )^ не
in t m y R e su lt = M u ltip ly T w o N u m b er s(3, 5); ' только константы,
но 1А перем еним .
118 глава 3
объекты, по порядку стройся!

КЛЮЧЕВЫЕ
МОМЕНТЫ
Классы состоят из методов, которые, в свою очередь, состоят из операторов. Осмысленный выбор методов позво­
ляет получить удобный для работы класс.
Некоторые методы могут возвращать значение. Тип этого значения нужно объявлять. Например, метод, объявлен­
ный как public int, возвращает целое число. Пример такого оператора: return 37 ;
Метод, возвращающий значение, обязан включать в себя оператор return. Если в объявлении метода указано
public string, значит, оператор return возвращает значение типа string.

После оператора return программа возвращает управление оператору, вызывающему метод.

Метод, при объявлении которого было указано public void, не возвращает значения. Но оператор return
может использоваться для прерывания такого метода: if (finishedEarly) { return; }.

Построим программу с использованием классоб


Привяжем форму к классу и сделаем так, чтобы принадлежащая фор­
ф
ме кнопка вызывала метод этого класса. араж нение

^ Создайте новый проект Windows Forms Application. В окне Solution Explorer щелкни­
те правой кнопкой мыщи на имени проекта и вы берите в появившемся меню команду
A dd»C lass... Н азовите файл Talker.cs, при этом класс автоматически получит имя
Talker. В ИСР появится новая вкладка с именем Talker.cs.

Сверху вставьте строчку using System.Windows .Forms, a затем введите код самого класса:
class Talker {
p u b lic s ta tic i n t B la h B la h B la h (s tr in g th in g T o S a y , i n t n u m b e r O fT im e s )
{
О перат ор s tr in g f in a lS tr in g =
о5т?я6ляет
кхеременнуи? fo r ( i n t c o u n t = 1; c o u n t <= numberOf T im e s; co u n t+ + )
finalString { X
и пры соаийа fin a lS tr in g = fin a lS tr in g + t h i n g T o S a y + " \n " \
t m ей нулевое ^
К переменной FinalString
c m y ^ C w p o K y )- M e s s a g e B o x .S h o w (fin a lS tr in g ) ;
добавляется значение
r e tu r n fin a lS t r in g .L e n g t h ; , переменной thingToSay и знак
1
} Метод BlahBlahBlahQ возвращает
^ переноса строки (\п).
значения типа int. Свойство .Length ^ Свойством Length
можно добавить к любой строке обладают все строки.
и узнать ее длину. Знак переноса (\п)
считается за один
символ.

IJej^eBepHuiue с тр ан и ц у и и родолж ш !

дальше ► 119
знакомство с объектами

*
Ч то )ке мы только что построили?
Новый класс содержит метод с именем B l a h B l a h B l a h {) и двумя параметрами. Первый пара­
метр — строка с произносимым текстом, а второй — количество повторений. Этот метод вы­
зывает окно, в котором фраза повторяется указанное количество раз. Метод возвращает дли­
ну строки. Ему следует передать строку для параметра t h i n g T o S a y и число для параметра
n u m b e r O f T i m e s . Указанные значения вводятся в поля формы, созданные на основе элементов
управления T e x t B o x и N u m e r ic U p D o w n .
Добавим в проект форму, работающую с новым классом!
О т редакт ируйте свойство
Text таким образом, ч т о ­
бы в текстовом поле по
умолчанию появлялся т екст Saythis: Hello!
«Hello!» # oftimes: 3

О Вот внешний вид формы.


Дважды щелкните на кнопке, чтобы добавить к ней код, вызывающий метод B l a h B l a h B l a h ( ) / .
Он должен возвращать целое число 1еп: Свойству M inimum
<^рисвойте значение и,
p r iv a te v o id b u tto n l_ C lick (o b ject sen d er, E ven tA rgs e) a с в о ^ т в ^ ^ а ^ е '^ :^ ^ ^ "

i n t l e n = T a l k e r .B la h B la h B la h ( t e x t B o x l.T e x t, (in t)n u m e r ic U p D o w n l.V a lu e );


M e s sa g e B o x .S h o w (" T h e m e s s a g e l e n g t h i s " + l e n ) ;

Запустите программу! После щелчка на кнопке вы увидите


два окна с текстом. Класс вызывает первое окно, ф орма —
второе.
ШШ Возвраи^енное
ШШ
Метод BlahBlahBlahQ вы­
методом
зывает окно с сообш,ением,
сформированным на основе значение форма T h e m e s s a g e le n g th is 21
НеКо!
введенных пользователем Не«Ы
показывает
Hello! в эт ом окне
параметров. диалога. \ /
f Ш 1

...... ......

Методы добавленного к проекту оасса можно


использовать и в других классах.
120 глава 3
объекты, по порядку стройся!

ІІдея Майка Вот если бы сравнить


несколько маршрутов и выбрать
Собеседование прошло замечательно! из них самый быстрый...
Но утренние пробки заставили Майка за­
думаться об усовершенствовании его на­
вигационной программы.

Почему бы не создать три класса Navigator...


Майк м о ж ет скопировать код класса N a v i g a t o r и вставить его Э т о диаграмма классов.
в другие классы. В результате программа получит возможность В ней перечисляются
одновременно сохранять три маршрута. все 6кодяш,ие в класс
методы. Это наглядное
представление
выполняемых классом
N a v ig a to r функций. .
SetDestinationO N a v ig a to r2
ModifyRouteToAvoidO
ModifyRouteTolncludeO SetDestinationO
N a v ig a to rs
GetRouteO ModifyRouteToAvoidO
ModifyRouteTolncludeO SetDestinationO
GetTimeToDestinationO
GetRouteO ModifyRouteToAvoidO
TotalDistanceO
GetTimeToDestinationO ModifyRouteTolncludeO
TotalDistanceO GetRouteO
GetTimeToDestinationO
TotalDistanceO

Получается, что редактировать


О ( метод теперь придется три раза
вместо одного?

И м енно так! У прав л ение тр ем я копиям и одного кода — непро стая за д а ­


ча. Но большинство задач, с которыми вам придется столкнуться, требуют не­
однократного использования одного элемента. В данном случае это один марш­
рутизатор. Нужно сделать так, чтобы редактирование любого фрагмента кода
сопровождалось внесением аналогичных изменений во все его копии.

дальше ► 121
про экземпляры

Объекты как способ решения проблемы


Объектами (objects) называются инструменты С#, позволя­
ющие работать с набором одинаковых сущностей. Именно
благодаря им, один раз написав класс N a v i g a t o r , Майк смо­
жет использовать его нужное количество раз.

N a v ig a to r
3 « « . « SetCurrentLocationO
W3 C^WCKOM SetDestinationO
ModifyRouteToAvoidO
дде1ляоЭоб.' ModifyRouteTolncludeO
GetRouteO
GetTimeToDestinationO
TotalDistanceO

' Для сравнения


т рех марилрутов
Майк воспользу-
i# ^ ется т ремя о6г>-
актами Navigator.
Д л я создани я об ъ екта д остато чн о
клю чевого слова new и им ени класса.

Navigator navigatorl = m e v y Navigator () ;


navigatorl.SetDestination("Fifth Ave & Penn Ave");
string route;
route = < g a ]^ g a ^ o rl. GetRouteTT

О б ъ ект уж е м ож но использовать! Так как


он получен из кл асса, то сод ер ж и т все
вход и в ш ие в кл асс методы .

122 глава 3
объекты, по порядку стройся!

Возьмите класс и постройте объект


Класс можно представить как копию объекта. Скажем, для по­
строения пяти одинаковых домов в коттеджном поселке архи­
тектору не нужно рисовать пять одинаковых чертежей. Для вы­
полнения задачи вполне достаточно одного.

Определяя класс, вы определяете


и его методы, точно также как
чертеж определяет внешний вид
дома. '

На основе одного чертежа


Можно построить сколько
угодно домов, а из одного
класса можно получить
сколько угодно объектов.

Объект берет методы из класса


Оператор new позволяет создать произвольное количество объ­
ектов одного класса. При этом все методы исходного класса ста­
новятся частью объекта.

Дом
ДатьКровО
ВыраститьЛужайкуО
ДоставитьПочтуО
ПочиститьКанализациюО
ЗаплатитьНалогО
СделатьРемонтО

дальше > 123


объекты совершенствуют ваш код

Экземпляры
Все элементы окна Toolbox являются классами: класс B u t t o n ,
класс T e x t B o x , класс L a b e l и т. п. При перетаскивании
на форму кнопки из окна Toolbox автоматически создает­
ся экземпляр класса B u t t o n , которому присваивается имя
b u t t o n l . Перетаскивание второй кнопки приводит к появле­
нию второго экземпляра с именем b u t t o n 2 . Каждый экзем­
пляр имеет собственные свойства и методы. Но при этом все
кнопки работают одинаково, так как они были созданы из од­
ного класса.

До: Так выглядим памят ь


компьютера перед началом
работы программы.

' ^выполняется
/ ^<^ератор new.

Ho u s e m a p l e D r i v e l l S = n e w H o u s e ();

^"^У п р> аж н ен и е
Убедитесь сами!
Откройте любой проект, в котором присутству­
ет кнопка b u t t o n l , и найдите в его коде текст
b u t t o n l = new. Этот код ИСР добавила в кон­
Экземпляр,
структор форм при создании экземпляра класса образец, вещь, подобная
B u tton . другим, функция поиска и заме­
ны находит все экземпляры слова
и заменяет их.

124 глава 3
объекты, по порядку стройся!

Простое решение! с и / -- это сокращение от


graphical User Interface
Майк придумал новую программу сравнения маршрутов, ко­ {^р^сричсский иии/\срфсис
торая ищет кратчайший путь при помощи объектов. Вот как пользователя).
она создавалась.

Майк добавил к СТЛ текстовое поле — ЪехЬВох!, содержащее информацию о пункте назна­
чения. Затем создал поле te x tB o x 2 , для ввода названия улицы, которую нельзя включать
в маршрут; и поле ЬехЬВохЗ, содержащее информацию о еще одной улице, которую жела­
тельно объехать.

Это экземпляр
© О н со зд а л о б ъ е к т N a v i g a t o r и указал пункт н а зн а ч ен и я . класса Navigator.

N a v ig a to r
SetCurrentLocationO
SetDestinationO
IVIodifyRouteToAvoidO
str in g d e stin a tio n = te x tB o x l.T ex t;
!\/1odifyRouteTolnclude()
GetRouteO N a v ig a to r n a v i g a t o r l = n e w N a v i g a t o r () ;
GetTimeToDestinationO
TotalDistanceO n a v ig a to r l. S e t D e s t i n a t i o n ( d e s t i n a t i o n ) ;

ro u te = n a v i g a t o r l . G e t R o u t e () ;

O h добавил второй объект N a v ig a t o r с им енем n a v ig a t o r 2


и вы звал м е т о д S e t D e s t i n a t i o n O э т о г о о б ъ ек т а для зада­
н и я пункта н а з н а ч ен и я , п о с л е ч е г о вы звал м е т о д M o d i f y ­
R o u t e T o A v o i d () ( Р е д а к т и р о в а т ь и з б е г а е м ы е у л и ц ы ) .

Третий объект N a v i g a t o r называется n a v i g a t o r s . Майк указал „ „ М string-


пункт назначения и вызвал метод M o d i f y R o u t e T o l n c l u d e O
(Редактировать включаемые в маршрут улицы) J

vigotorl'
5,6 км Создание нового
объекта на основе
класса называется
Теперь Майк может вызвать общий для всех объектов метод
T o t a l D i s t a n c e O (Общее расстояние) и определить самый
созданием экзем­
короткий маршрут. И для этого ему понадобился один фраг­ пляра класса.
мент кода, а не три!

дальше У 125
немножко маленьких секретов

Эй, подождите-ка! Данной вами


информации недостаточно для того,
чтобы написать навигационную
программу!

Э т о д е й с т в и т е л ь н о т а к . Программа построения маршрутов очень


сложна. Но сложные программы работают по тому же принципу, что
и простые. Навигационную программу Майка мы привели только
как пример использования объектов на практике.

Теория U практика
Наша книга построена по следуюгцему принципу. Вводится некое по­
нятие, для демонстрации которого используются картинки и неболь­
шие фрагменты кода. На данном этапе вы должны всего лишь попять
новую информацию. Процедура написания рабочих программ будет
рассматриваться позднее.

Ho u s e m a p l e D r i v e l l S = n e w Ho u s e О ;

Знакомясь с новыми понятиями


(например, с объектами), вни­
мательно смот рит е на кар­
тинки и код.

У вас будет достаточно возможностей применить полученные знания


на практике. Иногда это выполнение письменных упражнений, одно
из которых вы найдете на следуюш,ей странице. В других случаях вам Если у вас слож­
сразу будет предложено написать код. Такая комбинации теории с
практикой является самым эффективным способом запоминания но­ ности с выполне­
вой информации.
нием упражнения,
Упра)княясь В написании кодов...
...желательно помнить следуюш;ее: можно сразу по­
★ Сделать опечатку очень легко. При этом даже одна незакрытая
скобка является препятствием к построению программы.
смотреть решение.
★ Лучше подсмотреть решение, чем долго мучиться. Мучения от­ Его можно даже
бивают желание учиться.
★ Код, который вы найдете в этой книге, протестирован и рабо­ скачать с сайта
тает в Visual Studio 2010! Но от опечаток никто не застрахован
(можно перепутать 1 и букву L нижнего регистра). Head first Labs.
126 глава 3
объекты, по порядку стройся!

Г в руку карандаш
Попробуйте вслед за Майком написать код для объектов
N a v i g a t o r И вызова ИХ
методов.

str in g d e stin a tio n = te x tB o x l.T e x t;


Здесь Майк задавал пункт
str in g r o u te 2 S tre e tT o A v o id = tex tB o x 2 .T ex t; назначения и улицы,
str in g r o u te 3 S tr e e tT o In c lu d e = tex tB o x 3 .T ex t; которых следует избегать.

N a v ig a to r n a v ig a to r l = new N a v i g a t o r ( ) ; А здесь MW создаем объект


n a v ig a to r l.S e tD e stin a tio n (d e stin a tio n ); n a v i g a t o r , указываем п у н к т
in t d ista n ce l = n a v ig a t o r l.T o t a lD is t a n c e {);
назначения и определяем
расстояние.

. Создайте объект navigator2, укажите пункт назначения, вызовите метод ModifyRouteToAvoidO,'^


а затем воспользуйтесь методом TotalDistanceO для вычисления переменной distance2.

Navigator navigator2 =

navigator2.

navigator2.

int distance2 =

^2.CoздaйтeoбъeктnavigatorЗ,yкaжитeпyнктнaзнaчeния,вызoвитeмeтoд^ЯodifyRouteToincludeO,~*
a затем воспользуйтесь методом TotalDistanceO ДЛЯ вычисления целой переменной distances.

L _
Встроенный в .NET Framework метод Math.MmQ
сравнивает два числа и возвращает меньшее. Именно
с его помощью Майк нашел самый короткий путь.

in t sh o r te stD ista n c e = M a th .M in (d is ta n c e l, M a th .M in (d is ta n c e s, d ista n ce s ));

дальше > 127


слово static

|Лозьми В руку карандаш


п Вот как правильно создать объекты N a v i g a t o r и вызвать их
6Ш6НИ6 методы.

str in g d e stin a tio n = te x tB o x l .T e x t; Зд есь И айк задавал


str in g r o u te 2 S tre e tT o A v o id = te x tB o x 2 .T e x t; н азн ач ен и я
^ \ M улицы , кот оры х
str in g r o u te S S tr ee tT o In clu d e = tex tB o x 3 . T e x t; j сл ед ует и зб ега т ь.

N a v ig a to r n a v ig a to r l = n e w N a v i g a t o r () ; ^ ^ СОзЭаеМ о б ш к т
n a v ig a to r l. S e tD e stin a tio n (d e stin a tio n ) ; ^ a .v ig a t o r , у к а з ы в а е м п у н к т
in t d ista n ce l = n a v ig a to r l .T o ta lD ista n c e 0 ; ^ н а з н а ч е н и я и определяем
расст ояни е.

1. Создайте объект navigator2, укажите пункт назначения, вызовите метод ModifyRouteToAvoidO,


а затем воспользуйтесь методом TotalDistanceO Для вычисления переменной distance2.

Navigator navigator2 = new NaviyatorQ

navigator2 . SetDestination(destination);

navigator2 . ModifyRouteToAvoid(routeZStreetToAvoid);

int distance2 = navigatorZ.TotalDi$tanceQ;

*~2.CoздaйтeoбъeктnavlgatorЗ,yкaжитeпyнкт назначения, вызовитеметод ModifyRouteTolncludeO,"^


a затем воспользуйтесь методом TotalDistanceO для вычисления переменной distances.

navigator3.SetDestination(destination);

navigator5.ModifyRouteTolnc!ude(route3StreetTolnclude);

int distances = navigators.TotalDistanceQ;

Встроенный 6 .NET Framework метод Matk.MinQ


сравнивает два числа и возвраш,ает меньшее.
Именно с его помощью Майк нашел самый
короткий путь.
in t sh o r te stD ista n c e = M a th .M in (d is ta n c e l, M a th .M in ( d i s t a n c e s , d ista n ce s))

128 глава 3
объекты, по порядку стройся!

Я написал уже несколько новых классов, но ни разу


не воспользовался ключевым словом new! Получается,
О я могу вызывать методы, не создавая объектов?

Д а! И и м енно поэтом у в м етодах и споль зов алось клю чевое сл о во s t a t i c .


Еще раз посмотрим на объявление класса T a lk e r:
c la ss T a lk er
{
p u b lic sta tic in t B la h B la h B la h (strin g th in g T o S a y , in t n u m b erO fT im es)
{
str in g fin a lS tr in g =

П ри вызове метода не создавался новы й экземпляр T a lk e r. Вы написали только:


T a lk e r.B la h B la h B la h (" H e llo h e l l o h e llo " , 5);

Именно так вызываются с т а т и ч е с к и е методы, с которыми вы работали до сих пор.


Если убрать модификатор s t a t i c из объявления метода B l a h B l a h B l a h (), вызов
метода окажется уже невозможен без создания экземпляра T a l k e r . Впрочем, это
единственное отличие статических методов. Вы можете передавать им параметры,
они возвращают значения и принадлежат определенным классам.
Модификатором s t a t i c можно отметить целы й класс. Все входящие в этот класс
методы также долж ны бы ть с т а т и ч е с к и м и . Добавив в статический класс нестати­
ческий метод, вы сделаете компиляцию невозможной.

Чаап°
'^ а Д а Б а е М ы е
Б о гц р о с ь і

Слово «статический» ассоциируется у меня с веща­ Почему не сделать статическими все методы?
ми, которые не меняются. Означает ли это, что нестати­
ческие методы могут меняться, а статические нет? 0 1 При наличии объектов, отслеживающих данные, напри­
мер, экземпляров класса N a v i g a t o r , каждый из которых
0 ; Нет. Единственным отличием статического метода от отслеживал свой маршрут, для работы с этими данными мож­
нестатического является невозможность создавать его эк­ но использовать собственные методы экземпляра. Скажем,
земпляры. Слово «статический» в данном случае не следует при вызове метода M o d i f y R o u t e T o A v o i d () для экзем­
воспринимать слишком буквально. пляра n a v i g a t o r s менялся только маршрут номер два.
На маршруты экземпляров n a v i g a t o r l и n a v i g a t o r s
То есть я не смогу пользоваться классом, не создав эта процедура никак не влияла. Именно поэтому программа
экземпляр объекта? Майка могла работать с тремя маршрутами одновременно.

0 ; Создание экземпляров является обязательным услови­ • А как именно экземпляры отслеживают данные?

О
ем для работы с нестатическими классами. Для статических
классов это не требуется. ; Переверните страницу и узнаете!

дальше * 129
как дела у объектов

Поля
Редактирование расположенного текста на кнопке осуществляет­
ся при помощи свойства T e x t . При внесении подобных измене­ Технически вы
ний в конструктор добавляется следующий код: задаете свойстЯп
Свойс\тва очень
b u tto n l.T e x t = "Текст для кнопки";
покожи на поля^
Как вы уже знаете, b u t t o n l —это экземпляр класса B u t t o n . ' Код но об эт ом мы
же редактирует поле этого экземпляра. На диаграмме классов спи­ поговорим чуть
позже.
сок полей находится сверху, а список методов —снизу.

Сверху на диаграмме
классов находится список
полей. Экземпляры
класса используют их Эта линия
для отслеживания своего отделяет поля
состояния. от методов.

Метод — это то, что объект делает. Поле — это то, что объект знает^.
в результате создания Майком трех экземпляров класса N a v i g a t o r его программа создала три
объекта, каждый из которых отслеживает свой маршрут. При вызове метода S e t D e s t i n a t i o n ()
для экземпляра n a v i g a t o r 2 пункт назначения указывается только для этого экземпляра. Он ни­
как не влияет на экземпляры n a v i g a t o r l и n a v i g a t o r 3 .

N a v ig a to r Каждый из экземпляров
Navigator знает свой пункт
Destination назначения и свой маршрут.
Route

SetCurrentLocationO Объект Navigator позволяет


SetDestinationO указывать пункт назначения,
ModifyRouteToAvoidO
редактировать возможный
марш рут и получать
ModifyRouteTolncludeO информацию об эт ом
GetRouteO маршруте.
GetTimeToDestinationO
TotalDistanceO
Поведение объекта определяется его
методами, поля используются для от­
слеживания его состояния.
130 глава 3
объекты, по порядку стройся!

Создаем эЬемкдяры!
M em odL
Для добавления полей достаточно объявить I
п е р е м е н н ы е в н е м е т о д о в . Так, в с е эк зем п л я - c l a s s C lo w n {
ры будут и м ет ь с в о и к о п и и э т и х п е р е м е н н ы х . str in g Nam e;

p u b lic in t H eig h t;

p u b lic v o id T a l k A b o u t Y o u r s e l f () {
M e s s a g e B o x . S h o w ( "My n a m e is "
+ Nam e + " and I'm "
+ H eig h t + " in c h e s ta ll." );

При создании экземпляра,


не использиймя ключевое Операт ор *= означает,, что
слово static ни в объявленш парамет р, стояиА,ий справа от
класса, ни в объявлении /" оператора, нужно умножить
метода. на парамет р, стоящий слева.
м/
^Возьми в руку карандаш
Клоуны рассказывают о себе при помощи метода
Та1кАЬои1Уоигзб1^ Они называют имя и рост. Напишите,
какие сообщения будут содержать всплывающие окна.
C lo w n o n eC lo w n = new C lo w n ( );
o n e C lo w n .N a m e = " B offo" ;

o n e C lo w n .H e ig h t = 14;

o n e C lo w n .T a l k A b o u t Y o u r s e l f ( ) ; «Меня зовут , мои рост .дюймов»

C lo w n a n o th er C lo w n = new C lo w n ();

a n o th e r C lo w n .N a m e = " B iff" ;
a n o th e r C lo w n .H e ig h t = 16;

a n o t h e r C lo w n .T a lk A b o u t Y o u r s e lf ( ) ; «Меня зовут. , мои рост .дюймов»

C lo w n c lo w n s = new C lo w n {);

c lo w n 3 .N a m e = a n o th e r C lo w n .N a m e ;

c lo w n 3 .H e ig h t = o n e C lo w n .H e ig h t - 3^

c l o w n 3 .T a l k A b o u t Y o u r s e l f () ; «Меня зовут , мой рост .дюймов»

a n o th e r C lo w n .H e ig h t *= 2;

a n o t h e r C lo w n .T a lk A b o u t Y o u r s e lf 0 ; «Меня зовут , мои рост. дюймов»

дальше > 131


складываем объекты в кучу

Спасибо за память
Создаваемые объекты находятся в так называемой куче
(heap) — области динамической памяти, выделяемой на
стадии исполнения программы. Применение оператора
new автоматически резервирует место в памяти под хра­
нение данных. /
Вот так выглядит куча до
начала работы программы.
Д д, она действительно
пуста.

Внимательно посмотрим на происходяи^ее здесь

Решение Вот как нужно было заполнить пробелы в тексте.

Операторы jjew создают


C lo w n o n eC lo w n S ^ ^ ew C l o w n () экземпляр?« класса Clown,
резервируя участки памяти и
o n e C lo w n .N a m e = " B S tfn - заполняя их данными об объекте
o n e C lo w n .H e ig h t = 14;

o n e C lo w n .T a l k A b o u t Y o u r s e l f () ; «Меня зовут Boffo , мой рост 3-4 дюймов^

C lo w n a n o th er C lo w n = (n ew C lo w n ( );

a n o th e r C lo w n .N a m e = " B iff" T
a n o th e r C lo w n .H e ig h t = 16;

a n o t h e r C lo w n .T a lk A b o u t Y o u r s e lf 0 ; «Меня зовут Biff , мой рост дю йм ову

--- - .
C lo w n c lo w n 3 = m ew C lo w n () ;
c lo w n B .N a m e = an o B H S F C T o ra .N a m e;

c lo w n 3 .H e ig h t = o n e C lo w n .H e ig h t - 3;

c l o w n 3 .T a l k A b o u t Y o u r s e l f ( ) ; «Меня зовут Biff _ мой рост 3-3- дюймов»

a n o th e r C lo w n .H e ig h t *= 2;

a n o t h e r C lo w n .T a lk A b o u t Y o u r s e lf ( ) ; «Меня зовут Biff . мой РОСТ S 2 дюйма».

Новые объекты добавляются в кучу — динамически рас


пределяемую память.
132 глава 3
объекты, по порядку стройся!
Это экземпляр
Ч то происходит 6 памяти программы класса Clown.

Программа создает новый экземпляр класса C low n :

C lo w n m y l n s t a n c e = new C lo w n ( );

В выражении использованы два оператора. Пер­


вый объявляет переменную типа C l o w n ( C l o w n
m y l n s t a n c e ; ) . Второй создает новый объект и
присваивает его только что созданной переменной
(m y ln sta n ce = n e w C l o w n О ; ) . Вот как выглядит
куча после выполнения каждого из операторов:

C lo w n o n eC lo w n = new C lo w n о
o n e C lo w n .N a m e = " B offo" ;
^Ьект
o n e C lo w n . H e ig h t = 14; vvOA^-
o n e C lo w n .T a lk A b o u tY o u r s e lf();

C lo w n a n o th er C lo w n = new C l o w n ()
a n o th e r C lo w n .N a m e = " B iff" ;
Операторы
создают второй
a n o th e r C lo w n .H e ig h t = 16; объект и п р и ­
' • a n o t h e r C l o w n . T a l k A b o u t Y o u r s e l f {)
сваивают ему
данные.

о C lo w n c lo w n 3

c lo w n 3 .N a m e
= new C lo w n 0 ;

= a n o th e r C lo w n .N a m e ;
c lo w n 3 .H e ig h t = o n e C lo w n .H e ig h t - 3
-c lo w n s.T a lk A b o u tY o u rself();

a n o th erC lo w n .H eig h t *= 2;

■ ' a n o t h e r C l o w n . T a l k A b o u t Y o u r s e l f () ;

Так как команда n e w н е


используется, новый объ­
ект не создается. Редак­
т ирует ся только и н­
формация, которая уже ^
имеется о памяти.

дальше > 133


зачем нужны методы

Значимые имена
При написании кода методов выбирается структура программы. Вы ищете
ответы на вопросы: воспользоваться ли одним методом или, может быть,
разбить его на несколько? А может быть, метод тут вообще не нужен? В ре­
зультате можно получить как интуитивно понятный код, так и нечто за­
путанное и нечитаемое.

О Перед вами пример компактного кода. Это программа, управляющая автоматом по производ-
ству шоколадных батончиков.

іпЬ t = т.сЬкТетр( ч^сло... но дляцелое


tb, т — а {t > 160) { чего?
ужасные имена! Т tb = пеш
Вы никогда не Метод с1&Тгр\'() имеет
ЬЬ.с1зТгр\/((2
догадаетесь, ---- один П(Хр(ХМС\Л/\.р} но
І С 8 . Р ІП () ; б жызнм не угадаете его
зачем нужны эти
переменные и ic s.V e n t О ; предназначение.
какова функция .т.аігзузсЬкО
класса Т.

Вы можете, в зглянув на этот код, понять, какую ф ункци ю он в ы полняет?

© Операторы не дают даже намеков на предназначение кода. Зато нужный результат полу­
чен при помощи всего одного метода. Но всегда ли максимальная компактность является
конечной целью? Давайте разобьем код на несколько методов и сделаем его более про­
стым. Вы на практике убедитесь в необходимости классов и осмысленных имен. Для на­
чала нужно понять, какую задачу решает данный код.

Управление автоматом, производящим батончики


Автоматизированная система проверяет температуру
нуги каждые 3 минуты. Если она выше 160 °С, необхо­
димо выполнить процедуру охлаждения (С1С8).
• З а к р ой т е клапан турби ны #2
• Зап ом ни те водой систему охлаж дения

• В ы п устите воду

• У беди тесь, ч то в с и с т е м е отсутствует воздух

134 глава 3
объекты, по порядку стройся!
О Инструкция не только помогла определить назначение кода, но и показала, как сделать его
более понятным. Мы узнали, что проверка условия определяет, не превышает ли параметр
Ъ значение 160, ведь в инструкции написано, что при 160 °С нуга становится слишком го­
рячей. А буква т оказалась классом, который контролирует поведение автомата. В класс
входит статический метод проверки температуры нуги и работы воздушной системы. Вы­
делим проверку температуры в отдельный метод и дадим классу и методу смысловые имена.

' p u b l T c ^ ' b o o l e a ^ I s N o u g a t T o o H o t {) {
Тип значений, in t tem p = M aker. C h eck N ou gatT em p eratu re{);
возвращ аем ы х (tem p > 160) { ^ Назовем к л а с с M a k e r
^ r e t u „ t.ue,. L

j , , , ^ 3 ^ наг«).

, ^ В о зв р а щ а е м ы е зн ач ен и я
J т и п а B o o le a n , э т о tr u e
и л и fa lse .

При превышении рекомендуемой температуры инструкция требует выполнения процеду­


ры CICS. Создадим еще один метод и выберем для класса Т (он управляет турбиной) более
значимое имя. Переименуем также класс i c s (управляющий изолированной системой ох­
лаждения). Этот класс содержит два статических метода. Один —для заполнения системы
водой (fill), второй —для слива воды (vent):

p u b l i c (y o i d J P o C I C S V e n t P r o c e d u r e () {

/к о д и ф и к а т о р v o id T u r b in e tu r b in e C o n tr o lle r = new T u r b i n e () ;
озн ач ает , чт о м ет о д t u r b i n e C o n t r o l l e r . C lo se T r ip V a lv e (2) ;
Т н а ч е н т '^ ^ ^ '^ ни как ого I s o l a t i o n C o o l i n g S y s t e m . F i l l {) ;
Is o la tio n C o o lin g S y ste m .V ent О ;
M a k e r .C h e c k A ir S y ste m O ;

}
Теперь, даже если вы не читали инструкцию и не знаете, что процедура CICS запускается
при слишком высокой температуре нуги, вы все равно можете понять, что делает код:
if { I S N o u g a t T o o H o t () == tru e) {
D oC IC S V en tP roced u re( ) ;

Помните 0 T O M , зачем вы пишете код. Это позволит сделать ко­


нечный результат простым и легко редактируемым. Выбор смыс­
ловых имен упрощает последующую расшифровку программы
другими людьми и дает возможность улучшить код!
дальше > 135
классы как они есть

Структура классоб
Почему же методы следует делать интуитивно понятными? Потому что каждая
программа решает проблему или имеет конкретную цель. Целью далеко не
всегда являются серьезные задачи, иногда программы могут писаться просто для
забавы! Но каково бы не было предназначение программы, чем больше код напо­
минает о том, какая именно проблема с его помош;ью решается, тем проще такую
программу писать, читать и редактировать.

Д и а гр а м м а к л а с с а
C la s s N a m e
Э т о н а гл я д н о е п р е д с т а в л е н и е MethodO
к л а с с а на л и с т е дуллаги. MethodO
MethodO
В в е т ц п и ш е т с я амя к л а с с а ,
а п о д н и м в се в х о д я щ и е
в класс м ет оды !

Пример построения диаграммы


Посмотрите на оператор 1 £ из пункта #5 на предыдущей странице. Вы уже знаете,
что операторы находятся внутри методов, а методы, в свою очередь, внутри клас­
сов? В данном случае оператор i f принадлежит методу D o M a in te n a n c e T e sts {),
который является частью класса С а п с 1 у С о п Ь г о 1 1 е г . Теперь разберемся, как со­
относятся друг с другом код и диаграмма классов.

c la ss C a n d y C o n tro ller {

p u b lic v o id D o M a i n t e n a n c e T e s t s () {

if ( I S N o u g a t T o o H o t () == tru e) {
D oC IC S V en tP roced u re(); C a n d y C o n tro lle r
} DoMaintenanceTestsO
DoCICSVentProcedureO
} IsNougatTooHotO

p u b lic v o id D o C I C S V e n t P r o c e d u r e ()

p u b lic b o o le a n I S N o u g a t T o o H o t {) ..

136 глава 3
объекты, по порядку стройся!

возьми в руку карандаш


Код для управления машиной по производству шоколадных
батончиков содержит еще три класса. Изучите его внима­
тельно и заполните эти диаграммы.

“»І»

Ж "
ддетоЭ.

Программа содержит

м °m Z “

дальше ¥ 137
несколько советов

Выбор структуры класса при помои^и


диаграммы
Д и аграм м ы классов п озв ол я ю т увидеть п отен ц и ал ьн ы е п р обл ем ы ещ е до
того как вы н а ч н е т е п и с а т ь к о д . П р е д в а р и т е л ь н о е о б д у м ы в а н и е с тр у к т у р ы
классов гар ан ти рует связь кода с п о ст а в л ен н о й п е р е д вам и п р о б л е м о й . О н о
п озв ол и т и збеж ать работы над ненуж ны м кодом и получить ин туитивно п о­
н я тн ую и легкую в п р и м е н е н и и программу.

П осудом ойка П осудом ойка


МытьеТарелокО Все методы класса МытьеТарелокО
ДобавкаМылаО «П осудом ойка» должны бы ть ДобавкаМылаО
ТемператураВодыО связаны с мы тьем посуды . ТемператураВодыО
ПарковкаМашины()
с этой процедурой ни как не
связан, поэтом у его нуж но
пом естить в д ругой класс.

_________________

Возьми в руку карандаш


^0ШеНИ6 должны выглядеть диаграммы клас- Понять, что Maker —
сов, которые вам было предложено запел- название класса, легко
нить на предыдущей странице. положению 6 имени
Maker.CheckAirSystemQ.

Turbine IsolationC oolingSystem M aker


FillQ
CloseTripValveQ C keckN o u g a tT em p era tu reQ
VentQ
C heckA irSystem Q

138 глава 3
объекты, по порядку стройся!

_ і^озьм и в руку карандаш


Все эти классы содержат ошибки. Напишите, в чем, по ва­
шему мнению, они состоят, и как их можно исправить.

Класс23 Этот класс является частью знакомой вам системы произ­


водства шоколадных батончиков.
ВесБатончикаО
ПечатьОберткиО
ПечатьОтчетаО
ДелаемО

Р азносчи кП и ц цы
Эти классы являются частью системы учета доставки пиццы.
ДобавитьПиццуО
ПиццаДоставленаО
ПодсчетСуммыО
ВремяВозвращенияО
Р а зн о с ч и ц а П и ц ц ы
ДобавитьПиццуО
ПиццаДоставленаО
ПодсчетСуммыО
ВремяВозвращенияО

Класс КассовыйАппарат является частью программы


Кассовы йА ппарат автоматизированной системы контроля в круглосуточном
ПродажиО магазине.
НетПродажО
ЗакачатьГазО
ВозвратДенегО
ВсегоВКассеО
СписокТранзакцийО
ДобавитьСуммуО
ВычестьСуммуО

дальше * 139
создадим класс

Возьми в руку карандаш


В от как мы ско р р екти р о в ал и классы . Но это лишь один
Решение способ решения проблемы. Можно использовать и другие спо­
собы, в зависимости от того, как предполагается использовать
класс.

V
Этот класс является частью знакомой вам системы
производства шоколадных батончиков. Д елаем Б атончики
Из имени класса С1а5523.ао() не понятно его назначение. ВесБатончикаО
ПечатьОберткиО
Кроме того, мы выбрали более осмысленное имя для ПечатьОтчетаО
СоздатьБатончикО
последнего метода.

Эти классы являются частью системы учета доставки пиццы.


Р азносчикП иццы
Классы Pe^(Very^5uy и РеИуегур 1'Н выполняли одну
Пол
задачу. Имеет смысл объединить их друг с другом,
ДобавитьПиццуО
добавив поле для ввода половой принадлежности ПиццаДоставленаО
ПодсчетСуммыО
работника. ВремяВозвращенияО

Поле Пол было добавлено,


чтобы отслеживать
результат ы работы юношей
и девушек по отдельности.

Класс КассовыйАппарат является частью программы


автоматизированной системы контроля круглосуточного Кассовы йА ппара!
магазина. ПродажиО
был удален метод ЗакачатьГаз(), как единственный, НетПродажО
ВозвратДенегО
не имеющий отношения к работе кассовых ВсегоВКассеО
СписокТранзакцийО
аппаратов. ДобавитьСуммуО
ВычестьСуммуО

140 глава 3
объекты, по порядку стройся!

p u b lic p a r tia l c la s s Form l : Form


(
p r iv a te v o id b u tto n l_ C lic k (o b je c t sender, E v en tA rg s e)
{
S tr in g r esu lt =

Echo e l = new E c h o ( ) ; Возьмите фрагменты кода из бас­


сейна и поместите их на пустые
строчки. Фрагменты можно
in t X = 0; использовать несколько раз.
w h ile { ) { В бассейне есть и лишние
фрагменты. Полученная в ито­
resu lt = resu lt + e l.H e llo 0 + " \n " ;
ге программа должна выводить
окно с показанным ниже текстом.
if ) {
e 2 . c o u n t = e 2 . c o u n t + 1; Результат:
}
if { ________________ ) {
e 2 .c o u n t = e 2 .c o u n t + e l.c o u n t ;
НеНоооо...
h elto o o o ...
hello o o o ...
X = X + 1; helloooo,..
C o u n t 10
}
M e s s a g e B o x .S h o w ( r e s u l t + "C ount: " + e 2 .c o u n t);
OK

c la ss {
p u b lic i n t ________ = 0;
Дополнительны й
p u b lic s t r i n g ________
в о пр о с!
retu rn " h e llo o o o ..
Как решить задачу, чтобы
} вместо 10 в последней
строке оказалось 24? Для
} этого достаточно заме­
нить всего один оператор.
К аж д ы й ф р а г­
м ент можно
и с п о л ь зо в а ть <4
IS 'теч*' X
н е с ко л ь ко раз! Echo
х х<5
у X > 0 Tester
е2 X > 1 Echo() е2 = е1;
count count(; Echo е2;
ЄІ = ЄІ + 1;
НеІІо() Echo е2 ■ el; x==3
ЄІ = count + 1;
Echo е2 ■ new Echo(); x == 4
el .count = count + 1;
el count = e1 count + 1;

ж®;
ОіюБЄї О на с.
дальше *
работающий класс guys

Помогите парням
Джо и Боб все время одалживают друг другу деньги. Напишем программу отслежи­
G uy
вания истории займов. Для начала нужно понять структуру создаваемого класса.
Name
Cash
Создадим класс би у и добавим в цю рм у два е го экземпляра
Первое поле формы будет называться j ое (для слежения за первым
объектом), а второе bob (для слежения за вторым объектом). GiveCashO
ReceiveCashO
Оператор neWj
создающий экземпляры Os
запускается в м омент
загрузки формы. На
рисунке представлен Итак, наш класс на­
вид кучи после этой зывается Quy (Парень).
операции. Метод GiveCaskQ
’Ьект отвечает за передачу
денег в долг, а м е ­
тод ReceiveCashQ —
за их получение. Поля
Name и Cash содержат
информацию об имени
о Сопоставим каж дом у объекту б и у поля N am e и Cash
Два объекта соответствуют двум парням, у каждого из кото­
и сумме соот вет ­
ственно.
рых есть свое имя и некоторое количество денег в кошельке.

Поле Name содержит .


информацию 00 —р
имени парня, а поле
Cash —- о сумме его
наличности.

Ч5іект&'^ Параметром
метода ReceiveCash()
является количество
получаемых денег.
Поэтому запись
О П а р н и д а ю т деньги и п о л учаю т и х назад
Метод R e c e iv e C a sh () увеличивает количество денег у объ­
joe.ReceiveCash(ZS)
означает, что Джо
получит Я5" долларов.
екта, а метод G iveC ash () уменьшает этот параметр.

форма вызывает метод ReceмeCash().


\
joe.R eceiveC ash(25) ; —I и 'р

^Ъекг Метод возвращает сум м у, которую


объект добавил к полю Cash.

142 глава 3
объекты, по порядку стройся!

Проект «Парни»
Создайте проект Windows Forms Application (ведь нам понадо­
У п і^ а ж н е н и е
бится форма). В окне Solution Explorer создайте класс с именем
G u y . Добавьте в верхнюю часть файла этого класса строчку u s i n g
S y s t e m . W i n d o w s . F o r m s ; , затем введите следующий код:

Поле Name — это строка,


с именем парня (Joe), а поле Cash
целое число, указываюш,ее на
количество наличных денег.
class Guy { Метод diveCasH) имеет
ртіЬІіс string Name; единственный парамет р
public int Cash; am ount, указываюш,ии,
сколько денег следует
отдать. /К
public int GiveCash(int amount) {
if (amount <= Cash && amount :
Оператор if проверяет, хватает ли
Л Cash -= amount;
денег на возврат долга. Если да,
Т ребуем ая сум м а return amount;
Золжнд бы т ь \ else {
б ольш е нуля.
И н аче ден ьги
MessageBox.Show(
б у д у т добавлены "У меня не хватает денег + amount.
в к о ш е л е к , а не Name + " говорит ");
взят ы о т т у д а .
return 0; ® случае нехватки денег появляется
} окно с сообщением, а мет од СЦуеСавНО
возвращает значение о (ноль).
}
М е т о д R e c e iv e C a s h Q т а к ж е
и сп о л ьзует в качест ве
public int ReceiveCash(int amount) { п арам ет ра перем енную
if (amount > 0) { a m o u n t, п р о в е р я е т ее зн а к ,
Cash += amount; и е с л и о н а б о л ь ш е н у л я ,^
д о б а в л я е т к п е р е м е н н о й cash .
return amount;
} else {
MessageBox.Show(amount + " мне не нужно",
Name + " говорит ■ • • н \ f
.
f

return 0;
Если переменная am ount больше
} нуля, мет од ReceiveCash ()
} добавляет ее к переменной
Cash. В противном случае
Следите за т ем , чтобы появляется окно с сообщением
количество открывающихся и возвращается значение О (ноль).
скобок совпадало с количеством
закрывающихся. ИСР поможет вам
в этой задаче.

дальше > 143


джо говорит: «где мои деньги?»

Форма для Взаимодействия с кодом


Теперь нам нужна форма, которая будет работать с экземплярами ■ф-
класса G u y . Она должна содержать метки с именами парней и ко­ |JoCmJ>oU3Iie
личеством денег у каждого их них, а также кнопки, управляющие
процессом взятия и возврата денег.

Н а м понадобятся две кнопки и три м етки


Две верхние метки должны показывать сумму наличности у каждого из парней. К фор­
ме также нужно добавить поле b an k — это еще одна метка. По очереди выделяйте все
метки и редактируйте их свойство «(Name)» в окне Properties. Присвоив метками имена
joesCashLabel и bobsCashLabel вместо имен labell и 1аЬе12, вы сделаете код более читабель­
ным.

Ц? Fun with ioe and Bob Верхней м е т ­


ке присвойте имя
Э т д кнопка joesCashLabel J сред-
Joe has $50 ^ ней — bobsCashLabel,
вызываем, м ем од a нижней —
ReceiveCashQ Bob has $100
объекта Joe, bankCashLabel.
передаем Свойство Text пока
The bank has $100 можно не редакти -
значение 1 0 ровать.
и вычимаем
из поля bank Qve $10 to Receive $5
сум м у, которую Joe from Bob Эта кнопка вызывает
/получает Джо.
мет од QiveCashQ
объекта Bob,
передает значение S
и прибавляет его
О П о л я формы
к полю bank.

Для отслеживания финансового состояния наших героев потребуются два поля. Назовите
их j ое и bob. Затем добавьте поле с именем b an k для расчета, сколько форма должна взять
у объектов, а сколько отдать им. Дважды щелкните на третьей метке и добавьте в появив­
шийся код строки:
n am esp ace Y o u r_P roject_N am e {
p u b lic p a r tia l c la ss Form l : F orm {

Guy joe?
Поля Joe ------^
и Bob объяв- Guy bob ; Значение поля ban k
лены в классе т о возраст ает , т о
in t bank ■ 100; И м еньилает ся в з а ­
Guy. ви си м ост и о т т о го
p u b lic F o rm l0 { ^1<олько д е н е г ф о р м а
In itia liz e C o m p o n e n t(); о т д а л а о б ъ е к т а м Quu
и ск о л ьк о взяла о т них
}

144 глава 3
объекты, по порядку стройся!

О М етод , обновляю щ ий м етки


Д о б а в и м к ф о р м е м е т о д U p d a te F o r m ( ) , ч т о б ы м етк и всегда показы вали актуальное кол и­
ч еств о денег. Убедитесь в наличии модификатора v o id , так как д а н н ы й м е т о д н е д о л ж е н
в о зв р а щ а т ь з н а ч е н и е . Д о б а в ь т е э т и с т р о ч к и п о д п р е д ы д у щ и й код:

М етки U p dateForm О {
ляю т сй при jo esC a sh L a b el .T ex t = jo e .N a m e + им еет $" + jo e .C a s h ; Этот метод
и м е е т $" + b o b . C a s h ;
обновляет м е т ­
поМОЫА,и п о л е й г b o b s C a s h L a b e l . T e x t = b o b . Name +
N a m e и C a sh J - i ки, изменяя их
м е т о д а G u y. ( b a n k C a s h L a b e l . T e x t = "B б а н к е с е й ч а с $" + b a n k ; свойство Text.

Код взаимодействия кнопок с объектами


У бедитесь, ч то кнопка назы вается b u t t o n l , а кнопка справа - b u t t o n 2 . Д важ ды щ елкни­
т е н а к а ж д о й и з к н о п о к , ч т о б ы д о б а в и т ь м е т о д ы b u t t o n l _ C l i c k {) и b u t t o n 2 _ C l i c k ( )
соответственно, и д л я к а ж д о й к н о п к и в в е д и т е код:

p r iv a te v o id b u tto n l_ C lic k (o b je c t sen d er, E ventA rgs e) {

if ( b a n k >= 1 0 ) { ^ ^ 'Мслцщ на кнопке Give ^ г о to Joe,


b a n k -= j o e . R e c e i v e C a s h {10) ; / Ф°рма вызывает метод ReceiveCaskh

} e ls e {
M e s s a g e B o x . S h o w ( "B б а н к е н е т д е н е г . " ) ;

^ Чтобы дать деньги Дж о, в банке должно


} X. быть не меньиле Если их нет,
появляется окно с сообщением.
p riv a te v o i d b u t t o n 2_ C l i c k ( o b j e c t sen d er, E ven tA rgs e) {
b a n k += b o b . G i v e C a s h ( 5 ) ;

U p dateF orm O ; ^ ^ о п к а Я есел ч е f r o m ЪоЬ д о б а в л я е т

I 6 Ът ьш .
Ц 5o6a метод GiveCashQ
возвращает О.

О Начальны й кап и та л Д ж о $ 5 0 , а Боба — $ 1 0 0


А теперь самостоятельно укажите начальное состояние полей Cash и Name для
обоих экземпляров. Код расположите под строкой I n i t i a l i z e C o m p o n e n t ( ) , ведь
процедура должна выполняться при инициализации формы. Завершив работу, убе­
дитесь, что щелчок на левой кнопке забирает $10 из банка и отдает эту сумму Джо,
а вторая кнопка забирает у Боба $5 и возвращает их в банк.

p u b lic Form l О { А с>^ явьт е к о д , со зд а ю щ и й


In itia liz e C o m p o n e n tO ; _________ — ^в а э к з е м п л я р а и з а д а ю -

„ з а д а й т е н а ,а л ь н ь .е з н а ^ е н и ^ и Т Г е Г л /^ Г
}

ажнеше дальше ► 145


объекты, по порядку стройся!

Более простые способы присвоения начальных значений


Практически всегда создание объектов сопровождается присвое­
нием им начальных значений. Объект G u y не исключение —он бес­ Инициализаторы
полезен, если не заданы значения полей N a m e и C a s h . Для решения
этой задачи в C# существует инициализатор объектов. В его основе о^ектов экономят
лежит технология IntelliSense.
ваше время и уве­
Рассмотрим исходный код, присваивающий полям личивают ком­
объекта Joe начальные значения.
joe = new Guy О ; пактность и чита­
joe.Name = "Joe";
joe.Cash = 50; бельность кода.
О Удалите две последние строки и точку с запятой после Guy {) и добавьте справа
фигурную скобку,
joe = new Guy о {
О Нажмите пробел. Появится окно со списком полей, для которых могут быть за­
даны начальные параметры.
joe = new Guy о {
ф Cash int Guy.Cash
0 Name
Нажмите Tab, чтобы добавить поле C ash. Затем присвойте ему значение 50.
joe = new G u y O { Cash = 50

О Введите запятую, и сразу после нажатия пробела появится еще одно поле,
joe = new G u y O { Cash = 50,

ф N am e string Guy.Name

Завершите работу инициализатора. Итак, вы уменьшили свой код на две строчки!


joe = new G u y O { Cash = 50, Name = "Joe" };

Новое объявление выполняет m y ж е ф у н к ­


цию, что и приведенные вначале три
с Т р о ч т к о д і Но оно короче и пройде для
чтения.

дальше > 147


несколько советов

- з д а н и е u H in J J u n iu B H o ц о н я щ н ь іх кЛ ассоБ
G

★ Программа должна решать какую-то задачу.


О бдумайте проблему. Ответьте на вопросы: легко ли ее поделить на несколько ча­
стей, и как бы вы объяснили ее другому человеку?
—у --------- ^
Мне нужно сравнить
несколько маршрутов и
выбрать самый короткий....

★ С какими реальными объектами работает программа?


Программе кормления животных в зоопарке наверняка потребуются клас­
сы для различных видов корма и различных видов животных.

ROAD CLOSED
CHEMIN FERMÉ І
★ Присваивайте классам и методам значимые имена.
Другие пользователи должны понимать назначение классов и методов по виду их
имен.

★ Ищите сходство между классами.


Классы, выполняю щ ие одинаковые функции, имеет смысл объединить. В системе,
производящ ей батончики, может быть несколько турбин, но для закрытия клапана
достаточно одного метода, в котором номер турбины будет использован в качестве
параметра.
ПерекрытаяДорога к
Название О бъ езд
Тупик Название
Длина НазваниеУлицы Длина
ПричинаЗакрытия ПричиныЗакрытия
ПоискОбъездаО
РасчетЗадержкиО ПоискОбъездаО
РасчетЗадержкиО

148 глава 3
объекты, по порядку стройся!

Добавьте кнопки к программе «Веселимся с Джо и Бобом», чтобы заставить парней


к р а ш е н и е передавать друг другу деньги.

П р исво йте экземпляру Bob начальные значения при пом ощ и


ини циал изатора
Вы уже проделывали эту операцию с экземпляром Joe. Потренируйтесь
в работе с инициализатором объектов еще раз.
Если вы поспешили и уже щелкну­
ли Нй кнопке, удалите ее, добавь­
те заново и присвойте новое имя.
Зат ем удалите старый методу
button3_Click() и добавьте новый. \
О Ещ е две кнопки для срормы
Пусть при щелчке на первой кнопке Джо отдает Бобу 10 долларов, а
\
!
при щелчке на второй Боб дает Джо 5 долларов. П еред двойным щелч-
ком на кнопке поменяйте ее имя в окне Properties, используя свойство
«(Name)». Первой кнопке присвойте имя joeGivesToBob, а второй —имя
bobGivesToJoe.

Щ Fun with Joe and Bob

Joe has $50


Bob has $100
Эта кнопка заставляет ТТтє b a * has $100
Ажо отдать й-О долла
ров 5обу, поэтому вос­
пользуйтесь свойством Give 510 to Receive S5
«(N am e)» в окне Properties, Эта кнопка заст авля­
Joe from Bob ет Зоба отдать Джо
чтобы присвоить ей иМЯ
JoeGivesToBob. \ долларов. Присвойте ей
Joe gives S10 Bob gives 55 имя oobC^ivesToJoe.
to Bob to Joe
J

О Заставим кнопки работать


Дважды щелкните на кнопке joeG ivesT oB ob в конструкторе. Форме будет
добавлен метод jo e G iv e sT o B o b _ C lic k (), который запускается при лю­
бом щелчке на кнопке. Пусть этот метод заставляет Джо отдавать 10 дол­
ларов Бобу. Якяждьт щелкните на второй кнопке и заставьте новый метод
Ь o b G iv e sT o Jo e _ C lic k () передать 5 долларов от Боба к Джо. Убедитесь,
что после передачи денег форма обновляется.

дальше > 149


решение упражнения

Вот как стала выглядеть программа после того, как к ней добавили кнопки передачи денег
аж нш е от Боба к Джо и обратно.

у г ш ш г

p u b lic p a r tia l c la s s Form l : F orm {


G uy j o e ;
Это инициализаторы
G uy b o b ; одьектов для двух
i n t bank = 100; экземпляров класса Quy.

p u b lic F o rm l0 {
In itia liz e C o m p o n e n tO ;
b o b « n ew G u yO { C a sh = 1 0 0 , Name * "Bob" } ;
j o e a n ew Q u yO { C a sh = 5 0 , Name = " J o e" } ;
U p dateF orm O ;
}

p u b lic v o i d U pdateForm O { Чтобы заст а­


j o e sC a sh L a b el.T ext = j o e . Name + " и м е е т $" + j o e . C a s h , вить Джо дать
b o b sC a sh L a b el.T ex t = b o b . N a m e + " и м е е т $" + b o b . C a s h , деньги 5обу, мы
b a n k C a sh L a b el. T ex t = "B б а н к е $' + b a n k ; вызываем м е ­
тод GiveCashQ
}
и передаем воз­
вращаемое им
p r iv a te v o id b u tto n l_ C lic k (o b je c t sen d er, E v e n tA r g s e) {
значение методу
i f ( b a n k >= 1 0 ) { ReceiveCashQ.
bank -= j o e .R e c e i v e C a s h (1 0 );
U p dateF orm ( );
} e lse {
M e s sa g e B o x .S h o w (" B банке нет д е н е г ." ) ;
} Результаты
} работы метода
GiveCashQ ис­
p r iv a te v o id b u tto n 2 _ C lick (o b je ct sender, E v e n tA r g s e) {
пользуются
в качестве пара ~
b a n k += b o b . G i v e C a s h ( 5 ) ;
мет ра метода
U p dateF orm O ;
R.eceiveCashQ.
Важно }
помнить,
кто дает . p r iv a t e v o id jo e G iv e s T o B o b C llc k ( o b je c t se n d e r , E v e n tA r g s e ) {
деньги, \ J b o b . R e c e iv e C a s h ( j o e . O iv e C a s h ( 1 0 ) ) ;
а кто их \ U p d a te F o r m ( ) ; ________ _
получает, j }

p r i v a t e v o i d b o b G i v e s T o J o e _ C X ic k ( o b j e c t s e n d e r , E v e n tA r g s e ) {

V j o e .R e c e iv e C a s h ( b o b .G iv e C a s h ( 5 ) ) г
U p d a te F o r m O ;
}
объекты, по порядку стройся!

еШ ение ® бас:с:е^1Не

Требовалось расположить представлен­


ные в бассейне фрагменты кода на
пустых строчках таким образом, что­
бы в итоге получилась работающая
программа. Вот как это нужно было
сделать:

p u b lic p a r tia l c la ss Form l : Form


{
p r iv a te v o id b u tto n l_ C lic k (o b je c t send er, E v e n tA r g s e)
{ Вот правильный ответ.
S tr in g r esu lt =
А это ответ на
Echo e l = new E c h o O ; дополнительный вопрос!
Echo eZ = new Ecko( ); Еско eZ = е1;
int X = 0;
while ( ^ ^ ^ ) {
r esu lt = r esu lt + e l.H e llo 0 + " \n " ;

ei-.count = ei-.count + Д-;__


if { X == 3 ) {

e 2 . c o u n t = e 2 . c o u n t + 1;

}
if ) {
е 2 .count = е 2 .count + e l.c o u n t;

x = X + 1;

}
MessageBox.Show(result + "Count; " + e2.count)

c la ss Echo {
p u b lic in t count = 0;
p u b lic str in g HelloQ {
retu rn " h e llo o o o ..." ;
}

дальше * 151
4 щ ипь! и ссылки

10:00 утра. ^
Куда подевались наши данные?

Без д а н н ы х п р о г р а л ш ы б е с п о л е з н ы .
Взяв информацию от пользователей, вы производите новую и н ф о р м а­
цию, чтобы вернуть ее им же. Практически все в программировании свя­
зано с о б р а б о т кой д а н н ы х тем или иным способом. В этой главе вы
познакомитесь с используемыми в С # т ипам и данны х, узнаете методы
работы с ними и даже ужасный секрет объ ект ов (только т-с-с-с... объ­
екты — это тоже данные).
не мои тип

Tun переменной определяет, какие данные она MO)kenfi сохранять


Количество встроенных типов C# велико, и каждый из них хранит свой собственный вид данных.
С некоторыми из них вы уже познакомились и даже поработали. Пришла пора узнать о неизвестных
доселе типах, которые крайне пригодятся вам в будущем.

Н аиболее используем ы е типы


Вряд ли вас удивит тот факт, что типы i n t , s t r i n g , b o o l и d o u b le являются
самыми распространенными.
★ i n t хранит целые числа от -2 147483 648 до 2147483647;
★ s t r i n g хранит текст произвольной длины (в том числе и пустую строку
★ b o o l хранит логические значения —t r u e или f a l s e ;
★ форма пред­
d o u b le хранит вещественные числа от ±5.0 •10’^'*до ±1.7 -10“ ®до 16 зна­ ставления, при
чащих цифр. Подобный диапазон выглядит странным и сложным, но на которой число
самом деле все очень просто. Словосочетание «значащие цифры» ука­ хранится в виде
зывает на точность числа: и 35048 410 000000, и 1743059, и 14.43857, мантиссы и по­
и 0.00004374155 имеют по семь значащих цифр. Запись 10-’“ означает, казателя степени,
называется числом
что вы можете хранить любое число не больше 10®”*, при условии что с плавающей т оч­
количество значащих цифр не превышает 16. С другой стороны диапа­ кой (запятой).
зона — позволяет хранить числа не меньше 10"*^^...но как неслож­
но догадаться, опять же при условии, что количество значащих цифр не
превышает 16. Часто вы м еняете
(/ т и п переменной
Ц елочисленны е типы
Когда оперативная память компьютера стоила дорого, а процессоры работали Ьыть решена и при
медленно, использование неверного типа данных могло серьезно замедлить помощи «циклыче
ского присваивания»
работу программы. К счастью, времена изменились и теперь для хранения це­ о котором МЫ по
лых чисел в большинстве случаев достаточно типа i n t . Но иногда требуются говорим через пару
дополнительные возможности, поэтому в C# присутствуют такие типы как; страниц.
★ b y te хранит целые числа от Одо 255;
★ s b y te хранит целые числа от -128 до 127; ^
Бук­ ★ s h o r t хранит целые числа от -32 768 до 32 767; Вуква «5» означает
ва « и » «со знаком». То есть
означа­ ★ u s h o r t хранит целые числа от Одо 65,535; число может быть
ет «без отрицательным.
знака». ★ u i n t хранит целые числа от Одо 4 294 967 295;
★ long хранит целые числа в диапазоне от минус до плюс 9 триллионов;
★ u lo n g хранит целые числа от Одо примерно 18 триллионов.

154 глава 4
типы и ссылки

Типы д л я хранени я очень больших и очень маленьких Ч И С б Л


Иногда семи значащих цифр оказывается недостаточно. Бывает так, что 10*® — недостаточно
большое число, а 10"“*^—недостаточно малое. С такими проблемами сталкиваются программы фи­
нансового учета и научных исследований, и для них в С# предназначены дополнительные типы:
★ f l o a t хранит любое число в диапазоне от ±1.5- 10'^'’до±3.4 ■10*®с 7 значащими цифрами;
~^^с'таЫасто* d e c im a l хранит любое число в диапазоне от ±1.0 • 10'^® до ±7.9 • 10^® с 28-29 значащими
встречается цифрами. Свойство Value
6 программах у п « К о н с т й н т й » — это ^ ^ с л о к о - элемента управле­
финансового торое 6t>! вводите в код. В Ьы ния numericUpPown
учета. ЛX---- ' раженим « in t і - 5;» s — это ^принадлежит
константа.
t/ПМЛІЛЛйН данных
Константы тож е им ею т тип decimal.
Числа, используемые в программе на С#, называются константами... и все они
принадлежат какому-то типу. Попробуйте написать код, присваивающий значе­
ние 1 4 .7 переменной типа i n t :
Description
in t m y in t = 1 4 .7 ;
^ 1 Carmot imptkrtty convert type 'double' to ‘inf. An
explicit conversion Bcists (are you missing # cast?)
П ри компиляции вы увидите:
ИСР сообщает, что константа 1 4 . 7 принадлежит типу d o i i b l e . Вставив в конец
букву F ( 1 4 . 7 F ) , вы поменяете тип константы на f l o a t . А в форме 1 4 . 7М оно
будет принадлежать уже типу d e c im a l.
Попытавшись п р и ­
«М » означает «m oney» своить константу
(деньги). Я не шучу! типа float пере­
Ещ е несколько встр оенны х ти по в менной типа double
Для хранения единичных символов, например, Q, 7 или $ используется или decimal, вы
увидите окно с под­
тип c h a r. Константы этого типа всегда заключаются в одиночные кавычки сказкой.
( ' X', ' 3 '). В кавычки можно заключить и евс-последовательность ( ' \ п ' —
это перенос строки, ' \ t ' —знак табуляции). Хотя в коде эти последователь­
ности фигурируют в виде пары символов, программа хранит их в памяти
в виде одного. К
И наконец тип таких данных как o b j e c t . Вы уже получали объекты, созда­
вая экземпляры классов. Любой из них мог быть переменной типа оЬj e c t. "О связи между
О том, как работают объекты и переменные, которые на них ссылаются, мы типами char
и byte вы узнаете
еще поговорим в этой главе. о главе ■?.

W TVPM в Windows 7 у Калькулятора сущ ест вует и режим «П ро­


грам м ист », позволяющий использовать бинарные и деся­
тичные числа одновременно!
Встроенный калькулятор Windows можно использовать для перевода десятичных чисел в двоичные:
перейдите в Инженерный режим, введите число, и установите переключатель в положение Bin. Для
обратного преобразования достаточно вернуть переключатель в положение Dec. Проделайте эту опе­
рацию с пограни чны м и зн ачен иям и цел ы х ти п о в (скажем, с -32 768 и 255). Вы можете объяснить,
почему в C# используются именно такие значения?

дальше у 155
взять деньги на мороженое

Наглядное предстабление переменных Не все переменные попадают


в кучу. Значимые п^ипы хранят
Данные занимают место в памяти. (Помните кучу из преды­ данные в другой части памяти,
дущей главы?) Значит, следует учитывать, сколько простран­ которая называется стеком,
долее подробно мы поговорим
ства требуется под строку или число в программе. Именно о5 эт ом главе 14.
поэтому мы пользуемся переменными. Они позволяют вы­
делить достаточно места для хранения данных.
Представим переменную в виде стакана. В С# используют­
ся разные стаканы для хранения данных разных типов. Чем
больше переменная, тем больший стакан ей требуется.

Tun int
хранения це з ^47.
не больиле 2. 3-^
Tun short хранит целые
числа до 32.767.

чисел Tun byte хро-нит цифры


от О до 7-55.
long int__ short byte
64 32 16 8

Количество битов, выделяемое под


переменную в м омент ее обьявления.

Числа с десятичной точкой хранятся по другому. Для большин­


ства таких чисел подойдет тип f l o a t , занимающий меньше
всего места в памяти. Если вам требуется большая точность,
используйте тип d o u b le . А для финансовых приложений, в ко­
торых хранится информация о курсах валют, используется тип
d e c im a l.

Впрочем, речь не только о цифрах. (Вы же не наливаете горя­ указать.


чий кофе в пластиковый стаканчик, а холодный в бумажный.)
Компилятор C# умеет обрабатывать и не-численные типы дан­
ных. Тип c h a r хранит один символ, а тип s t r i n g позволяет
хранить целый набор символов. При этом под объект s t r i n g
не выделяется предустановленное место в памяти. Он расши­
ряется в зависимости от количества помещаемой в него инфор­
мации. А тип данных b o o l хранит значения true и false, с кото­
string
рыми вы уже сталкивались при работе с оператором i f .
зависит от
размера
строки

156 глава 4
типы и ссылки

10 литров 6 S -литробой банке


Объявив тип переменной, вы фактически объясняете ком­
пилятору, как ее следует воспринимать. Компилятор ви­
дит стаканы, а не то, что в них налито. Поэтому такой код
работать не будет:
in t leagu esU n d erT h eS ea = 2 0 0 0 0;

short sm a llerL ea g u es = lea g u esU n d er T h e S e a ;

Хотя число 20 ООО попадает в диапазон, заданный для типа


данных s h o r t , переменная l e a g u e s U n d e r T h e S e a была
объявлена как i n t , и компилятор не может положить ее
в контейнер s h o r t . Следовательно, вам всегда нужно сле­
дить за совпадением типов данных.

смысл, так как


^збежать с и т и ^
^%'^опы т ает есТ!^‘'^ /сог^й
^/^ержимое
К о м п и л я т о р « в и д и т » , ч т о вы п ы т а е т е .

$ к о гЬ . С о д е р ж и м о е с т а к а н а m t п р и э т о м н е
и м е е т значен и я.

возьми в руку карандаш_ _ _ _ _ _ _ _ _ _


^Возьм1/
Обведите три оператора, которые не будут компилироваться из-за
V несовпадения типов данных или из-за того, что им пытаются при­
своить слишком большое или слишком маленькое значение.

in t hours = 24; str in g ta u n t = "your m oth er" ;

b y te days = 365;

bool isD o n e = yes; lo n g r a d iu s = 3;

sh ort RPM = 33; char in itia l = 'S '

in t b a la n ce = 345667 - 567; str in g m on th s = "12";

дальше ► 157
приведения

Приведение типов

Посмотрим, что произойдет при попытке назначить


значение типа d e c im a l переменной типа i n t . Vу п |^ а ж н ^е н и е
Создайте новый проект и добавьте кнопку. Для метода
C l i c k О этой кнопки напишите:
d e c im a l m yD ecim alV alu e = 10;
i n t m y ln tV a lu e = m y D ecim a lV a lu e;

M e s sa g e B o x .S h o w (" T h e m y ln t V a lu e is " + m y ln tV a lu e);

© Попытавшись запустить программу, вы получите сообщение об ошибке:

' Q 1 Effor |; ^ ОWaFnings v [h ^ О M essages


Description

О 1 C annot tmpKcttly convert type 'd e d m a f to ’in f. An ecpttcrt conversion ecists (are you nfiissing a cast?) ИСР предполагает,
что вы забыли осуще­
ст вит ь приведение
о Устраните ошибку, преобразовав тип d e c im a l в i n t . Внесите во вто­
рую строку следующие изменения:
типов (casting).

i n t m y ln tV a lu e = (in t) m y D ecim a lV a lu e;
Этот оператор делает
приведение типа decimal
Ч то )ке произошло? к т ипу int. Вернитесь в начало
'предыдущей главы
Компилятор не позволяет присваивать значения несовпадающих ти­ « посмот рите, как
пов, даже если переменная попадает в правильный диапазон. Приведе­ ^ приведение типов
ние типа —это объяснение компилятору, что вы знаете о несовпаденйи использовалось для
і^ередачи значения
типов, но в данном конкретном случае осознанно осуществляете опера­
^J^'^encUpDown ф ор­
цию присваивания. м е Talker Tester

Вот операторы, которые не будут компилироваться из-за несо­


аж нш е впадения типов данных или из-за того, что им пытаются присво­
ить слишком большое или слишком маленькое значение.
Слишком большое
sh ort у = 78000;
число. Тип short
хранит значения от
-ЗЯ,767 до 32..,762.
С b y te days = 365;

bool isD o n e = yes


Переменной типа bool переменные т ш а К
можно присвоить только хранят числа do % s f
значения true или false.

158 глава 4
типы и ссылки

Автоматическая коррекция слишком


больших значений
Вы уже видели, что тип d e c im a l может быть приведен
«о Г э ч З Г *
к типу i n t . Получается, любое число может быть приве­
дено к любому типу. Но это не означает сохранение зна­
чения. Приведем переменную типа i n t со значением 365
к типу b y te . Число 365 выходит за границы диапазона
этого типа. Но вместо сообщения об ошибке произойдет
циклическое присваивание: например, 256 после при­
ведения к типу b y te превратится в О, 257 —в 1, а 365 — возьми в руку карандаш
в 109. Как только вы дойдете до 255, произойдет переход
к нулевому значению. О п ер ац и я при в ед ения раб о тает
не со всем и ти п ам и . Создайте новый проект,
добавьте кнопку, дважды щелкните на ней
и введите следующие строки. Попытайтесь
построить программу. Зачеркните строки с
О О Еще в главе 2 я начал ошибками. Это поможет понять, для каких
комбинировать строки с типов приведение допустимо, а для каких нет.
цифрами в окнах диалога!
in t m y in t = 10;
Получается, я все это время
преобразовывал типы данных? b y t e m yB yte = (b y te )m y in t;

d o u b l e m y D ou b le = (d o u b le)m y B y te;

b o o l m yBool = (b o o l)m y D o u b le;


Да! Это д елал за ва с оператор + . s t r i n g m y S tr in g = " fa ls e " ;

ч Вы пользовались оператором +, который


автоматически преобразовывал дан­
ные. Когда вы добавляли число и логиче­
ское значение к строке, указанное вами
myBool =

m y S trin g =
(b o o l)m y S trin g ;

(str in g )m y in t;

значение автоматически преобразовы­ m y S trin g = m y in t. T o S t r in g ();


валось к типу string. Операторы + (или *, myBool = (b o o l)m y B y te;
/ или -), примененные к значениям раз­
личных типов, автоматически преобра­ m yB yte = (b y te)m y B o o l;
зовывают меньший тип в больший. Вот
s h o r t m yShort = (sh o rt)m y in t;
пример:
in t m yInt = 36, c h a r m yChar = 'x ';
После числа, кот о­■ —' d o u b l e m y F l o a t = 1 6 . 4D;
m y S trin g = (strin g )m y C h a r;
рое присваивается
m y F lo a t = m y in t + m y F lo a t ;
переменной типа
l o n g m yLong = (lo n g )m y in t;
double, нужно до­
бавлять р, чтобы Диапазон значений типа i n t больше диа­
d e c im a l m yD ecim al = (d e c im a l)m y L o n g ;
указат ь ко м ­ пазона значений типа f l o a t , оператор +
пилят ору на его преобразует переменную m y in t к типу m y S tr in g = m y S tr in g + m yin t + m yB yte
!лринадлежность f l o a t и только потом прибавляет ее к пе­ + m y D o u b le + m yChar;
к т ипу float.
ременной m yF loat.

дальше ► 159
настоящее преобразование

иногда приведение типов происходит автоматически


Есть два случая, когда вам не требуется делать приведе­
ние типов. Автоматически эта процедура выполняется,
во-первых, при проведении арифметических опера­
ций:

П арам ет р т ипа
lo n g 1 = 139401930;
sh o rt вы чит ает ся
из п а р а м е т р а т и п а
sh ort s = 516;
lo n g , а о п е р а т о р =
п р ео б р а зует
d o u b le d = 1 ;
р езул ьт ат к т ипу нение--------- ----------
d = d / 1 2 3 .4 5 6 ;
d o u b le .
)'^ешенке
M e s s a g e B o x . S h o w ( "О твет " d) ; И так, вы у б е д и л и с ь , что п р и в е д е н и е в о з­
м о ж н о д а л е к о не д л я всех ти п о в . Зачер­
кнутые строки не позволяют осуществить
В л а го д а р я о п е р а т о р у ^ построение программы.
п р о и сх о д и т п реоб разован и е
т и п а d e c im a l к т и п у s tr in g . in t m y in t = 10;

b y t e m yB yte = (b y te )m y in t;
Во-вторых, автоматическое преобразование происхо­
d o u b l e m yD ou ble = (d o u b le)m y B y te;
дит при объединении строк оператором Все числа
при этом преобразуются к типу string. Рассмотрим при­ bqrri—и у В в е 1 ■—— —
мер, в котором первые две строки кода написаны пра­
вильно, третья не может быть скомпилирована. str in g m yS trin g = " f a l s e " ;

lo n g X = 139401930; -royBool—=— (Jaooif) my Ot r i n g - ,-^

M e s s a g e B o x .S h o w (" О т в ет " + х ) ;
mygtrrreg--~~4-&fe*4:ag-)-my»m‘fe-;—^
m yS trin g = m y in t.T o S tr in g ();
M e s s a g e B o x .S h o w ( х ) ;
- « ¥ E ucl1—^=— (-b e o l ) m y B y t «-,-
Компилятор выдает сообщение об ошибке в связи с не­
правильным аргументом (аргументом в C# называется —(fa^'tTc:') myOoa lf ----
значение, передаваемое методу в качестве параметра). s h o r t m yShort = (sh o rt)m y in t;
Параметр метода M e s s a g e B o x . S h o w O должен при­
надлежать типу s t r i n g , код же передает переменную c h a r myChar = ' x ' ;
типа l o n g . Впрочем, вы легко можете осуществить jny S t r ir a g —^— (-at r i n g ) m y e h a g ;
это преобразование при помощи метода T o S t r i n g ( ) .
Этим методом обладают все объекты. (И все создан­ l o n g m yLong = (lo n g )m y in t;
ные вами классы имеют метод T o S t r i n g ( ) , возвраща­
d e c im a l m yD ecim al = (d e c im a l)m y L o n g ;
ющий имя класса.) Именно с его помощью можно пре­
образовать X в параметр метода M e s s a g e B o x . S h o w (): m y S t r i n g = m y S t r i n g + m y in t + m yB yte
+ m y D o u b l e + m y C h a r;
M essageB ox. S h ow (x. T o S tr in g ( ) ) ;

160 глава 4
типы и ссылки

Аргументы метода дол)кньі б ы ть совместимы с типами параметров


Попытайтесь вызвать метод Me s sa g e B o x . Show ( 12 З ), то есть передать ^ - Параметр ~ это то,
чмо вы определяете
методу M essageB ox.Show () константу (123) вместо строки. ИСР не внутри метода, а а р ­
позволит построить программу. Появится сообщение об ошибке: «Аргу­ гумент — что в него
мент ‘1’: преобразование из типа i n t в тип s t r i n g невозможно». Ино­ предает ся. Метод
гда преобразование происходит автоматически, например, если ожида­ с парамет ром типа int
лось значение типа i n t , а вы передали методу значение типа short, но может принять а ргу­
м ент типа byte.
в случае с переменными типа i n t и s t r i n g это невозможно.
Не только метод M essageB ox.Show O , все методы, даже написанные
вами, будут показывать ошибку компиляции, если передать им пара­ Ошибка <<неверный
метр неправильного типа. Попробуйте использовать на практике этот
совершенно правильный метод: ар1умент>> означает,
p u b l i c i n t MyMethod (b o o l yesNo) {
что вы попытались
if (yesNo) {
вызвать метод с пе­
r e t u r n 45;
^йї?еменная yesNo
ременными, тин ко­
} e ls e {
r e t u r n 61; торых не совпадает
} с его параметрами.
}

Все работает, пока вы передаете методу то, что он ожидает получить (логиче­
скую переменную), вызов MyMethod ( tr u e ) или MyMethod ( f a l s e ) позволяет
легко скомпилировать код. ^ ^
Переменной, парам е-
Но что получится, если передать методу число или строку? Вы получите <тру или полю Можно
уже знакомое сообщение об ошибке. Попытайтесь передать параметр типа Р\р01АЗ —
вольное значение при
B o o lean , но в качестве возвращаемого значения укажите строку или передай­
помощи типа object.
те результат методу M essageB ox. Show (). Код перестанет работать, ведь ме­
тод возвращает значение типа i n t , а не lo n g или s t r i n g , которое требуется
методу M essageB ox. Show().

ш «ижно л и м т ь в явном биде if (yesNo == tru e), т ак кяк


Не нужно писат о о uomuHHOcmf условия. Для
оператор всегда про Р итользчйте оператор !

условия.

дальше > 161


таблица зарезервирована Вы можете использовать для имен переменных даже
, зарезервированные слова. Достаточно поставить
у в начале знак
В С # существует около 77 зарезервированны х слов. Их нельзя использовать в
качестве имен переменных. К концу данной книги вы должны познакомиться с ними
нение всеми, а пока посмотрите на список слов, с которыми вы уже сталкивались. Напи­
шите, зачем они нужны.

na m e s p a c e

£ог

class

p u bl ic

else

while

using

if

пеуг

О ш Б е г п ы н а с. 9
162 глава 4
типы и ссылки

Создадим калькулятор расходов для деловой поездки. Вы будете вводить данные с одометра
п^ражнение в начале и конце путешествия, а счетчик вычислять, какое расстояние было пройдено и какую
сумму денег вам вернут в бухгалтерии, при условии, что за каждую пройденную милю полага­
ется $.39.

О Новы й проект W indow s.


Вам понадобится вот такая форма:
Избавьтесь am
кнопок управления
Mileage Cakulator размером окна.
Д ая этой
ааШпд Mileage
мет ки ис~ В
пользуйте Endng Wteage
‘полужирный
ш риф т и
кегль 1 2 p t Anourt Owed la b e H
Calculate

Завершив создание формы, дважды щелкните на кнопке и добавьте код.

О П ер ем енны е, которые потребую тся для калькулятора.


Поместите переменные в строчки, объявляющие класс, в верхней части кода
F o rm l. Вам потребуются две целочисленные переменные для начальных и
конечных показаний одометра. Присвойте им имена sta r tin g M ile a g e и
e n d i n g M i l e a g e . Затем вам потребуются три нецелых числа. Выберите для
них тип d o u b l e и имена m i l e s T r a v e l e d (Пройдено миль), r e i m b u r s e R a t e
(К оэфф ициент возмещения) и a m o u n t O w e d (Должны денег). Переменной
r e i m b u r s e R a t e присвойте значение .39.

Чтобы заставить калькулятор работать.


Добавьте код для метода b u t t o n l _ C l i c k ( ) :
★ Убедитесь, что значение поля StartingMileage меньше значения поля
EndingMileage. В противном случае должно выводится окно с текстом «На­
чальный пробег не может превышать конечный». Присвойте этому окну имя
«Cannot Calculate» (Невозможно рассчитать).
★ Вычтите начальный пробег из конечного и умножьте полученный результат
на тариф;
m ile sT r a v e le d = e n d in g M ile a g e -= sta r tin g M ile a g e ;
am oun tO w ed = m ile sT r a v e le d *= r e im b u r se R a te ;
la b e l4 .T e x t = "$" + am oun tO w ed ;

З ап уск прогроАлмы.
Убедитесь в вводе правильных значений. Укажите начальный пробег больше конеч­
ного и убедитесь в появлении окна с сообщением.

дальше > 163


что-то пошло не так...

Во т как выглядит код для первой части упражнения по расчету


нгнш компенсации.
ешение

p u b lic p a r tia l c la s s Form l : Form

{ ■^ак как в данном


in t S ta r tin g M ile a g e ; случае число может
превысить 999 999
in t e n d in g M ile a g e ; типы short или byte не
подходят.
d o u b le m ile sT r a v e le d ;

d o u b le r e im b u r se R a te = .3 9 ;

d o u b le am oun tO w ed ;

p u b lic F o rm l0 {

In itia liz e C o m p o n e n t();

} П ом ни те, « ,ц ц я
, э л е м е н т а управления
p r iv a te v o id b u tto n l_ C lic k (o b je c t sender, E ven tA rgs e) { ,,iflA ericU p'P ovJn о а м
н у ж н о п о т н я т ^ > у^ ^ ^
S ta r tin g M ile a g e = (in t) n u m e r ic U p D o w n l.V a lu e ;
d L l m a l на m t.
e n d in g M ile a g e = (in t)n u m e r ic U p D o w n 2 .V a lu e ; ^

if (sta r tin g M ile a g e <= e n d in g M ile a g e ){


Здесь рассчиты­
m ile sT r a v e le d = e n d in g M ile a g e -= sta r tin g M ile a g e ; ) ваете количество
am ou n tO w ed = m ile sT r a v e le d *= reim b u rseR a te ;
пройденных миль,
которое зат ем
la b e l4 .T e x t = "$" + am oun tO w ed ; j* умножается на
тариф возмеще­
} e ls e { ния расходов.
M essageB ox. Show (
"Начальный пробег не может превьшать конечный",
"Cannot C a lc u la te M ile a g e " ); Здесь использует ­
ся альт ернат ив-
} ^ ный способ вызова
\ MemodaMessageBox.
^ SkowQ. Первый пара­
м ет р — это от о­
бражаемое сообщение,
а второй — заголовок.
Кнопка, каж ется, работает, но есть одна
проблем а. Вы м ожете указать ее?

164 глава 4
типы и ссылки

Ещ е одна кнопка
Попытаемся понять, почему не работает форма, добавив еще одну кноп­
ку, которая будет показывать значение поля m ile s T r a v e le d . (Это можно
сделать при помощи отладчика!)

Если сначала щелкнуть


Satffig Weage 1Ш Э на кнопке Calculate, а потол/\
на этой кнопке, должно п о ­
Bid^g Meage 12354 явиться окно диалога с иказа-
нием пробега.
AmoirrtOwed $532.35 •ВЧТЯГЯЯТТГЯЯТТГЯЧИТЯТКЯРм^!
Calcdate

Закончив конструирование формы, дважды щелкните на кнопке Display


Miles, чтобы добавить код.

О П онадобится всего одна строка


Нам нужно окно, в котором будет отображаться значение переменной
m ile s T r a v e le d , не так ли? Для этого достаточно одной строки:
p r iv a te v o id b u tto n 2 _ C lic k (o b je c t send er, E ven tA rgs e) {

M e s s a g e B o x .S h o w (m ile s T r a v e le d + " m ile s" , " M ile s T r a v eled " );

З ап уск програ/имы
Введите какие-нибудь значения и посмотрите, что
получится. После ввода начального и конечного про­
бега щелкните на кнопке Calculate. После этого щел­
чок на кнопке Display Miles покажет значение поля
m ile sT r a v e le d .

Вот только странно...


П ри любых введенных значениях пробег совпадает с суммой возмещения. Почему?

дальше > 165


операторы наготове

Комбинация с оператором =
Обратите внимание на оператор, который использовался для вычитания на­
чального пробега из конечного (-=). Проблема в том, что он не только вычита­
ет, но и присваивает значение переменной. То же самое происходит в строке,
где мы умножаем пройденные мили на тариф возмещения. Поэтому нужно
заменить операторы -= и *= на - и *:

p r iv a te v o id b u tto n l_ C lic k (o b je c t sender, E ven tA rgs e)


»созыва-
{ соси^авные
S ta r tin g M ile a g e = (in t) n u m e r ic U p D o w n l.V a lu e ; •^Р<^сваиёа~
e n d in g M ile a g e = (i n t ) n u m ericU p D o w n 2 . V a l u e ; operators).
if (sta r tin g M ile a g e <= e n d in g M ile a g e ){

m ile sT r a v e le d = e n d in g M ile a g e ^ ^ ^ ^ ta r tin g M ile a g e ;

am oun tO w ed = m ile sT r a v e le d (^ ^ )r e im b u r se R a te ;

la b e l4 .Text = "$" + am oun tO w ed

} e ls e {

M e s s a g e B o x .S h o w ("Н ачал ьны й п р о б е г fce мож ет превы ш ать конечны й"


"Cannot C a lc u la te ^ le a g e " );

При такой записи код не


будет менять? зидчение
переменных endingMileage milesTraveled = endingMileage - startingMileage
и milesTraveled.
amotmtOwed = milesTraveled * reimburseRate;

П о м о гл и л и нам значимы е и м ен а переменны х? Конечно! Посмо­


трим на функцию каждой переменной. По названию m i l e s T r a v e l e d
(Пройденные мили)вы понимаете, что эту переменную форма отобра­
жает неправильно и догадываетесь, как можно исправить ситуацию.
Вам было бы намного сложнее локализовать проблему, если бы код вы­
глядел вот так:
тТ = еМ - = sM ; таки АЛ ,
аО = т Т *= r R ; > менных невоТлТ‘^ ^
S^ бы полн^ он и

166 глава 4
типы и ссылки

Объекты то)ке используют переменные


До этого момента мы рассматривали объекты отдельно. На са­
мом же деле, это еще один тип данных. Объекты обрабатывают­
ся аналогично цифрам, строкам и логическим значениям. И точ­
но также используют переменные:

Работа с типом int Работа с объектом

(l) Пишем оператор объявления типа. Пишем оператор объявления типа.

int myint; Dog spot; объяблети


переменной название
класса используется
в качестве типа.
(8) Присваиваем новой переменной зна­ О Присваиваем новой переменной значение:
чение.
spot = new Dog();
myint = 3761;

(З) Используем переменную в коде. О Проверяем одно из полей объекта.

while (i < myint) { while (spot.IsHappy) {


___/ Получается, что нет разницы \
^ с чем работать, с объектом или с числом. )
В любом случае я работаю с переменной. )
О

Д а, об ъ екты — это всего л и ш ь ещ е один


тип перем енны х.
Если программе нужны целые большие числа,
используйте тип lo n g . Для целых малых чи­
сел используйте тип s h o r t . Если вам нужны
значения Д а/Н ет, используйте тип b o o le a n .
А если вам нужно нечто лающее, используйте
тип Dog. Во всех случаях вы работаете с пере­
менными.

дальше ► 167
получи ссылку

Переменные ссылочного типа


Э т о н а э ь 1 ва е т ся
Для создания нового объекта вы пишете n e w G u y ( ). Но этого недоста­ созданием экземпляра
точно; в куче появится объект G u y, но у вас не будет к нему доступа. Вам объекта. \
требуется ссылка на объект. Здесь на помош;ь приходят п е р е м е н н ы е с с ы ­
л о ч н о г о т и п а —это переменные типа G u y , имеюш;ие имя, например, j o e .
В итоге получается, что j o e —это ссылка на только что созданный объект
G uy. Именно она используется для доступа к объекту.

Все переменные типа object являются ссылочными. Давайте рассмотрим


пример:

Это куча до
запуска кода. Там
ничего нет.

p u b lic p a r t i a l c la s s F o rm l Form
{
Guy joe;

p u b lic F o rm l0
Это переменная { Создание ссылки подобно
joe, ссылающаяся I n i t i a l i z e C o m p o n e n t () наклеивании) стикера, вы
на объект йму< помечаете объект, чтобы
joe = new Guy(); получит ь возможность
ссылаться на него в даль­
нейшем.
Это ссылочная ^ объект,
переменная... на который
она ссылается.

Вид кучи после запу таинственный


ска кода. Теперь т ам к объ­
находится объект ект у Quy - аерез
с переменной Joe. хо ^^^ременную Joe
торая на него ссыла­
ется.

168 глава 4
типы и ссылки

Ссылки подобны маркерам


Подозреваем, что на вашей кухне есть отдельные емкости для сахара
и соли. Если поменять местами наклейки на них, еда будет несъедобной,
ведь содержимое емкостей при этом местами не поменялось. Аналогично
действуют ссылки. Ярлыки можно поменять местами и заставить указывать Для работы с хра­
на разные вещи, но какие именно данные и методы будут доступны, опре­
деляет только сам объект, а не ссылка на него. нящимися в памяти
Метод b u tto n l Clirîf Ar,
мы F orm l содеюжи^ объектами ис­
Это объект типа
^иу. Он ОДИН, но на пользуются ссылки,
него МНОГО ссылок
которые являются
перемеппыми. Тип
этих переменных
определяется клас­
сом объекта, на ко­
торый они ссыла­
ются.

Переменная ^т о ссылочные
«папа» является ’л еременные,
ссылкой на экзем ­ указывающие на
пляр класса Quy. ОДИН И ТО Т ЖЕ
ооьект. Различные методы м о ­
гут использовать эк-
Обращение к объекту никогда не проиходит напрямую. К примеру, не­
ссь/лоч-
возможно записать G u y . G i v e C a s h {), если G u y принадлежит типу object. НЫМ п е р е м е н н ы м р а ­
Компилятор не понимает, куда именно вы обращаетесь, так как в куче мо­ зум н о п р и сво и т ь разн ы е
жет храниться несколько экемпляров G u y. Вам нужна ссылочная перемен­ в зависимости от
ная, например, j o e , которой будет присвоен конкретный экземпляр G u y контекста.
j ое - new G u y ().

Теперь можно вызывать методы j о е . G i v e C a s h ( ), ведь компилятор «зна­


ет», к какому экземпляру обратиться. Как показано на рисунке, возможен
набор ссылок на один экземпляр. Можно написать G u y d a d = j о е и вызвать
метод d a d . G i v e C a s h о (папа.ДайДенег()). Именно этим сын Джо зани­
мается каждый день!

дальше ► 169
вот водитель мусоровоза

При отсутстви и ссылок объект


Объект остается
превращается 6 мусор
Если все ссылки на объект исчезают, программа теряет к нему
в куче, пока на него
доступ. Теперь объект предназначен для сборки мусора
(garbage collection). C# избавляется ото всех объектов, на ко­
есть хотя бы одна
торые нет ссылок, освобождая место в памяти.
ссылка. Как только
^ Вот код создания объекта.
последняя ссылка
G uy j о е = n e w G u y () исчезает, объект
{ Name = " J o e " , C a s h = 50
удаляется.

Оператор new соз­


дает новый объект,
a ссылочная пере­
менная Joe указывает
на него.

О Бьхл создан второй объект.


G uybob = n e w G u y O
{ Name = "B ob", C a s h = 7 5 } ;

о П у сть ссы лка на первый экзем пляр начнет Ссылок Нй пер-


указывать на второй. вый экземпляр
не осталось...
j ое = bob;

■■■поэтому
Объект
удаляется!

170 глава 4
типы и ссылки

Побочные эффекты мно)кественных ссылок


Обращаться с ссылочными переменными нужно аккуратно. В боль­
шинстве случаев кажется, что вы просто перенаправляете ссылку на
другой объект. Но при этом можно нечаянно удалить все ссылки на
другой объект. И далеко не всегда это будет тот результат, который
вам нужен. Рассмотрим пример:

Dog rover = new D og();

r o v e r .B reed = "G reyhound";

О бъ ектов:

© Dog fid o = new DogO ;


fid o .B r e e d = " B e a g le" ;
Dog spot = rover;

О бъ ектов: ^ Fido — это еще один


объект Dog. При эт ом
С сы лок: 5 Spot — это всего лииль
дополнительная ссылка
на первый объект.

Dog lu c k y = new D og();

lu c k y .B r e e d = "D achshund";
Бах!
fid o = rover;

/ Л \
Lucky — это т рет ий
объект. A Fido теперь
ссылается на Object *%.
На object не
остается ссылок. Он
удаляется.
дальше ► 171
так много меток

їозьми в руку карандаш Теперь ваша очередь. Вот длинный код, разбитый на блоки.
Укажите, сколько объектов и сколько ссылок на них имеется на
каждой стадии. Справа нарисуйте объекты в куче и их метки.

Dog rover = new D og();


r o v e r .B reed = "G reyhound";
Dog r in T in T in = new D og();
Dog f id o = new D og();
Dog q u e n t in = fid o ;

О бъ ектов :.

Ссы лок:

© Dog sp ot
s p o t .B reed
= new
=
D o g {);
"D achshund";
sp ot = rover;

О бъ ектов :______

С сы лок:_____

О Dog lu c k y
lu c k y .B r e e d
= new
=
D o g {);
" B e a g le" ;
Dog C h a rlie = fid o ;
fid o = rover;

О бъ ектов :______

С сы лок:_____

^ r in T in T in = lu c k y ;
Dog la v e r n e = new DogO ;
la v e r n e .B reed = "pug";

О бъ ектов :______

Ссы лок:_____

О C h a r lie
lu ck y =
= la v e r n e ;
r in T in T in ;

О бъ ектов :______

С сы лок:_____

172 глава 4
типы и ссылки

Напишите программу для класса e le p h a n t (слон). Получите два экземпляра


e le p h a n t и поменяйте указывающие на них ссылки таким образом, чтобы
ни один из экземпляров не был уничтожен.
ажнение
Н ачните с проекта W indows Application.
Форма должна выглядеть так; А т г р а м м а для

S S e .t e ,- " -
с E le p h a n t
Name
т акое окно.
EarSize

WhoAmIO

убеди т есь, чт о в т екст е


содерж ит ся и н ф орм ац и я
о р а зм ер е уха, а ст роке
за го л о вк а ф и гу р и р у е т
и м я слона.

Класс Elephant
Согласно диаграмме классов E le p h a n t вам потребуется поле типа i n t с названием E a r S iz e
и поле типа S t r i n g с названием Name. (Убедитесь в наличии модификатора public.) Добавьте
метод WhoAmI (), вызывающий окно с информацией о размере уха и имени слона.

о Еще два экзем пляра Elephant и ссы лки на н и х


Добавьте к классу Form l два поля E le p h a n t (сразу под объявлением класса) с именами L lo y d
и L u c in d a. Присвойте им следующие начальные значения;
l u c i n d a = n e w E l e p h a n t О { Name = " L u c i n d a " , E a r S i z e = 3 3 };
l l o y d = new E le p h a n t 0 { Name = " L l o y d " , E a r S i z e = 4 0 } ;

Кнопки Lloyd и Lucinda


Кнопка Lloyd должна вызывать метод 1 l o y d . WhoAmI , a кнопка Lucinda —метод l u c i n d a .
WhoAmI{).
Кнопка переклю чения
Это самая слож ная часть. Кнопка Swap должна поменять местами две ссылки. То есть щел­
чок на ней заставляет переменные L lo y d и L u c in d a ссылаться на другие объекты и вызва-
ет окно с сообщением «Objects swapped» (Замена объектов). После щелчка на кнопке Swap
щелчок на кнопке Lloyd должен вызывать окно Lucinda и наоборот. Повторный щелчок на
кнопке Swap должен возвращать все обратно.

Как только на о б ъ ект не остается ни одной ссы лки, он удаляется. Ч то ж е д ел ать ?


П редставьте, что вы реш и ли пер ел и ть пиво в стакан, которы й в д анны й м ом ент
напол нен водой. Чтобы сд ел ать это вам потребуется ещ е один, пустой стакан...
Возьми 8 руку карандаш Вот как выглядит куча с объектами и ссылками на них.

“^ешение
Dog rover = new D og();
r o v e r .B reed = "G reyhound";
Dog r in T in T in = new D og();
Dog f id o = new D og(); % dof
Dog q u e n tin = fid o ;
(^здан новый объект
^од со ссылкой Spot. В
результ ат е операции
Spot = Rover объект
исчезает.

y/
© Dog sp ot
s p o t .B reed
= new
=
D og();
"D achshund";
^

spot = rover;

Создан новый объект


Dog, но после Fido=Rover
^ предыдущий объект на
/ который ссылался Fido
исчезает
© Dog lu c k y
lu c k y .B r e e d
= new
=
D og();
" B e a g le" ;
Dog C h a rlie = fid o ; . ,,
^ ^ Charlie, как и Fido, стал
fld o = rover; указывать на object
* 3 . Зат ем Fido начал
О бъ ектов: 4 ссылаться на object ^й-.
Исчезла
Ссы лок: 7 последняя
ссылка на ^
объект
r in T in T in = lu c k y ;
Dog
Dog la v er n e = new D o g ()
la v e r n e .B reed = "pug";

О б ъ екто в :. 4
Ссылка Rin Tin Tin
Ссы лок: ^ т е п ^ ь указывает
на объект Lucky,
поэт ому предыдущий
C h a rlie = la v e r n e ; объект был удален.
lu c k y = r in T in T in ;

О бъ ектов: 4
\
Ссы лок: 8 Происходит перенаправление
ссылок, но новые объекты не соз­
даются. Последний оператор не
делает ничего, так как обе ссылки
174 глава 4 и т ак указывали на один и т от
же объект.
типы и ссылки

Посмотрим, как же поменять между собой ссылки на два экземпляра,


пражнение чтобы ни один из них при этом не оказался удаленным.
решение
Э т о определение
к л а с са B ie p h a n t
u s in g S y stem .W in d o w s.F o r m s;
He за б уд ьт е _
c la ss E lep h a n t { строчку
System-Wtn-ftOV/s-
p u b lic i n t E a rS ize; pormSi»>
p u b lic s t r i n g N am e; ие о п е р а т о р
M essa g eB o x не oy
p u b l i c v o i d W hoAmI0 { д е т p a S o m a m t? -
M essa g eB o x .S h o w (" M y e a r s are + E a rS ize + " in ch es ta ll.'
Name + " s a y s . . . " ) ;

p u b lic p a r tia l c la ss Form l ; F orm {

E lep h a n t lu c in d a ;
E lep h a n t llo y d ;

p u b lic F o rm l0

In itia liz e C o m p o n e n tO ;
Это код класса l u c i n d a = n e w E l e p h a n t ()
F orm l из файла { Name = " L u c i n d a " , E a r S i z e = 3 3 } ;
Formi.cs. l l o y d = new E le p h a n t 0
{ Name = " L l o y d " , E a r S i z e = 4 0 } ;
}
p r iv a te v o id b u tto n l_ C lic k (o b je c t sen d er, E ven tA rgs e) {
С с ы л к а H o ld e r llo y d .W h o A m I( ) ;
н у ж н а для т о го }
ц т о б ы о б ш к т L io y d p r iv a te v o id b u tto n 2 _ C lick (o b je ct sen d er, E ven tA rgs e) {
не и сч ез, п о сл е т о го lu c in d a .W h o A m I( ) ;
как ссы лка б уд ет
п е р е н а п р а в л е н а на
}
объект Lucinda. p riv a te v o id b u tto n 3 _ C lick (o b je ct sen d er, E ven tA rgs e) {
- E lep h a n t h o ld e r ; О п е р а т о р n e w не и с п о л ь з у е т с я ,
S S r= ^ u c? n d ;; ^ ^ т а к к а к н а м не н у ж е н е щ е оЗын
lu c in d a = h o ld e r ; э к з е м п л я р E le p h a n t .
M e s s a g e B o x . S h o w ( "Obj e c t s s w a p p e d " ) ;
}

Типы данных string


и array от личаю т ­
ся от всех остальных
от сут ст вием фикси­
рованной длины.
Как вы считаете, почему к классу Elephant не был добавлен метод Swap()?
держи ссылку

Дбе ссылки это Д В А способа редактировать данные объекта


Наличие множественных ссылок на объект создает
опасность непреднамеренного редактирования. Дру­
V ^
гими словами, одна ссылка может енести изменения
в объект, в то время как другая рассчитана на исполь­
зование старой версии этого объекта. Посмотрите
сами:

О Добавим форме еще одну кнопку.

© Добавим к кнопке код. А теперь подумайте, что


произойдет после щелчка на этой кнопке?
После запуска кода
p riv a te v o id b u tto n 4 _ C lick (o b je ct sen d er, E v e n tA r g s e) 1^еаеменные lloyd
{ и lucinda будут
Этот опера­ llo y d = lu c in d a ; ■■— ссылаться на ОЛИН
тор прцсбаива- . llo y d .E a r S iz e = 4321; объект Elephant.
ет переменной-^ l l o y d . W h o A m I {) И е т о З WboAmiO
EarSize значение вы зы вает е^
43Д 1 т ому объекта
объекту, на ко­
торый покажет
ссылка lloyd.
С Но lloyd указывает
же, куда и lucinda.

Щ елкните на кнопке, чтобы проверить свои догадки. Смотрите, это окно ^1ер)г\0!Г^
Lucinda. При том что вы вызывали метод WhoAmI () для Ллойда.

Lucinda says...
Но это значение
Окно Аизсинды... <^е.ременной
EarSize было
определено для
Ллойда. Что
'происходит'?

Данные при эт ом
не п ереп и сы вали сь,
изменились толч^ко
ссылки.

176 глава 4
типы и ссылки

Особый случай: массиВы


Для слежения за набором данных одного типа, например, спи­
ска высот или группы собак используются м а с с и в ы (a r r a y ). Этот
термин обозначает г р у п п у п е р е м е н н ы х , которая обрабатыва­
ется как единый объект. Вы получаете возможность хранить
и редактировать большие наборы данных. Массивы объявляют­
ся аналогично другим переменным. Нужно указать имя массива значений:
и его тип: Ьоо1[]
new
Для об-ьявления мяссибя
нужно указат ь его т ип,
поставив следом ква bool [] туАггау;
дратные скобки. состоит из
1 5 элементов.
туАггау = new bool
— -
Новый массив С03-,
дается командой туАггау[4] = true
new, как и любой
другой объект.
Нумерация элементов массива нйим
Ка)кдЬ1й элемент waccuBa — это всего лишь переменная нается с О. Эта строка присваивает
пят ому элементу массива значение
Прежде всего вы должны о б ъ я в и т ь с с ы л о ч н у ю п е р е ­
в памяти массив
м е н н у ю для массива. Затем при помощи оператора new всегда хранится од­
с о з д а е т с я о б ъ е к т с указанием размера. Все готово для п о ­ ним бAOKOMJ сколь­
м е щ е н и я э л е м е н т о в в массив. Вот пример кода создания ко бы переменных
массива и вид кучи. Первый элемент массива всегда имеет в нем ни было.
нулевой и н д е к с .

Тип элементов
ИМЯ
Мйссибя heights;
heights = new int [7];
[0] = 68
[1] = 70
Вы ссыла­ [2] 63
етесь на j
элем енты < [3] = 60
массива по
индексу [4] 58
[5] = 72 ^ М а с с и в — это единый объ­
^QCOA^ ект , хотя и состоящий из
[б] = 74 семи обычных переменных.

дальше ► 177
выбираем объект из очереди

Массиб мо)кет состоять из ссылочных переменных


Массив может содержать не только числа или строки, но и ссылки
на объекты. Словом, только от вас зависит, переменные какого типа
вы хотите таким образом организовать. Можно создать как массив
Число в квадрат­
целых чисел, так и массив объектов типа Duck. ных скобках на­
Рассмотрим код создания массива из 7 переменных типа D o g . Стро­
ка инициализации создает только ссылочные переменные. За ней зывается индексом
следуют две строки new D o g {), то есть создаются два экземпляра
класса D o g . ( index) и исполь­
Dog[] dogs = new Dog [7]; Эта строка зуется для обраще­
Jk— ' объявляет
dogs [5] = new DogO; переменные,
в которых будет
ния к элементам
f dogs[0] = new DogO ; храниться
массив ссылок массива. Индекс
объекты Род.
( 7
\
первого элемента
экземпляры
(). которые пом е­
всегда равен нулю.
щаются в нулевой Т пя~
элел1ен ть/ массива.

Первая строка кода


создает только сам
массив, который ^
представляет собой
набор из семи ссылочных
переменных.

о д у -

Все элементы массива являются


ссылками, в то время как
массив — это объект.

178 глава 4
типы и ссылки

Добро по)каловать на pacnpoga)ky сэндВичей о т Д}ко!


в заведении Джо множество видов мяса, хлеб на любой вкус
и больше приправ, чем вы можете себе представить. Н ет только
меню! Сможете создать для него программу, которая будет гене­
рировать меню случайным образом?
У^їражнениеі
10І МепиМакег
Randomizer
Новы й проект, содержащ ий класс М еп и М акег Meats
Для меню вам потребуются ингредиенты. Информацию о них лучше Condiments
всего хранить в виде массивов. Ингредиенты должны смешиваться Breads
случайным образом, образуя сэндвич. В этом вам поможет встроен­
ный класс R a n d o m , генерирующий случайные числа. Итак, наш класс GetiVlenultemO
будет иметь четыре поля: R a n d o m i z e r для хранения ссылок на объект
R a n d o m и три массива типа s t r i n g для информации о мясе, приправах
и хлебе. Из элементов эт их трех м ас­
сивов будет случайным образом
class МепиМакег { создаваться сэндвич.
public Random R a n d o m iz e r ;
Поле Randomizer
содержит ссы лку — " ■,
н а объект Random, string [] M e a ts = { "Roast beef , Salami^ "Turkey", "Ham", "Pastrami" };
М е т о д NextQ гене- goring[] C o n d im e n ts = { "yellow mustard", "brown mustard",
p u p y e m случайные "honey mustard", "mayo", "relish", "french dressing" };
числа.
string[] B r e a d s = { "rye", "white", "wheat", "pumpernickel",
"Italian bread", "a roll" };
^ Для доступа к элементу укажите его номер
— в скобках. Например, элемент Breads[Z] имеет
значение white.
М ето д S e tM e n u Ite m O
Наш класс должен создавать сэндвичи, поэтому добавим к нему соответствующий метод.
Это ини­ Он будет использовать метод N e x t () объекта R a n d o m для выбора элемента из каждого мас­
циализатор
коллекций,, сива. При передаче методу N e x t () целочисленного параметра он возвращает случайное
с которым вы число меньше значения этого параметра. То есть результатом работы метода R a n d o m i z e r .
подробно п о ­ N e x t (7) будет случайное число от Одо 6.
знакомитесь
в главе 8. Но как узнать, какой параметр нужен методу N e x t {) ? Он легко вычисляется при помощи
свойства массивов L e n g t h . Именно так вы получите случайный номер элемента массива.

public string G etM en u Ite m O {


string randomMeat = M e a t s [Randomizer.Next(Meats.Length)];
Метод
QetM enuttemQ. string randomCondiment = Condiments[Randomizer.Next(Condiments.Length)];
возвр айка­ string randomBread = B r e a d s [Randomizer.Next(Breads.Length)];
ет строку ■r e t u r n randomMeat + " with " + randomCondiment + " on " + randomBread;
с названием
сэндвича. }
Случайный элемент массива Meats попадает в метод randomM eat
пат ем передачи параметра Meats.Length методу Next() объекта
Random. Массив Meats состоит из 5 элементов, значит, Meats.Length
равно 5, U мет од Next(S) возвратит случайное число от О до 4.
дальше > 179
джо говорит: «не старый, а выдержанный»

Р К ^ак ЭЩО р»абогцаега.,

той
в о зв р а щ а ет число э л е м е н -
M e a ts. Т а к ч т о r a n d o m i z e r
N e x t ( M e a t s .L e n g t h ) да ем , с л ц ч а й н о е ч и гл п

> < оли 7ст в7элем ен т ов

Me a t s [ й ш 5 о т Г г е г .Ne xt (M e a t s .Length) ]
Meats - э т о массив строк, состоящий
из п я т и э л е м е н т о в . tA e a ts ^ ^
Веер’ (Ростбиф), л Meats[3j н
(Ветчина).

© Построение срормы
Добавьте к форме шесть меток и задайте свойство T ex t. Для этого воспользуй­
тесь объектом МепиМакег. Объекту потребуется присвоить начальные значения,
исп ол ьзуя н о в ы й э к зем п л я р класса Random .
^ П р и сво и т е п о л ю R a n d o m iz e r
p u b l i c F o rm l О { О б ъ е к т а М е п и М а к е г новы й
I n i tializecom ponent О ; Г х^^^сса R a n d o m .

МепиМакег menu = new МепиМакегО { Randomizer = new Random О }j A чт о будет ,


если не у к а -
labell.Text = menu.GetMenuItemO; _ за т ь начальны е
зн ачен и я п оля
labels. Text = menu.GetMenuItemO; R a n d o m iz e r ?
labels.Text = menu.GetMenuItemO; / Все готово для запуска И как эт о го
V м е т о Э й d ^ e t M e n ы it e m ( ) м ож но и зб е ­
label4.Text = menu.GetMenuItemO; Г ы получения случайного ж ат ь?
labels. Text = menu.GetMenuItemO; \ меню из шести пунктов.
label6.Text = menu.GetMenuItemO;
}

>1орру Joe's Menu


После запуска
программы шесть
мет ок п окаж ут Salami with honey m u s ta rd on rye
шесть случайных R oast beef with french dressing on v#ieat
рецептов сэндвичей. Turkey V/rith yellow mustard on wheat
Turkey with mayo on white
Pastrami vwth relish on Italian bread
Roast beef vwth french dressing on pumpernicket

180 глава 4
типы и ссылки

Ссылки позболяют объектам обращаться друг к другу

В ы у ж е в и д е л и , как ф о р м ы о б р а щ а ю т с я к о б ъ е к т а м п р и п о м о щ и с с ы л о ч н ы х
п е р е м е н н ы х , к о т о р ы е вы зы ваю т м етоды и п р о в ер я ю т зн ач ен и я п ол ей . Д ля
объ ек тов оп р ед ел ен ы те ж е оп ер ац и и , ч то и для ф о р м , потом у ч то форма —
это объект. П р и о б р а щ е н и и объ ек тов друг к другу и сп ол ьзуется к л ю ч евое
с л о в о t h i s , с е г о п о м о щ ь ю д е л а е т с я ссы л к а н а т е к у щ и й э к з е м п л я р класса.

Метод, заставляющий слона говорить


Д о б а в и м к классу E l e p h a n t м е т о д , п е р в ы м п а р а м е т р о м к о т о р о г о будет с о о б щ е н и е о т о б ъ ­
екта e l e p h a n t . В т о р ы м п а р а м етр о м будет им я слона:

public void TellMe(string message, Elephant whoSaidIt) {


MessageBox.Show(whoSaidlt.Name + ” says: " + message);

}
М ож н о добавить м етод b u t t o n 4 _ C l i c k ( ) , н о сделать эт о нуж но до оператора, переза­
гружающего ссы лки ( 1 1 o y d = l u c i n d a ;)!

lloyd.TellMe("Hi", lucinda);

М ы в ы зв а л и м е т о д T e l l M e () (С к а ж и м н е ) д л я Л л о й д а и п е р е д а л и е м у д в а п а р а м е т р а : с т р о ­
ку H i и с с ы л к у н а о б ъ е к т L u c i n d a . П а р а м е т р w h o S a i d I t ( К т о э т о с к а за л ) и с п о л ь з у е т с я д л я
д о с т у п а к п а р а м е т р у N a m e л ю б о г о с л о н а , и м я к о т о р о г о б у д е т п е р е д а н о м е т о д у T e l l M e ()
вторы м параметром.

О Вызов одного метода другим


Д о б а в и м м е т о д S p e a k T o () к к л ассу E l e p h a n t . И м е н н о з д е с ь м ы и с п о л ь з у е м к л ю ч е в о е с л о ­
во th is . О н о является ссы лк ой, позволяющей объекту рассказать о себе.
public void SpeakTo(Elephant whoToTalkTo, string message) {
whoToTalkTo.TellMe(message, this);

Э т о т м е т о д к л а с с а E le p h a n t
к-як- ч т о п я б о т я р т в(р13Ь1в а е т м с т о д T a lk T o Q
П о с м о т р и м , как э т о р а б о т а е т . (П о го в о р и с ), п о з в о л я ю щ и й о д н о м у
lloyd.SpeakToducinda, "Hello") ; слон у р а зго в а р и в а т ь с д р уги м .

П р и в ы з о в е м е т о д а S p e a k T o () д л я Л л о й д а в ы и с п о л ь з у е т е п а р а м е т р t a l k T o ( я в л я ю щ и й ­
с я с с ы л к о й н а Л ю с и н д у ) , ч т о б ы в ы з в а т ь м е т о д T e l l M e () д л я Л ю с и н д ы .

WhoToTalkTo.TellMe(message, this);
ключевое слово this
Ллойд использует ^заменяется ссылкой
WhoToTalkTo (со ссылкой на .на объект Ллойд
Люсинду) для вызова Те11Ме()

lucinda.TellMe(message, [ссылка на Ллойда]);

В результате п оя вл яется о к н о ди ал ога с о б р а щ е н и е м к Л ю си н де:

дальше > 181


болтливые объекты

Сюда объекты еще не отправлялись


с объектами связано еще одно важное ключевое слово.
Только что созданная ссылка, которгш пока никуда не указы­
вает, имеет по умолчанию значение n u l l .

Пока сущ ествует один


Dog fido; объект, ссылка fido имеет
значение null.
Dog lucky = new Dog();

Когда ссылка fido


указывает на объект, она
оольиле не равна null.

fido = new DogO;

Если присвоить lucky


значение null, объект будет
уничтожен, так как на него
не останется ссылок.

lucky = null; 5 ах! ^

/ \Л o b i« ''''

Часш'»
• Форма — это только объект? ^адаБаеМые
Что на самом деле происходит
Б оИ роС Ь !

О
с объектами после попадания в мусор?
^ ! Да! Именно поэтому код класса начи­ ! Вот для такой проверки условия:
нается с объявления этого класса. Открой­ Q ; Помните, в начале первой главы мы
те код формы и убедитесь сами. Затем if (llo y d == n u l l ) { говорили об Общеязыковой исполняю­
откройте файл P r o g r a m . c s для любой щей среде (CLR)? Это виртуальная ма­
из написанных программ и посмотрите на Оно имеет значение t r u e , если ссылка шина, управляющая всеми программами
метод M a i n O , вы найдете там строку l l o y d указывает на n u l l . •NET. Виртуальной машиной называется
new F o r m l {). способ изоляции работающих программ
Кроме того, ключевое слово n u l l от остальной оперативной системы. Вир­
Зачем мне может потребоваться позволяет быстро избавиться от туальная машина управляет используемой
ключевое слово null? ставшего ненужным объекта. Если памятью. Она следит за всеми объектами,
ссылки на объект остаются, а вам он уже и как только последняя ссылка на какой-
не нужен, достаточно воспользоваться нибудь из объектов исчезает, она освобож­
ключевым словом n u l l , и объекг будет дает оперативную память, которая была
уничтожен. выделена под этот объекг.

182 глава 4
типы и ссылки
Часзцо
Задаваем ы е
Б оИ роС Ь !

J > Я до сих пор не очень понимаю, П : я не понимаю, почему переменные Напомните мне еще раз, какую
как работают ссыпки. ^зн ы х типов имеют разный размер. ( )ункцию выполняет ключевое слово
Зачем это нужно? th is?
Q ; Ссылки — это способ доступа к мето­
дам и полям. Создав ссылку на объекг Dog, ^ ! Переменные определяют размер Q ; t h i s — это специальная перемен­
вы получаете возможность пользоваться присваиваемого значения. Если вы объя­ ная, используемая только внутри объекта.
любыми методами, определенными для это­ вите переменную типа l o n g и присвоите Она ссылается на поля и методы выбран­
го объекта. Для нестатических методов D o g . ей значение (например, 5), CLR все равно ного экземпляра. Это полезно при работе
B a r k () и D o g . B e g () можно определить выделит памяти на большое значение. с классом, методы которого обращаются
ссылку s p o t . После чего для доступа к В конце концов, на то они и переменные, к другим классам. Объект может передать
ним достаточно написать s p o t . B a r k () чтобы все время меняться. ссылку на себя другому объекту. Если
и s p o t . B e g (). Ссылки позволяют S p o t вызывает один из методов R o v e r
редакгировать поля объекта. Например, для CLR предполагает, что вы выбираете при помощи параметра t h i s , объект
внесения изменений в поле B r e e d доста­ тип осознанно и не будете объявлять R o v e r получает ссылку на объект S p o t .
точно написать s p o t . B r e e d . переменную ненужного типа. Сейчас

Получается, что редактируя


переменная хранит небольшое значение,
но потом оно может стать большим. По- •
Экземпляры объ­
объект посредством одной ссылки, этому для нее заранее выделен нужный
я меняю его значение для остальных объем памяти. ектов используют
ссылок?

Q ; Да. Если r o v e r ссылается на тот


ключевое слово this
же объект, что и spot, заменив r o v e r .
B r e e d на beagle вы получите значение
дая ссылки па са­
beagle и для s p o t . B r e e d .
мих себя.
При рассмотрении модификатора var
КЛЮЧЕВЫЕ в главе 1 4 вы узнаете, в каком случае тип
МОМЕНТЫ переменной не обьявляется заранее.

Объявляя переменную, вы ВСЕГДА указываете ее Некоторые типы могут преобразовываться друг


тип. Иногда при этом задаются и начальные значе­ в друга автоматически (например, short в int).
ния. Для других случаев используйте операцию приведе­
ния типов.
Существует множество значимых типов для хране­
ния значений разного размера. Для огромных чисел Зарезервированные слова (например, for, while,
используйте тип long — а для самых маленьких (до using, new) нельзя использовать в качестве имен
255) — bytes. переменных.

Невозможно присвоить значение большего типа пе­ Ссылки подобны наклейкам: вы можете иметь мно­
ременной, принадлежащей к меньшему типу. жество ссылок на один и тот же объект.

При работе с константами используйте суффикс F Если на объект нет ни одной ссылки, он отправляется
для обозначения типа float (15.6F), а суффикс М — в мусор, и место, занимаемое им в памяти, освобож­
для обозначения типа decimal (36.12М). дается.

дальше ► 183
возьми в руку карандаш
Перед вами массив объектов E l e p h a n t и цикл для поиска слона
с самыми большими ушами. Определите значение b i g g e s t E a r s .
E a r s п о с л е каждой итерации цикла f o r .

p r iv a te v o id b u tto n l_ C lic k (o b je c t send er, E v e n tA r g s e)

{
' 8w создаете массив
E l e p h a n t [] e le p h a n ts = n e w E l e p h a n t [7 ] из 7 ссылок ElephantQ.
e l e p h a n t s [0 ] = n e w E l e p h a n t () { Name = "L loyd " , E a rS ize =40 };
Первый индекс
e l e p h a n t s [1 ] = n e w E l e p h a n t () { Name = " L u cin d a" , E a rS ize =33 }; любого массива
e l e p h a n t s [2] = n e w E l e p h a n t () { Name = "Larry", E a rS ize =42 };
равен О,
поэтому первым
e l e p h a n t s [3 ] = n e w E l e p h a n t () { Name = " L u c ille " , E a rS ize =32 }; элементом
I Зудет
e l e p h a n t s [4] = n e w E l e p h a n t () { Name = "L ars", E a rS ize =44 }; Elephants[0].
e l e p h a n t s [5 ] = n e w E l e p h a n t () { Name = " L in d a " , E a rS ize =37 };

e l e p h a n t s [6] = n e w E l e p h a n t () { Name = "H um phrey", E a rS ize =45 };

Итерация #1 biggestEars.EarSize =
E lep h a n t b ig g e s t E a r s = e le p h a n t s [0];

fo r (in t i = 1; i < e le p h a n ts.L e n g th ; i+ + )

^ Итерация #2 biggestEars.EarSize =.
if (e le p h a n ts[i].E a r S iz e > b ig g e stE a r s.E a r S iz e )

{
b ig g e stE a r s = e le p h a n ts[i] ;
i^licpc
Итерация #3 biggestEars.EarSize =,
} Точкд останова в этой ст ро-
ке поможет отследить значения
clepkantsli] всех элементов массива.
M essageB ox. S h o w (b ig g e stE a r s. E a r S iz e . T o S tr in g ( ) ) ;
Итерация #4 biggestEars.EarSize =

Будьте внимательны, эт от цикл Итерация #5 biggestEars.EarSize =


начинается со второго элемента
массива (с индексом 1) и выполня­
ется шесть раз, пока 1 не ст ано­
вится равной длине массива.
Итерация #6 biggestEars.EarSize = _

184 глава 4
QuiBeni на с. 92*
типы и ссылки

^ а Г н и т ь ! с КоДоМ
Магниты с фрагментами кода упали с холодильника.
Верните их на место, чтобы получить код, приводящий
к появлению показанной ниже формы. refNum = index[y]; |

int refNum;

while (y < 4 ) {

resu ]_t += islands [refNumJ ,

MessageBox.Show(result);

string[] islands = new string[4]; |

result += "\nisland = "•

index =
new int f4J ;

у = Y + i

istartd = Fiji
island = C o zu m el p r iv a te v o id b u tto n l C lick frihTIZZ ^ — ---------- ------ -------------
island = B erm uda I _ -Lick ( o b j e c t s e n d e r , E ven tA rgs e)
island = Azores

OK

-------------- ► QuiBero Ha c. 93 -
магниты с кодом и ребус в бассейне

c la ss T r ia n g le
е ^ с Б бассейне
{
Возьмите фрагменты кода из d o u b le area;
бассейна и поместите их на
пустые строчки. Каждый
in t
in t
h e ig h t;
len g th ;
I K “e
фрагмент можно исполь­ p u b lic sta tic v o i d M a i n ( s t r i n g [] args
зовать несколько раз. {
В бассейне есть и лишние str in g r esu lts =
фрагменты. Нужно получить
окно, в котором для каждого
треугольника (triangle) будет по­
w h ile (
казана его площадь (area).
{
Результат: .h e ig h t = ( x + 1) * 2;
•le n g th = X + 4;

tria n g le 0, a re a 4
tria n g le 1, a re a = 1 0 resu lts += " t r i a n g l e " + x + ", area",
tria n g le 2, a re a = 1 8 resu lts += " = " + _______. a r e a + "\n " ;
tria n g le 3, a re a = _
y=__________ }

X = 27;
OK T r ia n g le t5 = ta [2 ];
t a [ 2 ] .area = 343;
r esu lts += "y = " + y ;
M e s sa g e B o x .S h o w (r e s u lts +
Дополнительный вопрос! ", t5 area = " + tB .a r e a );
Подсказка: М е­
Вспользуйтесь фрагментами из бас­ тод SetAreaQ
сейна, чтобы составить код, заполняю­ v o id setA reaO HE является
щий даже пустые поля в нижней части статическим.
окна диалога. = (h e ig h t * len g th )

К аж д ы й ф р а гм е н т кода
м о ж н о и с п о л ь зо в а ть н е ­
с ко л ь ко раз!
4, t5 area = 18
area
4, t5 area = 343
ta.area int х;
27, t5 area = 18
ta.x.area int у; X = X + 1; ta j^ ^
X ta.x.area 27, t5 area = 343
y laixj.area
ta[x].area ^ int X == 0; X = X + 2; ta(x)
int X == 1; ' X = X -1;
Triangle [ ] ta = new Trlangle(4); setArea(); ta[x]
int у == x;
Triangle ta = new [ ] Triangle[4]; ta.x = setArea(); ta = new TriangleO;
28
Triangle [ ] ta = new Triangle[4]; ta[x].setArea(); ta[x] = newTriangleO
30.0
^ ta.x = new TriangleO;

О хпБ еш на С.
186 глава 4
типы и ссылки

играем 6 печатную машинку


Вы достигли первых результатов... и знаете достаточно, чтобы создать собственную
игру! Пусть в форме случайным образом появляются буквы. Если игрок вводит букву
правильно, она исчезает, а уровень растет. Если же буква введена неправильно, уровень
уменьшается. По мере ввода букв игра усложняется. Как только форма оказывается за-'у' / |
полнена буквами, игра заканчивается! \ ^

H it th e к в у іїЗ

и К К N С
C otrect, 18 M issed-3 Total: Д A ccuracy; 85%

^ іо р м а
Вот как она должна выглядеть в конструкторе форм.

listBoxI
Correct О Total: О Accuracy: 0 %

Уберите кнопки управления размерами окна. Для свойства FormBorderStyle выберите


вариант FixedSD. Теперь игрок не сможет случайно перетащить форму и изменить ее
размер. Установите размер 876 X 174.
Перетащите из окна Toolbox на форму элемент управления ListBox. Присвойте свой­
ству Dock значение Fill. Свойству MultiColimm присвойте значение True. Для свойства
Font выберите кегль 72 пункта и полужирное начертание.
В окне Toolbox раскройте группу All Windows Forms. Найдите элемент управления Timer
и дважды щелкните на нем, чтобы добавить его к форме.
Затем найдите элемент управления StatusStrip и двойным щелчком добавьте к форме
строку состояния. В нижней части конструктора формы должны появиться значки эле­
ментов управления StatusStrip и Timer:

0 timerl statusStripl
J
дальше * 187
создадим что-нибудь забавное

О Параметры элемента управления StatusStrip ІССЛАРТКЇ)


Обратите внимание на первый рисунок данного
раздела. В нижней части строки состояния вы Вы познакомились с тремя
увидите метки: новыми элементами управления!
Задать свойства для ListBox,
I Correcte 18 M t5sedi3 T o ta b Z t A c c u f a ^ 8 5 % |
StatusStrip и Timer вы сможете зна­
комым способом.
★ A с другой стороны —метку и индикатор:
I Difficulty Ь ю у у ж

Щ елкните на стрелке справа от элемента управления


S t a t u s S t r i p и выберите в появившемся меню вариант
S ta tu s L a b e l
S ta tu sL a b el:
P rogressB ar

D ro p D o w n B u tto n

Ш SplitButton

★ Свойству S i z i n g G r ip элемента S ta tu sS tr ip присвойте значение False.


★ В окне Properties присвойте свойству ( N a m e ) значение c o r r e c t L a b e l , а свойству T e x t
значение Correct: О (Правильных: 0). Добавьте три аналогичные метки: m i s s e d L a b e l ,
t o t a l L a b e l и a c c u r a c y L a b e l (Пропущенных, Всего и Точность).

★ Добавьте S ta tu sL a b el. Укажите для S p r i n g значение T r u e , для T e x t A l i g n значение


M id d le R ig h t и для T e x t — D i f f i c u l t y (Сложность). Напоследок добавьте элемент
P ro g r ès sB ar и присвойте ему имя d i f f і c u l t y P r o g r e s s B a r .

О Параметры элемента управления Tim er.


Вы заметили, что элемент управления Timer на форме отсутствует? Дело в том, что это не-
визуалъный элемент управленім. Он не меняет вида формы. Его функция снова и снова вы ­
зы вать некий метод. Присвойте свойству I n t e r v a l значение 800, чтобы вызов метода про­
исходил каждые 800 миллисекунд. Затем дваж ды щ елкните на значке timerl в конструкторе,
чтобы добавить к форме метод tim e r l_ T ic k . Вот код для этого метода:
p riv a te v o id tim e r l_ T ic k (o b je c t sen d er, E ven tA rgs e)

/ / Д обави м с л у ч а й н у ю клавиш у к э л е м е н т у L i s t B o x
l i s t B o x l . I t e m s .A d d ( ( K e y s ) r a n d o m . N e x t ( 6 5 , 9 0 ) ) ;
Скоро вы добавите
i f ( l i s t B o x l . I t e m s . C o u n t > 7)
поле гап(£от. Мо­
жете ли вы сейчас
{ назвать его тип?
lis tB o x l.Ite m s.C le a r 0 ;
l i s t B o x l . I t e m s . A d d ( "Игра окончена!");
t im e r l. S to p ();
}

188 глава 4
типы и ссылки

Класс, отслеживающий статистику игры


Так как в форме должно отображаться общее количество нажатых, пропу­
щенных и правильно нажатых клавиш, а также точность, значит, мы долж­
ны отслеживать все эти данные. Кажется, нам потребуется новый класс!
Назовите его S t a t s . Он будет содержать четыре поля типа i n t с именами
T o t a l (Всего), M i s s e d (Пропущено), C o r r e c t (Правильно) и A c c u r a c y
(Точность), а также метод U p d a t e с одним параметром типа b o o l , кото­
рый получает значение t r u e , если нажатая игроком клавиша совпала с бук­
вой, появившейся в окне ListBox, и значение f a l s e , если игрок ошибся.

c la ss S ta ts
{
p u b lic in t T o t a l = 0;
p u b lic in t M is s e d = 0;
p u b lic in t C o r r e c t = 0;
p u b lic in t A c c u r a c y = 0;

p u b lic v o id U p d ate(b ool correctK ey)


{
T o t a l ++;

if ( ! correctK ey)
{
M isse d + + ; При каждом вызове метода
} UpdateQ вычисляется про­
e ls e цент правильных попаданий,
{ а результ ат эт их вычислений
C o r r e c t ++; помеицается в поле Accuracy.
}
A c c u r a c y = 100 * C orrect / (M isse d + C orrect) ;
}
}
Поля для отслеживания объектов S tats и Random
Вам потребуется экземпляр класса Stats для хранения информации, поэтому добавьте поле
stats. Как вы уже видели, поле random, содержащее объект Random, пока отсутствует.

Напишите в верхней части кода формы:


Прежде чем продолжить, присвойте свойству
p u b lic p a r tia l c la ss Form l : Form Enabled таймера значение True. Для элемента
{ ProgressBar сделайте свойство Maximum
Random random = new Random(); равным 701, а для свойства KeyPreview
Stats stats = new Stats(); формы выберите значение True. Попробуйте
ответить на вопрос, что будет,
если это не сделать?

дальше ► 189
ключ к прекрасной игре

Контроль за нажатием клавиш.


Осталось сделать так, чтобы правильно нажатая игроком буква удалялась из окна ListBox),
а статистика S t a t u s S t r i p обновлялась. Эта кнопка меняет
вид окна Properties.
Вернитесь к конструктору форм и выделите Properties
Кнопка слева от
форму. Щ елкните на кнопке с изображением Forml System.Wim нее возвраш,ает
молнии в верхней части окна Properties, а за­
тем дважды щелкните на строчке KeyDown,
О' !
InputlanguageChdf
окно к привычноми
виду.
n l.K e y O sm i Ц
чтобы добавить метод Forml_KeyDown (), вы­ KeyPfess Вы получаете
зываемый при каждом нажатии клавиши. Вот KeyUp
список событий
код для этого метода: (events). О т ом,
что это такое,
p r i v a t e v o i d F o rm l_ K ey D o w n (o b je c t s e n d e r , K ey E v en tA rg s e) мы поговорим
{ позднее.
Оператор П Е с л и п о л ь з о в а т е л ь п р а в и л ь н о наж и м а е т клавишу, у д а л и т е б у к в у из L i s t B o x ,
if проверя­
ет н а л и ч и е ^ и и у в е л и ч ь т е с к о р о с т ь п о я в л е н и я б у к в
в ListBox на­ i f ( l i s t B o x l . I t e m s . C o n t a i n s ( е . K e y C o d e ) )
жатой игро­ {
ком буквы. l i s t B o x l . Ite m s.R e m o v e (e . K eyC ode);
l i s t B o x l .R e fr e sh ();
i f (tim e r l. In terval Уменьшая числа, вычитаемые из
tim e r l. In terval парамет ра timeri-.Interval, вы
i f (tim e r l. In terval облегчаете игру, а увеличивая —
tim e r l. In terval усложняете ее.
i f (tim e r l. In terval
tim e r l. In terval
d iffic u lty P r o g r e ssB a r .V a lu e = 800 - tim e r l. In te r v a l;

При нажа­ / / При правильном нажатии клавиши обновляем объект S t a t s ,


тии клавиши / / в ы з ы в а я м е т о д U p d a t e () с а р г у м е н т о м t r u e
метод Forml. s ta ts .U p d a te (tr u e );
KeyPown() }
вызывает м е ­ e lse
тод UpdateQ {
объекта Stats, // П р и н е п р а в и л ь н о м н а ж а т и и к л а в и ш и о б н о в л я е м о б ъ е к т S t a t s ,
обновляющий // в ы з ы в а я м е т о д U p d a t e () с а р г у м е н т о м f a l s e
ст ат ист и- s ta ts .U p d a te (fa ls e );
ку, а зат ем ''
р езуль­ }
т ат от о-
6J}aжaemcя / / Обновление меток элемента S t a t u s S t r i p
StatusStrip. c o r r e c t L a b e l . T e x t = " C o r r e c t : " -i- s t a t s . C o r r e c t ,•
m i s s e d L a b e l . T e x t = " M issed : " + s t a t s . M is se d ;
t o t a lL a b e l.T e x t = " T o ta l: " + s t a t s . T o t a l ;
a c c u r a c y L a b e l. T ext = "A ccuracy: " + s t a t s .A c c u r a c y +

Запуск игры.
В окно ListBox должно помещаться ровно 7 букв, поэтому может потребоваться поменять
размер шрифта. Для изменения уровня сложности отредактируйте значения вычитаемые из
параметра t i m e r l . I n t e r v a l в методе F o r m l _ K e y D o w n ( ) .

190 глава 4 ★ -
типы и ссылки

Е щ е раз напомним, что в С # сущ ествует примерно 77 з а р е з е р в и р о в а н ­


н ы х сл о в . Вам бы ло предлож ено объяснить назначение некоторых из
г^р аж н ен и б
них. Вот правильны е ответы.
решение
namespace Пространство имен — это логическое понятие, объединяюи^ее
связанные друг с другом классы и типы.

for Это цикл, выполняющий оператор или блок операторов, пока


определенное выражение не прим ет значение false.

Класс —- это определение объекта. Классы имею т


class свойства и методы.

Ключевое слово, дающее уровень доступа с максимальными правами.


public Public class может использоваться любым другим классом.

else Оператор, который выполняется, если проверка условия if вернула


значение false.

while Цикл while выполняется до тех пор, пока условие имеет значение
true.

using Определяет пространства имен, предустановленные и созданные вами


классы, которые используются в программе.

if Оператор, выбирающий другие операторы по результ ат ам


проверки условия.

new Оператор, создающий новый экземпляр объекта.


решение упражнения

озьми в руку карандаш


’ешение Вот как выглядят результаты определения самого боль­
шого слоновьего уха на каждой итерации цикла for.

p riv a te v o id b u tto n l_ C lic k (o b je c t send er, E ven tA rgs e)

{ П ом ните, 41^0
E l e p h a n t [] e le p h a n ts = new E l e p h a n t [ 7 ]; цикл НйИиНйбИЛСЯ
со второго эле­
e l e p h a n t s [0] = n e w E l e p h a n t () { Name = " L l o y d " , E a rS ize =40 }; мента т с с м а .
К ак 6(?| Эуллаете,
e l e p h a n t s [1 ] = n e w E l e p h a n t () { Name = " L u c i n d a " , E a rS ize =33 }; почему?
e l e p h a n t s [2] = n e w E l e p h a n t () { Name = " L a r r y " , E a rS ize =42 };

e l e p h a n t s [3] = n e w E l e p h a n t () { Name = " L u c i l l e " , E a rS ize =32 };

e l e p h a n t s [4] = n e w E l e p h a n t () { Name = " L a r s " , E a r S i z e = 44 };

e l e p h a n t s [5 ] = n e w E l e p h a n t () { Name = " L i n d a " , E a rS ize =37 };

e l e p h a n t s [6] = n e w E l e p h a n t () { Name = " H u m p h r e y " , E a rS ize = 45 }

Итерация #1 biggestEars.EarSize = 4 0
E lep h a n t b ig g e s t E a r s = e le p h a n t s [0];

for (in t i = 1; i < e le p h a n ts.L e n g th ; i+ + )

{
Итерация #2 biggestEars.EarSize = 4 2
if ( e l e p h a n t s [ i ] . E a rS ize > b ig g e stE a r s.E a r S iz e )

Ссылка
{
b ig g e stE a r s = e le p h a n ts [i] ;
t biggestEars
отслеживает
самый боль - Итерация #3 biggestEars.EarSize =
42,

} Поместите сюда точку элемент


— останова для проверки g ц^кле.
>} результат а.
M e s s a g e B o x .S h o w (b ig g e stE a r s .E a r S iz e .T o S tr in g ());
Итерация #4 biggestEars.EarSize =

иОЛОШ^, .. - .......
/ Итерация #5 biggestEars.EarSize =

нает указывать нд него. Таким


образом определяется слон с са­
мыми большими ушами.
•4 -S
Итерация #6 biggestEars.EarSize = _____

192 глава 4
типы и ссылки

|^е1Нение Задачи сг МаГнишаМи


Вот каким образом нужно было расположить на хо­
лодильнике магниты с фрагментами кода.

f ^ c b задаются
^лчальные
значения
массива fndexli

Здесь задаются
начальные значения
массива 1$1апи51].

Итоговая строка
составляется
in t refN u m ; | при помощи
оператора +=■
w h i l e ^ (y < 4) { P
while
^^^Менилы
retN um = in d ex [y ];
^^ссива indexn
r esu lt += " \n isla n d = 1

r esu lt += i s l a n d s [ r e f N u m ] ; J
island = Fiji
island = C o zu m e l
island = B erm u da
island = Azores

дальше > 193


решение ребуса

еШ ен и е ]= * е^ са Б б а с с е й н е

Вы зам ет или, что класс


им еем мочку входа и при
эт ом создаем экземпляр
r себя? В С* такое вполне
c la ss T r ia n g le допусмимо.
{
d o u b le area;
in t h e ig h t;
in t len g th ;

После э т о й строки p u b lic sta tic v o i d M a i n ( s t r i n g [] args)


вы получаете массив {
из четырех ссылок strin g r esu lts =
Trian gle, но пока нет i n t X = O;
ни оЭного оЗьекта___ Triangle[] ta = new Triangle[4]; Цикл while
Triangle! создает чет ы -
w h ile ( X < 4 )
ре экземпляра
{ Triangle, чет ы ­
ОтВеш на дополнитель­ ta[x] = new TriangleQ) ре раза вызывая
ный Вопрос: ta fxl -h eig h t = (x + 1 ) * 2 ; оператор new.
ta fxl . le n g th = x + 4 ;
ta[x].setAreaQ;_____
r esu lts += " tr ia n g le " + x + area"
r e s u l t s += = " + ta fxl .area + " \n " ;
trtangte 0, area = 4 X = X + 1;

triangle 1, area = 10 }
triangte 2, area = 18 i n t у ~ x;

triangle 3, area = z s X = 27;

V = 4^ ts a r e a = 3 4 5 T r ia n g le t5 = ta[2];
t a [2 ] . a r e a = 343;
resu lts += "y = " + y ;
M e s sa g e B o x .S h o w (r e su lts +
OK ", t5 area = " + tS .a r e a );
}
v o id setA reaO
{
area = (h eig h t * len g th ) / 2;
Метод setAreaQ использу­
ет поля height и length для
вычисления значения поля }
area. Так как мет од не
является стат ическим, он
может вызываться только
для экземпляра Triangle.

194 глава 4
5 инкапсуляция

Пусть личное остается...


личным

Вы когда-нибудь мечтали о том, чтобы вашу личную жизнь


оставили в покое? Иногда объекты чувствуют то же самое. Вы же не
хотите, чтобы посторонние люди читали ваши записки или рассматрива­
ли банковские выписки. Вот и объекты не хотят, чтобы д р у ги е объекты
инкапсуляции.
имели доступ к их полям, в этой главе мы поговорим об
Вы научитесь закрывать объекты и добавлять методы защищающие
данные от доступа.
помогите кэтлин

Кэтлин профессиональный массобик-затейник


Она организует великолепные званые ужины.
Н о для нее настали тяжелые времена, так как
по мнению клиентов Кэтлин недостаточно
стро называет стоимость услуг.

5юЭжет.

После получения от нового клиента информации о количестве участ­


ников, желаемых напитках и оформлении она с помощью специальной
диаграммы выполняет довольно сложные вычисления, чтобы опре­
делить конечную стоимость, к сожалению, это медленный процесс,
и пока Кэтлин считает, клиенты звонят ее конкурентам.
Вы можете написать программу, автоматизирующую расчеты, и спасти
бизнес Кэтлин. Только представьте, какой обед она устроит вам в каче­
стве благодарности!

196 глава 5
инкапсуляция

Как происходит оценка


Вот фрагмент записей Кэтлин, касающийся расчетов
стоимости мероприятия:

О и е н к а стои м ости обеда

За каждого гостя — $25.


Большинство обедов сервируется с алкогольными напитками — это
дополнительные $20 на человека. Можно выбрать «здоровый» ва­
риант - это стоит всего $5 на человека, вместо алкоголя подаются
соки и газировка. «Здоровый» вариант намного проше в организа­
ции, поэтому скидка составит 5% на всю стоимость мероприятия.
. Обычное оформление стоит $7.50 на человека плюс первоначаль­
ный взнос $30. Стоимость особого оформления увеличивается до
$15 на человека, а первоначальный взнос составит $50.

В некоторых случаях
меняется не только
стоимость на одного
Для наглядности представим эти правила в виде гостя, но и общая цена
диаграммы: мероприятия.

$5
на человека-«^ $15 на че­
5% скидки ловека
с общей +ВЗНОС
суммы $50
Кол ичество О собо е
гостей «Здоровы й» чштшшлштт
ч оф о р м л е­
Еда ($2Ь вариант? ние?
на человека)
Нет ч
$20 с ч ел о ­ $7.50 на ^
века
человека 1
+взнос$30 1

г
Выбор этого варианта
не только влияет на ст о­
имость за одного человека,
но и включает единовре­
менный взнос.

дальше > 197


ok, нет проблем

Создадим программу, оценивающую стоимость обедов.


нение
Создайте проект Windows Application, добавьте класс D in n e rP a rty .c s
и оформите его в соответствии с показанной слева диаграммой. Ме­
тоды затрачивают расчеты стоимости «здорового» варианта, сто­
имости оформления, а также общей стоимости мероприятия. Два
последних поля должны принадлежать типу d e c im a l, а первое —
Dinnerparty типу i n t . Убедитесь, что в конце каждой константы типа d e c im a l
стоит б у к в а м (например, 10 . ОМ).
NumberOfPeople
CostOfBeveragesPerPerson @ Так как стоимость еды на одного человека не будет меняться, ее
CostOfDecorations можно объявить как консгттту. Вот как это делается:
p u b lic const in t C ostO fF oodP erP erson = 25;

SetHealthyOptionO e Один из методов возвращает значение типа d e c im a l, два дру­


CalculateCostOfDecorationsO гих — нет. Метод C a lc u la te C o s tO f D e c o r a tio n s O вычисляет
CalculateCostO стоимость оф ормления в зависимости от количества пригла­
шенных. Метод C a l c u la te C o s t () вы числяет общую стоимость,
складывая цену оф ормлениы , еды и напитков в зависимости от
количества приглащенных. П ри выборе здорового варианта де­
лается скидка с общей стоимости.

Добавьте к форме код:


Вы объявляете поле
Dinnerparty dinnerparty; dinnerparty и добавляете
p u b lic F o rm lО { четыре строчки.
In itia liz e C o m p o n e n tO ;
dinnerparty = new DinnerParty() { NumberOfPeople = 5 };
Рямет р dinnerparty.SetHealthyOption(false);
обновления SHauff dinnerparty.CalculateCostOfDecorations(true);
DisplayDinnerPartyCost0;
>

О Форма должна выгля-


деть так. Используйте
^

элемент N um ericUpDown, ццсА®


Party Planner
чтобы задать максималь-
Код using System. ное количество людей ~~~ Number rf People
VJindows.forms: для
класса Dinnerparty
равным 20, а минималь­
ное — 1. По умолчанию Свойство
^ it

не требуется, т ак [V] Fancy Oeceathms


количество приглашен- ^l^ccked
как вы не собираетесь Ej H eÄ yO f*«!
(^спользовать м е т о ­ ных равно 5. Избавьтесь t t Z a t i o n ^ ^
ды из пространства от кнопок управления должно имет ь Cost i
имен .NET Framework. размерами окна. значение True.

Имя этой м ет ки labelCost. Свойство Text оставлено


пуст ым, свойство BorderStyle имеет значение--------
FixedsD, а свойство AutoSize значение false.
198 глава 5
инкапсуляция

О Сделаем так, чтобы итоговая сумма автоматически менялась при изменении


флажков и показаний счетчика Ыитег1сирВо1А?п. Для этого вам потребуется ме­
тод, отображающий сумму.
Добавим его к Forml ( ). O h будет вы зы ваться при щ елчке на элем енте
Этот hAtmob вызы­ NumericUpDown:
ваемся всеми м е - Метод рассчитывает ст о-
тоЗаМи, связанными
p r iv a te v o i d D i s p l a y D i n n e r P a r t y C o s t ()
с формой, именно " р езул ьт ат м е т к е Cost.
т а к обновляется {
з н а ч е н и е м е т к и C ost. d e c im a l C o st = d i n n e r p a r t y .C a l c u la t e C o s t ( c h e c k B o x 2 . Checked)
c o s t L a b e l. T ext = C o s t.T o S tr in g (" c " );

Присвойте мет ке,


А ргум ент "с” метода
t Значение true появля-
отображающей
цену, имя costLabel. ToStringO отображает в а л ю
с использованием приня- Healthy Option,
того по соглашению символа.

О Соединим поле NumericUpDown с переменной N um berO fPeople, принад­


лежащей классу D in n e r p a r ty , чтобы в ф орме начала отображ аться сумма
При двойном расходов. Дважды щ елкните на элементе NumericUpDown, в код будет до­
щелчке на
кнопке ИСР бавлен обработчик событий (event handler). Так называется метод, запускае­
добавляет об­ мый при каждом изменении элемента управления. Он сбросит количество
работчик со­ гостей. Впишите следующий код:
бытия Click. p riv a te v o id n u m ericU p D o w n l_ V a lu eC h a n g ed (
o b ject sen d er, E ventA rgs e)
{
d in n e rp a rty .N u m b erO fP eo p le (i n t ) n u m ericU p D ow n l. V a l u e ;
^ D isp la y D in n e r P a r ty C o st();
J C_ Значение numericUpPown-Value
принадлежит т ипу Decimal,
поэт ому требуется операция
приведения типов.
Ой... код содержит ошибку. Вы ее видите? Если нет, не волнуйтесь. Скоро мы
все объясним!
Из формы методу Первый мет од рассчит^.,а
передается л о - ^ ^ ^ ‘^Мость
гическое значение \ второй
fancyBox.Checked. конечную сум м у в форме.

Дважды щ елкните на флаж ке(РапсуВесога11оп5’у убедитесь, что


сначала он вызывает метод C alcu iacscC ig iC C fD ecS ratio n s (), а по­
том метод D lsp la^ d iiB i^ erP arty C G S t (). Затем дважды щелкни­
те на флажке ^Healthy O ption? и убедитесь, что он сначала вызыва­
ет метод S e t H e a l t h y O p t i o ^ ) класса D in n e rP a r ty , а затем метод
D is p la y D in n e r P a r ty C o s t ( ) /

дальше > 199


решение упражнения

В от такой код дол ж ен содерж аться в ф айле D i n n e r P a r t y . c s .


т нш
И сп ользован и е конст ант , га р а н т и р у ­
е м , ч т о данны е п а р а м е т р ы о с т а н у т ­
ся н е и з м е н н ы м и н а п р о т я ж е н и и в с е й
п рогр ам м ы .
c la ss D in n erP a rty {
const in t C ostO fF oodP erP erson = 25; Создав объект, форма ис­
p u b lic i n t N u m b e rO fP eo p le ; пользует инициализатор
для указания параметра
p u b lic d e c im a l C ostO fB everagesP erP erson;
NumberOfPeople. Зат ем м е ­
p u b lic d e c im a l C o stO fD eco ra tio n s = 0; тоды SetHealthyOptionO
и CalculateCostOfDecorationsO
p u b lic v o id S e tH e a lth y O p tio n (b o o l h ea lth y O p tio n ) { задают значения других полей.
if (h ea lth y O p tio n ) {
C o s t O f B e v e r a g e s P e r P e r s o n = 5.00M ;
} e ls e {
C o s t O f B e v e r a g e s P e r P e r s o n = 2 0 .00M ; Оператор i f в с е г д а п р о в е р я е т , CO
5 л ю д а ет с я ли у с л о в и е , п о э т о м у
}
д о с т а т о ч н о н а п и с а т ь " if ( F a n c y ) ”
}
в м е с т о " if ( F a n c y = - t r u e ) ”.

p u b lic v o id C a lcu la teC o stO fD eco ra tio n s(b o o l fancy) {


if (fan cy)

{
C o stO fD eco ra tio n s = (N u m b erO fP eo p le * 1 5 .00M ) + SOM;
} e ls e {
C o stO fD eco ra tio n s = (N u m b erO fP eop le * 7.50M ) + 30M;
}
}
p u b lic d e c im a l C a lc u la te C o s t(b o o l h ea lth y O p tio n ) {
d e c im a l to ta lC o st = C o stO fD eco ra tio n s +
( (C o stO fB e v er a g e sP er P e r so n + C ostO fF oodP erP erson )
N u m b erO fP eo p le)
^ М ы и сп о л ьзо ва л и скобки , ч т обы г а -
ран т и роват ь прави льн ост ь м а т е ­
if (h ea lth y O p tio n ) {
м а т и ч е с к и х вы чи слен и й .
retu rn to ta lC o st .95М ;
} e ls e {
retu rn to ta lC o st;

} S % -я ск и дк а п р и у с л о в и и ,
ч т о обед п о д а ет ся без а л -
к о го л я .

200 глава 5
инкапсуляция

Д ля переменных, содержащих цены, выбирается тип d ecim a l. Всегда помещайте букву М


после цифры: чтобы присвоить переменной значение $35.26, нужно написать 3 5 . 2 6М. Это
легко запомнить, потому что М —первая буква в слове money (деньги)!

p u b lic p a r tia l c la s s Form l : F orm { DisplayDinnerPartyCost пере-


/-■ Оаепл. оанные мет.ке, которая от о-
D in n erP a rty d in n e r p a r ty ; / бражает сум м у расходов сразу после
p u b lic F orm iO { / загрузки формы.
In itia liz e C o m p o n e n tO ;
d in n erp a rty = n e w D i n n e r P a r t y () { N u m b e rO fP eo p le = 5 };
d in n e r p a r ty .C a lc u la te C o stO fD e c o r a tio n s(fa n c y B o x .C h e c k e d );
d in n e r p a r ty . S e tH e a lth y O p tio n (h e a lth y B o x .C h e c k e d );
D isp la y D in n e rP a r ty C o st 0 ; флажки меняьот значение перем ен-
} healthyOption и Fancy с true на
false и обратно.
p riv a te v o id fan cyB ox_C h eck ed C h an ged (ob ject send er, E ven tA rgs e) {
d in n e r p a r ty .C a lc u la te C o stO fD e c o r a tio n s(fa n c y B o x .C h e c k e d );
D isp la y D in n e r P a r ty C o st0 ;
Мы присвоили флажкам имена healthyBox
и fo.t\cyЗoxJ Ч1/\лоды вш м о 2ли чидо 1^ роис~
ходит в их методах обработчиков событий.
p r iv a te v o id h e a lth y B o x _ C h ec k e d C h a n g ed (o b jec t send er, E ven tA rgs e) {
d in n e r p a r ty . S etH ea lth y O p tio n (h ea lth y B o x .C h eck ed );
D isp la y D in n e r P a r ty C o st();

p riv a te v o i d n u m ericU p D o w n l_ V a lu eC h an g ed (o b ject sen d er, E ven tA rgs e) {


d in n e rp a rty .N u m b erO fP eo p le = (in t)n u m e r ic U p D o w n l.V a lu e ;
D isp la y D in n e rP a r ty C o st 0 ; ^ -------- СуМма
должна
tv'V"/»/-I"»* пересчитываться
---- -
и отображаться при каждом изм е-
J у^ '
нении количества гостей и1. каждой
^ л■• л (УЛ ЧА/ ЛЛ t~
i
V- установке флажка.
p r iv a te v o id D isp la y D in n e r P a r ty C o stО {
d e c im a l C o st = d in n e r P a r ty .C a lc u la te C o s t(h e a lth y B o x .C h e c k e d );
c o stL a b e l.T e x t = C o s t. T o S tr in g ( "c");

Метод ToStringQ преобразует переменные в строки. Аргумент при


этом преобразуется в локальную денежную единицу. Аргумент "ft5”
форматирует результат в виде типа decimal с тремя знаками после
запятой^ (ноль) превращает результат в целое число, "0%” — в це­
лое с процентами, а "п” дает число с запятой в качестве разделителя
групп разрядов. Вы сами можете посмотреть, как это работает!

дальше > 201


что-то пошло не так

Кэтлин те сти р уе т программу


г, S и з AhoSuMfIX к л и
лй К э т л и н . Она о р г а и ы з о -
Потрясающе! Как
быстро я теперь могу
назвать сумму! бйние званого обеда.

Роб (по телефону): Привет, Кэтлин! Как там под­


готовка моей вечеринки? флажок Fancy Decorations
установлен по ум олча-
Кэтлин: Великолепно. Мы уже заказали оформле­ HUhOj так как его свойство
ние. Тебе должно понравиться. Checked имеет значение
true. Если количество го­
Роб: Фантастика! Послушай, мне только что по­ стей равно Ю , стоимость
звонила тетя жены. Она с мужем собирается пого­ обеда составит $575.
стить у нас пару недель. Сколько будет стоит вклю­
чение в список гостей еш;е двух человек?
Кэтлин: Подожди минутку...

ш P arty P la n n e r
Изменив парамет р
Number rf People Number of People с l o
12 на IZ j в итоге п о лу­
Fancy DecoraJtons
чаем $б&5. Сумма
кажется ей слишком
H He^hyC^ton маленькой...
Coat !№65:00

Кэтлин: Кажется, общая стоимость обеда возрас­


тет с $575 до $665.
Роб: Всего на $90? Соблазнительно! А какая цена
получится, если убрать особое оформление?

202 глава 5
инкапсуляция

'iff Party Planner

МшЛег«# Peofrfe
Снятие флажка
Рапсу Decorations Ратсу OecOTthjns

s r s a r
^пакого не может!
Healthy Option

Coat | ii«6b.00

Кэтлин; Ээээ... получается сумма в $660.


Роб: $660? Я думал, оформление стоит $15 с человека. Вы что, поменяли цену?
Если разница составит всего $5, оставим особый вариант. Хотя, должен заме­
тить, что в твоих ценах я уже запутался.
Кэтлин: Я только что получила новую программу для оценки расходов. Но
кажется, она где-то ошибается. Подожди секунду, я попробую снова добавить
к счету особое оформление.

Itete-rfPes^jte
12
g ] Fancy DecotaHws
не Эол>кно!
H HeafchyOptiOTi

Кэтлин: Роб, произошла ошибка. С особым оформлением цена возрастает до


$770. Что-то я не доверяю этому приложению. Верну-ка я его на доработку и по­
считаю вручную, как и раньше. Я могу позвонить тебе завтра?
Роб: Я не готов платить $770 за двух дополнительных гостей. Сначала ты озву­
чила мне намного более разумную цену. Я дам $665 и ни центом больше!

ШТУРМ
Как вы думаете, почему каждое изменение условий давало ошибку в результате?

дальше У 203
неожиданно оказалось, что...

Ка)кдьш бариант ну)кно было счи тать АССЛ^?Т)ТК1)


отдельно Это не ваша вина!
В код специально была помеще­
Подсчет сумм велся строго по диаграмме Кэтлин, но мы не учли на ошибка, чтобы показать, ка­
того, каким образом на общий счет влияет изменение каждого кие проблемы возникают, когда
параметра формы. объекты используют поля друг
друга, и то, как сложно эти про­
При запуске формы количество гостей по умолчанию равно
блемы отследить.
5, кроме того, установлен флажок Fancy Decorations. Флажок
Healthy Option не установлен. При этих условиях стоимость ме­
роприятия равна $350. Вот как получается эта сумма:

«в Party Planner
5 человек
Nwnbe-of Peof^e
$20 с человека за напитки С тоим ость напитков = $100
$25 с человека за ед у -— С тоим ость еды = $125 Farwy O ecw afior»

$15 с человека за —----------- С тоим ость оф ор м л ения = $125 He^qrOjjBon


оф ор м л ение и взнос $50 Cost ^OKa все
‘^Р^вильно.
a j
$ 1 0 0 + $ 1 2 5 + 125 = $350

П ри изменении количества гостей приложение должно пересчи­


тывать сумму аналогичным образом. Но этого не происходит:

10 человек
Щ Party Planner
$20 с человека за напитки С тоим ость напитков = $200
Рес^
$25 с человека за ед у —- - V С тоим ость еды = $250
$15 с человека за ------------- ^ С тоим ость оф ор м л ения = $200 Н Fmc^ Оеояйюпз
оф орм л ение и взнос $50 В НеЛуйэеоп
Сол
$200 + $250+ 200 = $650

Снимите флажок Fancy Deco­


rations и проверьте результат. Программа складывает
'^^^лунит.ь. старцю иенц оформления
Поле объекта
C o stO fD ec o ra tio n s
с новьши ценами еды
обновится, и вы полу­
D in n er P a rty и напитков.
чите правильную сумму $650. + ^3-2.5= $575.
t У ' ^
Нена еды С тар ая цена
204 глава 5 ^»1^итков. оформления.
инкапсуляция

П р о б л е м а Ц оД у В е Л и Ч ш п е Л ь Н ь їМ с т е И І о М

Рассмотрим метод, обрабатывающий изменение состояния элемента


n u m e r i c U p D o w n . Он берет значение переменной N u m b e r o f P e o p l e и вы­
зывает метод D i s p l a y D i n n e r P a r t y C o s t О . Именно здесь осуществляет­
ся пересчет конечной суммы.
p r iv a te v o id n u m er icU p D o w n l_ V a lu e C h a n g ed
Эта строка
ob ject sender, E ven tA rgs e) {
задает значе­
d in n e r P a r ty .N u m b e r o fP e o p le = (in t)n u m e r ic U p D o w n l.V a lu e ;
ние параметра
l^umЬerofPeopie■
D isp la y D in n e r P a r ty C o st(); Эля экземпляра
ріппсграгіу на
1 Аанный m m o d вызывает метод основе введенных
' CalculateCostO, но забывает вызвать
мет од CalculateCostofPecorationsO- в форму данных.

То есть при изменении значения в поле N u m b e r o f P e o p l e


показанный ниже метод никогда не вызывается:

p u b lic v o id C a lcu la teC o stO fD eco ra tio n s(b o o l Fancy) {


Эта переменная сохраняет
if (F ancy) { значение полученное
при первом вызове формы.
C o stO fD eco ra tio n s = (N u m b erO fP eop le * 1 5.00М ) + 50М;

} e ls e {

C o stO fD eco ra tio n s = ( N u m b e r O f P e o p l e * 7 . SOM) + 30M;

Повторная установка флажка Fancy


} Pecorations снова запускает метод
CalculateCostOf PecorationsQ, что
приводит к коррекции данных.

Предполагалось, что все


три варианта будут выбираться
одновременно!

...иногда этими
К сожалению, пользователи не всегда исполь­ «пользова теля -
зуют классы так, как предполагал разработчик. м и » являетесь
К счастью, в C# есть функция, позволяющая га­ вы сами!
рантировать корректную работу программы, даже
когда пользователь делает вещи, о которых вы и
предположить не могли. Она называется инкапсу­
л яцией (encapsulation).

дальше ► 205
защити свои объекты

Неправильное использование объектов


Программа не принесла Кэтлин пользы, так как форма, проигнорировав метод
C a l c u l a t e C o s t O f D e c o r a t i o n s О , сразу перешла к полям класса D i n n e r P a r t y .
Этот класс написан без ошибок, но программа все равно работает некорректно.

Как нужно было бы вызывать класс DinnerParty


Класс D i n n e r P a r t y предоставляет форме прекрасный метод расчета
конечной стоимости оформления. Достаточно указать количество го­
стей и вызвать метод C a l c u l a t e C o s t O f D e c o r a t i o n s (), после чего
метод C a l c u l a t e C o s t {) покажет правильную сумму.

NumberOfPeople = Ю;
CalculateCostOfDecorations(true)

C a lc u la te C o stO retu rn s $650

о этот класс на самом деле вызывается


После указания количества гостей форма сразу вызывает метод
C a l c u l a t e C o s t O , игнорируя расчет стоимости оформления. То
есть пропускается целый этап, и в итоге получается неверная сумма.

NumberOfPeople - 10;

C a l c u la te C o s t O возвращ;ает$575
Метод CalculateCostO
\ возвращает значение,
но К эт лин не может
знать, что результ ат
расчетов некорректен.

206 глава 5
инкапсуляция

инкапсуляция как управление доступом к данным


В С* поля по умолчанию
Подобных проблем можно избежать: достаточно оставить один способ считаются закрытыми
работы с классом. Для это в C# в объявлении переменных использует­ поэт ому модификатор
ся ключевое слово p r i v a t e . До этого момента вы встречали только мо­ private зачастую опц~
дификатор public. Поля объектов, помеченные этим модификатором, скается.
были доступны для чтения и редактирования любым другим объектом.
Модификатор private делает поле доступным только изнутри объекта
(или из другого объекта этого же класса).

Статические методы им ею т
доступ к закрытым полям всех
к .« .» ,
полю достаточ-
Х" хлк>чевым словом private
ключедым
class DinnerParty { X при его объявлении. В итоге доступ к полю
^ экземпляра DinnerPartu
p r i v a t e int numberOfPeople; ^ ><^лсса.
А ругие объекты не смогут его «увидеть ».-

public void SetPartyOptions(int people, bool fancy) {

numberOfPeople = people;

CalculateCostOfDecorations(fancy); лребу-
'i го-
}
public int GetNumberOfPeople() { ^

return numberOfPeople;

поле, содержащее информацию о ко­


Зак р ы в Инкапсулированный -
личестве гостей, мы оставим форме всего заключенный в защитный
один способ передать эти данные классу
DinnerParty, гарантировав правильный рас­
слой или мембрану. Подвод­
чет стоимости оформления. Закрытие доступа ники полностью инкапсулиро­
к данным с последующим написанием кода ваны в подводной лодке.
для их использования называется инкапсуля­
цией (encapsulation).

дальше ► 207
шпионское противостояние

Д оступ к методам и полям класса


Доступ к ПОЛЯМ и методам, помеченным словом public, имеет любой класс. Вся содержащаяся в них
информация подобна открытой книге... вы уже видели, как подобные вещи могут стать причиной не­
предсказуемых результатов. Инкапсуляция меняет уровень доступа к данным. Рассмотрим на примере,
как это работает:

Супершпион Херб Джонс стоит на страже интересов, свобо­ SecretAgent


ды и счастья, работая секретным агентом в СССР. Его объ­
ект с1аАдепЪ (Агент ЦРУ) является экземпляром класса Alias
ЗесгеЬА депЬ (Секретный агент). RealName
Password
Р е а л ь н о е им я ( R e a l N a m e ) : " Х е р б Дж онс"
AgentGreetingO
П севдоним ( A l i a s ) : "Даш Мартин"
Пароль (P assw ord ): "ворона л е т а е т в полночь"

© У агента Джонса есть план, как избежать агентов КГБ. Он EnemyAgent


добавил метод A g e n tG r e e tin g (), параметром которого яв­
ляется пароль. Н е получив правильного пароля, он называет Borscht
только свой псевдоним —Даш Мартин. Vodka

ContactComradesO
OverthrowCapitalistsO
e Кажется, что это вполне надежная защита, не так ли? Если
объект, вызывающий метод, не «знает» правильного пароля,
он не «узнает» настоящ ее имя агента Джонса.

А гент К-Г5 использцет


Объект
экземпляром V—__'"это
это ^ качестве привет ст и
yA^e^t. ^ неверный пароле,.
Адепьагееь1пд("джип
A a e n tG : припаркован с н а р у ж и ")

"Д а ш М а р т и н "

Поэтому он узнает только псевдоним агента ЦРУ.

208 глава 5
инкапсуляция

НА САМ О М ли ДЕДЕ защищено поле realName?

Итак, мы закончили на том, что не знгш пароля, агент КГБ не смо­


жет узнать настоящее имя агента ЦРУ. Теперь посмотрим на объяв­
ление поля realN am e:

Модификатор
public означает, piiblic st ri ng RealName;
что переменная
доступна для
редактирования
извне класса.

А поле-то осталось
в общем доступе...
Так зачем мне тогда
пароль? Я могу узнать
имя и без него! name = ciaA g e n t . RealName;
s tr in g

для npocMoiMpa. об ъект каЬАдепЬ не


может «бидеть» за ­
крытые поля объек-
Для сохранения тайны агенту Джонсу нужно использовать поля ^
принадлежит другому
за к р ы то го д оступ а. Стоит объявить поле realN am e с модифика­ классу.
тором private, единственным способом узнать информацию станет
вызов метода имеющих доступ к закрытым элеметам класса. И агент
КГБ останется с носом!
Модификатор private
, гарант ирует , что
внешний код не сможет
Заменив public отредактировать
ка private, вы -^private s t ri ng realName; значения полей без
скроете поле от вашего ведома.
онешнего Мира.

ябляется поле, о к р
хранится пароль.

дальше > 209


сохранить секрет

Закры тые поля и методы доступны только изнутри класса


Существует всего один способ доступа к информации, хранящейся в за­ за-
Т^Т'»Ч-.ТТ'КТУ‘ ГТ/~^ тг с т V • т л ^ ^т т гч т т т »-».г-» ________ __________ ___ ________
крытых полях: использование полей и методов общего доступа, воз­
вращающих значение. Но если агентам КГБ и МИ5 требуется метод
A g e n tG r e e tin g (), коллеги из ЦРУ могут видеть все что угодно —лю­
бой класс им еет доступ к закры ты м полям лю бого своего экземпляра. После закры тия
д о ст ум к полям это
единственный способ
Объект m isaqent для m iSA gent узнать
‘принадлежит настоящее имя
классу BritishAqent объекта ciaAgent.
поэт ому он m L e
не имеет доступа
'^^^_^'^Рытым полям
объекта ciaAgent.

f
Их может видеть
только другой "Херб Джонс
CiaAgent.

_ Чаапо
^аД аБ аеМ ы е
БоГ^осы
из нескольких дюжин чисел, которые гарантируют,
Что произойдет, еспи класс с закрытыми по­ что метод Next () всегда даст на выходе случай­ Е^цинствен-
лями не даст мне доступа к данным, в то время ное число. Создав новый экземпляр Random, вы
как мне это нужно? не сможете увидеть этот массив. Да это и не нужно. ным способом
Имея доступ, вы могли бы добавлять значения, что
^ ! В этом случае вы не сможете получить доступ привело бы к генерации неслучайных чисел. Поэто­ получения ин­
извне. При конструировании классов нужно гаранти­ му начальные числа должны быть полностью инкап­
ровать доступ для других объектов. Закрытые поля сулированы. формации из
являются важной частью инкапсуляции, но нужно
оставлять удобный способ доступа к данным на слу­
чай, если вдруг возникнет такая необходимость.
^ 5 А почему все обработчики событий объяв­ закрытых по­
ляются со словом private?
лей являются
А зачем запрещать доступ к полям объектам
: Формы в C# сконструированы таким образом,
из другого класса?
что запуск обработчиков событий может осущест­ методы общего
вляться только при помощи элементов формы. Мо­
I Иногда классу приходится отслеживать инфор­дификатор p r i v a t e означает, что метод может ис­
мацию, необходимую для выполнения каких-то опе­ пользоваться только внутри класса. По умолчанию
доступа, кото­
раций, но при этом другим объектам эта информа­ обработчики событий не могут управляться посто­
ция не нужна. Скажем, при генерации случайных ронними формами или объектами. Но нет правила,
рые возвраща­
чисел применяются так называемые начальные это предписывающего. Вы можете щелкнуть два
числа (seeds). О том, как именно происходит ге­ раза на кнопке и указать в объявлении обработчика ют данные.
нерация, мы говорить не будем, достаточно знать, событий модификатор p u b l i c . И код все равно бу­
что каждый экземпляр R andom содержит массив дет компилироваться и запускаться.

210 глава 5
инкапсуляция

-Jo3bMH в руку карандаш


Перед вами класс с закрытыми полями. Обведите операторы,
которые не будут компилироваться, если их запустить извне
класса, используя экземпляр объекта mySuperChef.

class SuperChef
{
public string cookieRecipe;
private string secretlngredient;
private const int loyalCustomerOrderAmount = 60;
public int Temperature;
private string ingredientSupplier;

public string GetRecipe (int orderAmount)


{
if (orderAmount >= loyalCustomerOrderAmount)

else

return cookieRecipe;

1. string ovenTemp = mySuperChef.Temperature;

2. string supplier = mySuperChef.ingredientSupplier;

3. int loyalCustomerOrderAmount = 94;

4. mySuperChef.secretlngredient = "кардамон";

5. mySuperChef.cookieRecipe = "3 яйца, 2 1/2 чашки муки, 1 ст. л. соли,


1 ст. л. ванили и 1.5 чашки сахара смешать. Выпекать 10 минут при
температуре 190 °С. Приятного аппетита!";

6. string recipe = mySuperChef.GetRecipe(56);

7. Какое значение будет иметь переменная recipe после запуска всех этих строк кода?

дальше > 211


простор для воображения

^озьми в руку карандаш


Решение Вот какие операторы не будут компилироваться, если за­
пустить их извне класса, используя экземпляр объекта
mySuperChef.
c la ss SuperC hef
{
p u b lic s t r i n g c o o k ieR ec ip e;
p r iv a te s tr in g se c r e tln g r e d ie n t;
p r i v a t e c o n s t i n t lo y a lC u sto m e r O r d e rA m o u n t = 60;
p u b lic i n t T em peratu re;
p riv a te str in g in g r e d ie n tS u p p lie r ;

p u b lic strin g G etR ec ip e (in t orderA m ou n t)


{
if (orderA m ount >= l o y a l C u s t o m e r O r d e r A m o u n t )

retu rn c o o k ie R e c ip e + + se c r e tln g r e d ie n t;

e lse

retu rn c o o k ieR ec ip e;
Единственным способом получения
секретного компонента ябляется
заказ всех ингредиентов. Виешнии
код не им еет непосредственного
доступа к эт ому польо.

Попытка присвоить
■‘^ ^Р^менную типа
C V ^ rin g ovenTemp = m ySu perC hef . T e m p e r a tu r e ;
переменной
типа string
^ ^ ^ T itr in g s u p p l i e r = m ySuperC hef . i n ^ e d i e n t S u ^ T T e r 7

Строки 2. м 4 не будут ко м ­
3. i n t lo y a lC u sto m e r O r d e rA m o u n t = 54;
V пилироваться из-за закры-

5. m y S u p e r C h e f . c o o k i e R e c i p e = "3 яйца, 2 1/2 чашки муки, 1 ст. л. соли,


1 с т . л . в а н и л и и 1 . 5 чашки с а х а р а с м е ш а т ь . В ы п е к а т ь 1 0 м и н у т п р и
температуре 190 °С . П риятного а п п е т и т а !» ; ^^^и локальная
6. s t r i n g r e c ip e = m y S u p e r C h e f.G e tR e c ip e (5 6 );

/. Какое значение будет иметь переменная r e c i p e после запуска всех этих строк кода^Ту

3 яйца, Z 1 / Z чаш ки м у к и , 1 с т . л. сол и , 1 с т . л. ванили и 1 . S чаш ки с а х а р а с м е ш а т ь .

.......м и н у т п р и т е м п е р а т у р е Х йО °С. П ри я т н ого а п п е т и т а !

212 глава 5
инкапсуляция

Ничего не понимаю. Закрытое поле не


позволяет другом классу использовать себя. Но если
поменять private на public програллма все равно будет
построена! А вот добавление модификатора private
делает компиляцию невозможной. Зачем мне тогда
вообще его использовать?

Потому что иногда возникает необходимость


скрыть некую информацию от остальной части
программы.
Большинство пользователей поначалу не могут при­
нять идею инкапсуляции, так как не понимают, зачем
нужно скрывать поля, методы или свойства одного
класса от другого. Но постепенно вы поймете при­
чины, по которым некоторые классы имеет смысл
делать невидимыми для остальной части программы.

инкапсуляция делает классы...


-к Легкими в применении
Вы уже знаете, что поля нужны для отслеживания данных. И боль­
Инкапсуляция
шинство из них применяет методы для обновления информации —
эти методы не нужны никакому другому классу. Часто поля, методы
означает скры­
и свойства одного класса совершенно не нужны в других частях
программы. Пометив их словом private, вы уберете их из окна тие инфор­
IntelliSense.
★ Легкими в управлении
мации одного
Вспомните программу Кэтлин? Проблема возникала потому, что
форма имела непосредственный доступ к полю. Если бы поле было
класса от дру­
закрытым, программа работала бы правильно. гого. Она помо­
•к Гибкими
Иногда по прошествии времени возникает необходимость внести гает предотвра­
в программу изменения. С хорошо инкапсулированными классами
не возникает вопросов по их дгшьнейшему использованию. тить появление
ошибок.

ШTVP»Л
Как плохо инкапсулированные классы могут помешать
редактированию программы в будущем?

дальше > 213


путаница у майка

Программе Майка не помешала бы инкапсуляция


Геокэшингом называ­
ется игра, в которой
Помните программу Майка из главы 3? Майк увлекся геокэшингом и наде­ игроки иш,ут т айни­
ется на помощь навигатора. Он давно не обновлял программу и сейчас ис­ ки, с помощью аР5
пытывает проблемы. Класс Route в его программе хранит маршрут между определяют их коор­
двумя точками. Но Майк не помнит, как им пользоваться! Вот что получа­ динаты и сообщают
ется при попытках редактировать код: о Интернете.

Свойству StartPoint были присвоены координаты дома Майка,


а свойству EndPoint —координаты офиса. Свойство Length пока­
зало, что расстояние равно 15.3. Но метод GetRouteLength () вер­ Не могу вспомнить,
нул Ов качестве результата.
мне требовалось поле
Свойству SetStartPoint О были присвоены координаты StaгtPoint или метод
дома, а свойству SetEndPoint () - координаты офиса. Метод SetStaгtPoint(). Но раньше
GetRouteLength () вернул значение 9.51, в то время как свойство
Length показало расстояние 5.91.
все работало!
У
ПpипoпыткaxиcпoльзoвaтьcвoйcтБaStartPointиSetEndPoint {)
метод GetRouteLength () всегда возвращал О, такое же значение
показывало свойство Length.
Попытавшись взять для задания начальной и конечной точек
метод SetStartPoint () и свойство EndPoint соответствен­
но, Майк увидел, что свойство Length имеет значение О, а метод
GetRouteLength () приводит к сообщению об ошибке... что-то про
невозможность деления на ноль.

^ ^Возьми в руку карандаш


V Route
Вот объект Route из программы Майка. Какие свойства и методы
вы бы пометили как private, чтобы облегчить их использование?

StartPoint
EndPoint
Length
GetRouteLengtliO
GetStartPointO
GetEndPointQ
SetStartPointO
SetEndPointO
ChangeStartPointO
ChangeEndPointQ
Есть много потенциально правильных способов решения этой задачи!
Запиш ите лучшие.

214 глава 5
инкапсуляция

npegcmaBuM объект ß Buge «черного ящика» О ткры в через некоторое врем я


старую програм м у, вы в ряд ли
Иногда можно услышать, как программисты называют объекты вспом ни те, как пред пол агал ось
«черным ящиком». Примерно так все и выглядит. При вызове ме­
и спол ь зов ать те или ины е п е р е ­
тодов объекта вы вряд ли заботитесь о том, как именно они рабо­
м енны е. В от т у т и нкапсул яц ия
тают, по крайней мере, не на текущем этапе обучения. Вам пока
м ож ет си л ь н о обл егчи ть вам
нужно, чтобы метод взял указанные вами значения и выдал нуж­
ный результат ж изнь!

Мой объект Route


работает! Но вот как его
сейчас приспособить
Когда 6 главе 3 Майк создавал
навигатор, он хорошо знал,
его для геокэшинга? как работает одьект Route.
Но прошло врет ...

ЧУ
МйЫК цспешно ислоле?зобйЛ
Правильно н Х г ^ т о р и т еп ер . он хочет
«лЙидо^7Н0 исполмоба!!!^
инкапсулировав объект Route.

классы сегодня, V

вы облегчите себе Если 5tp! Майк вспомнил об


инкапсуляции при ^озЗянии
объекта Route, сегодня у
работу с ними него не болела 6t>i голова.

завтра.
Майк хочет воспринимать
объект Route как «черный
ящмк». Просто указывать
координаты, а о от вет
s t a r t P o in t
получать длину маршрута.
И сходная точка Его не волнует, как именно
объект Route вычисляет
эт у длину.

L ength
Длина марш рута

End P o i n t
Конечная точка

дальше * 215
как лучше провести инкапсуляцию

Получается, что
инкапсулированный класс делает ровно
то же, что и неинкапсулированный.

Естественно! Основное отличие инкапсулированного


класса в том, что он предотвращает появление
ошибок и более прост в использовании.
Испортить инкапсулированный класс легко: воспользуй­
тесь функцией поиска с последующей заменой, чтобы заме­
нить все модификаторы p r i v a t e на p u b l ic .
Как ни странно, после этого программа все равно будет бла­
гополучно скомпилирована. И даже будет работать так же,
как и раньше. Именно поэтому многим пользователям так
сложно понять, зачем вообще нужна инкапсуляция.
До этого момента вы учились тому, как заставить програм­
му вы п олн ять действия. Инкапсуляция же не меняет спо­
соб достижения поставленной цели. Она похожа на игру
в шахматы: скрывая определенную информацию на стадии
создания классов, вы задаете стратегию взаимодействия
этих классов в будущем. Чем лучше будет эта стратегия, тем
более гибкой получится программа, и тем большего коли­
чества ошибок вы избежите.

Как в шакматак существует


множество вариантов кодов, так
и количество стратегий инкапсуляции
является почти бесконечным.

216 глава 5
инкапсуляция

К^ак ираБиЛьНо инкапсули]=‘оБ аш ь КЛассы

Думайте о способах неправильного использования полей.


Ч то может пойти не так, если выбрать неправильный уровень д о сту п а ?

★ Вся ли информация в классе может бы ть открытой?


Если все поля и методы в классе являю тся открытыми, следует более
детально подумать об инкапсуляции.

★ Какие поля будут участвовать в вычислениях?


Они — первые кандидаты на инкапсуляцию. Если позже к программе будет
добавлен метод, использующий значение одного из таких полей, это может
привести к ошибке.

Сначала нужно вычислить


стоимость офор/йления. После
этого останется добавить стоимость
еды и напитков.

★ Открывайте доступ к полям и методам только в случае


необходимости.
Если у вас нет веской причины оставлять общий доступ к полю или методу,
не делайте этого. О ставив доступ ко всем полям, вы только запутаете
программу. Впрочем, не стоит впадать и в другую крайность делать все
поля закрытыми. Правильно вы брав уровни доступа, вы сэкономите время
в будущем.

дальше ► 217
читаем, записываем, получаем результат

инкапсуляция сохраняет данные нетронутыми


Иногда значение поля меняется по ходу выполнения программы. Если программе в явном
виде не сказано возвращать поля в исходное состояние, вычисления делаются с новыми зна­
чениями. В некоторых случаях именно это и требуется, то есть программа должна выполнять
некие вычисления при каждом изменении значения поля. Помните программу Кэтлин, в ко­
торой стоимость мероприятия пересчитывалась при каждом изменении количества гостей?
Мы можем избежать такого поведения, просто инкапсулировав поля. После этого потребует­
ся метод, получающий их значение, и другой метод, который присваивает значения полям и
выполняет все необходимые расчеты.

Пример инкапсуляции
Класс F arm er (Фермер) использует поле для хранения информации о количество коров
(numberOfCows), которое затем умножается на количество мешков с кормом, необходимое
для одной коровы:

Сделаем эт о поле закры т ы м ,


чтобы никт о не м о г пом енят ь
c la ss Farm er
его, не от редакт и ровав при
{ эт о м поле bagsOfFeed (меш ки
p r iv a te in t num berO fC ow s; с корм ом ). Рассинхронизация
эт и х полей приводит
к ошибкам в програм м е!

Ввод пользователем количества коров в форму должен менять значение


поля numberOfCows. Значит, потребуется метод, возвращающий значение
этого поля форме:

p u b lic c o n st i n t F e e d M u ltip lie r = 3 0;


p u b lic i n t G e t N u m b e r O f C o w s ()
Одной к о ­
рове m .pe- r e t u r n num berO fC ow s; Э т о т м е т о д сообщ ает
бует ся , др уги м классам
30 меш коо > количест во коров.
КОрМД- p u b lic v o id S e t N u m b e r O f C o w s ( i n t n e w N u m b e r O fC o w s )
{
num berO fC ow s = newN um berO fC ows;
B a g s O f F e e d = num berO fC ow s * F e e d M u l t i p l i e r ;
М ет од, задаюи^ий число
коров, кот оры й га р а н -
И мена полей ограниченного дост упа т .ирует одновременное
начинают ся со ст рочной буквы, в т о изменение переменной
врем я как у полей обш,его дост упа 8a^sC>fFeec^.
имена начинают ся с прописной буквы.

218 глава 5
инкапсуляция

инкапсуляция при помои^и сВойстВ


Свойства (properties) объединяют функции полей и методов. Они используются для чте­
ния и записи вспом огательного поля (backing field). Именно так называется поле, заданное
свойством.
Закрытое поле numherOfCo^s станет
в ^ м о г а т е л ь н ы м м л е м свойства
p r i v a t e int ni mb er OfC ow S; N u m b erO fC ow s.

in t K u ^ e .0 .c o „ s

{ Метод чтения вызывается каждый раз,

r e t u r n numberOfCows;
} записи

n u m b e r O f C o w s = value;
B a g s O f F e e d = n u m b e r O f C o w s * Fe edMultiplier;

Методы чтения и записи используют так же, как поля. Вот код В этой строчке м е ­
для кнопки, которая задает количество коров, а в ответ получает тод записи задает
количество мешков с кормом: значение закрытого
p r iv a te v o id b u tto n l_ C lic k (o b je c t sen d er, E ven tA rgs e) { поля numberOfCows
и т ем самым обнов­
F arm er m yFarm er = new F a r m e r ( ) ; ляет открытое поле
m yF arm er.N u m b erO fC ow s = 1 0 ;
BagsOfFeed.

in t howM anyBags = m y F a r m e r .B a g sO fF e e d ; Так как мет од записи


' NumberOfCows обно­
m yF arm er.N u m b erO fC ow s = 2 0 ; вил поле BagsOfFeed, вы
можете получит ь его
howM anyBags = m y F a r m e r .B a g s O fF e e d ; ^ значение.

враш,ающий значение 300.

дальше > 219


частная собственность (не входить)

Придо)кение для проВерки класса Farmer


Создайте новое приложение Windows Forms для проверки класса Farmer Л Т ^
и его свойств. Для вывода результатов будет использован метод C o n s o le . У1 1 Р & Ж Н Р Н Ц Р
WriteLineO. -•

О Добавьте к проекту класс Farm er:


c l a s s Farm er {
p u b l i c i n t B agsO fF eed ;
p u b lic c o n st in t F e e d M u ltip lie r = 30;

p r i v a t e i n t nu m berO fC ow s;
p i i b l i c i n t N um berO fC ow s {
(добавьте методы чтения и записи с предыдущей страницы)

О Создайте форму:

Кнопка называемся Присвойте парам е-


«вычислит ь» и ис­ >^рам Value, M inimum
пользует открытые и M aximum элемента
данные класса Farmer NumericUpDown значения
для вывода результат а. 3 0 0 соответ -
ственно.

О Код формы использует метод C o n s o le .W r ite L in e {) для отправки итоговых данных


в окно O u tp u t (это окно вызывается также командой O utput из меню Debug » Windows).
Методу W rite L in e () можно передать несколько параметров и первый - это выводимая
строка. Включив в эту строку “ {О}” вы выведете первый параметр, “ {1} ” - второй пара­
метр, “ {2} ” —третий параметр и т. д.
p u b l i c p a r t i a l c l a s s Form l : Form {
Farm er fa rm er;
p u b l i c Form l 0 { .дйШКоб
In itia liz e C o m p o n e n tO ;
f a r m e r = n e w F a r m e r () { N u m b e r O f C o w s = 1 5 };
}
p r i v a t e v o i d n u m ericU p D ow n l_V alu eC h a n g ed {o b ject s e n d e r , E ventA rgs
fa r m e r .N u m b e ro fC o w s = ( i n t ) n u m e r i c U p D o w n l .V a l u e ;
}
p r i v a t e v o id c a l c u l a t e _ C l i c k ( o b j e c t se n d e r , E ven tA rgs e) {
C o n s o l e . W r i t e L i n e ("I n e e d {o} b a g s o f f e e d f o r {l} c o w s " ,
fa r m e r .B a g s O fF e e d , fa r m e r .N u m b e rO fC o w s);

% [ м е т о д Console.WriteLineO
^ от правляет строчки
WriteLine () замещает
значением первого парамет ра, а {1} -
с текст ом в окно Output. значением второго параметра.

220 глава 5
инкапсуляция
Автоматические сВойстВа
Кажется, наш счетчик коров работает корректно. Запустите программу и щелкните на кнопке для про­
верки. Сделайте количество коров равным 30 и снова щелкните на кнопке. Повторите эту операцию
для 5 коров, а потом для 20 коров. Вот что должно появиться в окне Output:

I need 450 bags of feed for is cows


I need see bags of feed for 30 co»s
I need 150 bags of feed for 5 cows
I need 600 bags of feed for 20 cows
Вы понимаете,
почему это
стало причиной
ошибки?
Но есть небольшая проблема. Добавьте к форме кнопку, которая выполняет оператор:
farmer.BagsOfFeed = 5;

Запустите программу. Все работает до нажатия новой кнопки. Попробуйте после этого нажать кнопку
Calculate. Окажется, что 5 мешков корма требуется для любого количества коров! После редактирова­
ния параметра N u m e r i c U p D o w n кнопка Calculate снова начнет работать корректно.

Полностью инкапсулируем класс Farmer Напечатав


и дважды нажаЬ
Проблема в том, что класс не полностью инкапсулирован. С помощью свойств мы tab, вы добавите
инкапсулировали переменную N u m b e r O f C o w s , но переменная B a g s O f F e e d до сих пор к коду авт ом а­
тическое свой­
общедоступна. Это крайне распространенная проблема. Настолько распространен­
ство.
ная, что в C# существует автоматическая процедура ее решения. Просто замените поле
общего доступа B a g s O f F e e d автоматическим свойством. Вот как это сделать:
/
Удалите поле B a g s O f F e e d из класса F a r m e r . Вместо него введите prop и дважды нажмите
tab. Появится следующая строка кода:
public[xnt] МуProperty { get; set; }

о Снова нажмите tab, чтобы выделить поле M y P r o p e r t y . Введите имя B agsO fF eed :

public int B a g s O f F e e d { get; set; }


Теперь у вас свойство вместо поля. Компилятор обрабатывает эту информацию, как вспомо­
гательное поле.
о Впрочем проблема еще не решена. Для ее решения сделайте свойство доступным только
для чтения:
public int BagsOfFeed { get; p r i v a t e set; }
При попытке построить код вы получите сообщение об ошибке в строчке, задающей
свойство B a g s O f F e e d , — метод записи недоступен. Ведь вы не можете редактировать
свойство B a g s O f F e e d вне класса F a r m e r . Удалите строчку кода, соответствующую второй
кнопке. Теперь класс F a r m e r хорошо инкапсулирован!

дальше ► 221
настройки

Редактируем мно)китель feed


При построении счетчика коров множитель, указывающий количество
корма на одну особь, мы определили как константу. Но представим, что Ф t
нам требуется его изменить. Вы уже видели, как доступ к полям одного У п ]^ а ж н е н и е !
класса со стороны других классов может стать причиной ошибки. Имен­
но поэтому общий доступ к полям и методам имеет смысл оставлять
только там, где это необходимо. Так как программа никогда не обновляет
F e e d M u ltip lie r , нам не требуется запись в это поле из других классов.
Поэтому сделаем его свойством доступным только для чтения, которое ис­
пользует вспомогательное поле. Свойство
гательное поле .ег
Метод записи отсутствует,
Удалите строчку то есть оно доступно толь
ко для ч т е н и я . Метод чт ет я
p u b l i c c o n s t i n t F e e d M u lt ip lie r = 30; при этом открыт, то есть
значение поля
Воспользуйтесь комбинацией prop-tab-tab, чтобы добавить свойство, можно прочитать из людого
доступное только для чтения. Но вместо автоматического свойства другого класса.
добавьте вспомогательное поле:
p r iv a te in t fe e d M u ltip lie r ;
p u b lic i n t F e e d M u ltip lie r { g e t { retu rn fe e d M u ltip lie r ; } }

J
Так как вместо константы общего доступа у наст кры т ое поле
типа intj его имя теперь начинается со строчной буквы г.

0 Запуск кода после внесения в него изменений покажет абсурдный результат. Свойство
B agsO fFeed всегда возвращает Омешков.
Дело в том, что переменная F e e d M u lt ip l ie r не была инициализирована. Поэтому она по
умолчанию имеет значение ноль. Добавим инициализатор объекта:
p u b lic F o rm lО {
I n itia liz e C o m p o n e n t();
f a r m e r = n e w F a r m e r {) { N u m b e r O fC o w s = 1 5 , fe e d M u ltip lie r =30 };

Теперь программа не компилируется! Вот как выглядит сообщение об ошибке:

Error List ’ OX
0 1 Error -li 0 Warnings {! i) 0 Messages

Description File Line Column Project *


01 'Cow_Cakulator.Farmer,feedMu{tiplier' is Forml.cs 18 56 Cow Calculator (N
inaccessible due to its protection level T

Дело в том, что инициализатор объекта работает только с открытыми полями


и свойствами. Что же делать, если требуется инициализировать закрытые поля?

222 глава 5
инкапсуляция

Конструктор
Итак, вы уже убедились, что с закрытыми полями инциализатор объектов не Чтобы снабдить
работает. К счастью, существует особый метод, называемый конструктором класс конструкто­
(constructor). Это самый первый метод, который выполняется при созда­ ром, добавьте метод,
нии класса оператором new. Передавая конструктору параметры, вы указыва­
ете значения, которые требуется инициализировать. Но этот метод не имеет
имеющий имя класса
возвращаемого значения, так как напрямую не вызывается. Параметр пере­ и не имеющий воз­
дается оператору new. А как вы уже знаете, этот оператор возвращает объект, вращаемого значения.
поэтому конструктору возвращать уже ничего не нужно.

Добавление конструктора к классу Farmer


Требуется добавить всего две строчки кода, но как много они значат. Как вы помните, в классе
должны присутствовать данные о количестве коров и мешков корма на одну корову Добавим эту
информацию к конструктору в качестве параметров. Для переменной f e e d M u l t i p l i e r требует­
ся начальное значение, так как она более не является константой.

О т сут ст вие после риЫ,с


int или других обшвлении типа связано с т ем ,
Ключевое слово ^ цто конст рукт ор не возвращает значении.
this в кон-
струкции this.
public Farmer(Int nxunberOfCows, int feedMultiplier) {
feedMultiplier ^__ this.feedMultiplier = feedMultiplier;
указывает,
Перед вызовом
метода запи -
что вы имеете NuniberOfCows = numberOfCows; си NumberOfCows
в виду поле, ------ — V нужно з а ­
а не одноимен- } Запись в закрытое поле numberOfCows исключает
ный параметр. дальнейший вызов метода записи NumberOrCows.
дать парамет р
Данная же строка гарант ирует его вызов. reedMultiplier.

Эт о сообще-, 2 Errors 0 Warnirtgs 0 Messages [ ^


ниє об ошибке
означает, что Description File Line Column Project
оператор new 01 'Cowr_CBkulator.Farme’does not contain Forml.cs 18 22 CowCalcutetor
не имеет п а ­ а constructor that takes § argument
раметров.
0 Настройки яюрмы, необходимые для работы с конструктором
Теперь нужно сделать так, чтобы оператор n e w , создающий объект F a r m e r , использовал кон­
структор вместо инициализатора объекта. После редактирования оператора n e w сообщения об
ошибках исчезнут, и код начнет компилироваться!

public F o r m l О { Так как форма — это тоже


обьект, для нее определен
Init ia liz eC omp on en tO; конст рукт ор с именем F o rm l.
обрат ит е внимание, что он не
farmer = new F a rm er(15, 30) возвращает значение.
^ ------------^ _______ ^
} Мет од new, вызывающий конструктор,
отличается наличием передаваемых кон­
ст рукт ору параметров.

дальше > 223


разбираем конструктор

KoHCHlJ>ljKlIioJ>bi
зіоД м и к р о с к о п о м Внимательно рассмотрим конструктор F a r m e r , чтобы составить
представление о том, как он работает.

К-онструкторы Данный конструкт ор имеет два


не возвращают параметра: количество коров
значения. и количество корма на одну корову.

public Farmer(int пглпЬегО£Cows, int feedMultiplier) {


Так как второй оператор
feedMultiplier; вызывает метод записи
NumberOfCows, для вычисления
NximberOfCows = nxnnberOf Cows ; параметра BagsOfFeed вам
V нужно значение feedMultiplier.
Чтобы от личит ь поле
feedMultiplier от одноименного Так как this означает ссылку на текущий одьект,
парамет ра, мы воспользовались запись this.feedMultiplier является ссылкой на поле.
словом this. Так что сначала мы присваиваем закрытому полю
feedMultiplier второй парам ет р конструктора.

Ч асзцо

ЧаДаБаеМые
Б о Іїр о С ь і
Метод I n i t i a l i z e C o m p o n e n t ( ) вызывается внутри
• Бывают ли конструкторы без параметров? конструктора формы, поэтому инициализация всех элементов
управления происходит в момент создания формы. (Помните, что
Q ; Да. Классы часто снабжены конструктором, не имеющим все формы — не более чем объекты, использующие методы .NET
параметров. И вы уже видели пример — конструктор вашей Frameworl< из пространства имен S y s t e m . W i n d o w s . F o r m s
формы. Посмотрите на его объявление: для отображения окон, кнопок и другох элементов управления.)

p u b lic F o rm lО {
In itia liz e C o m p o n e n t О ;
}
Как разл ич ать од нои м енн ы е
Как видите, конструктор формы действительно не имеет па­ парам етры и поля?
раметров. Откройте файл F o r m l . D e s i g n e r . c s и найдите qae
метод I n i t i a l i z e C o m p o n e n t (), щелкнув на значке + о О р_ о рJ о Ж Н ь ї ! Вы заметили, что параметр
рядом со строчкой «Windows Form Designer generated code».
конструктора f e e d M u l t i p l i e r называется
так же, как вспомогательное поле свойства
Этот метод задает не только начальные значения всех элемен­
F e e d M u l t i p l i e r ? Чтобы использовать в кон­
тов управления формы, но и их свойства. Перетащите на фор­
структоре последнее, не забудьте про ключевое
му новый элемент управления, отредактируйте его свойства слово t h i s ; имя f e e d M u l t i p l i e r указывает на
в окне Properties и обратите внимание на изменения, которые параметр, а запись t h i s . f e e d M u l t i p l i e r на до­
произойдут с методом I n i t i a l i z e C o m p o n e n t ( ) . ступ к закрытому полю.

224 глава 5
инкапсуляция
_ Часзцо
ЧаДаБаеМые
BoTij>oc:i>i

Ql
Зачем нужна процедура создания
методов чтения и записи? Почему
нельзя просто создать поле?

Поля нужны для вычислений или


иных действий. Вспомните о проблеме
O : 0# позволяет не писать информацию,
которая не потребуется компилятору.
Параметр объявляется без ваших явных
указаний. Это не имеет особого значения,
когда вы вводите один или два оператора,
Passw ord,

p u b lic
в которое другие шпионы
могут только записывать информацию, но
не читать ее:

str in g P assw ord {


но если требуется ввести сотню, по­ set {
Кэтлин — после указания количества добный подход реально экономит время if ( v a l u e == s e c r e t C o d e ) {
гостей в классе D i n n e r P a r t y форма и уменьшает количество возможных name = "H erb J o n e s " ;
не запускала метод, пересчитывающий ошибок. }
стоимость оформления. Заменив поле }.
на метод записи, мы гарантируем, что Метод записи всегда имеет единствен­
этот пересчет будет сделан. (Через пару ный параметр v a l u e , тип которого
страниц вы убедитесь в этом!) всегда совпадает с типом свойства. 0# A как же с объектами, которые мы
получает всю необходимую информацию ( оздавали, не создав для них конструк­
о типе и параметре в момент, когда вы на­ тор? Получается, что он нужен далеко
Чем же отличаются просто методы не всегда?
брали “s e t Больше ничего набирать
от методов чтения и записи?
не нужно.
^ ! Нет, это означает, что 0# автома­
Q ; Ничем! Это особые виды методов — тически создает конструктор с нулевым
для других объешв они выглядят как поля То есть я могу не добавлять в кон- параметром, если вы не делаете этого.
и вызываются при записи в поле. Метод ( труктор возвращаемое значение? Вы можете заставить пользователя
чтения в качестве значения возвращает тип создать экземпляр вашего класса, чтобы
поля. А метод записи имеет только один ^ ! Именно так! Конструктор не имеет воспользоваться конструктором.
параметр с именем v a l u e , тип которого возвращаемого значения, так как принад­
совпадает с типом поля. Вместо фомоздких лежит к типу v o i d . Было бы излишне
«метод чтения» и «метод записи» можно заставлять вас набирать v o i d в начале
говорить просто — «свойства». каждого конструктора.

Получается, что в свойство можно


Могу ли я использовать только ме­
превратить ЛЮБОЙ оператор?
тод чтения или только метод записи? Свойства (методы
; Вы полностью правы. Все, что можно
реализовать при помощи метода, можно
Q ; Конечно! Воспользовавшись свой­ чтения и записи) —
ством get без свойства set, вы получите
превратить в свойство. Свойства могут
вызывать методы, получать доступ к по­
свойство только для чтения. Например, это особый вид ме­
класс S e c r e t A g e n t может иметь поле
лям и даже создавать объекты и экзем­
пляры. Но все эти функции реализуются
R e a d o n l y (только для чтения) для
имени (name):
тодов, запускаемых
только в момент доступа к свойству,
поэтому не имеет смысла превращать
в свойства операторы, не имеющие отно­
str in g
p u b lic
name = " D a sh M a r t i n " ;
s t r i n g Name {
при попытке другого
get { r e t u r n nam e; }
шения к процедурам чтения или записи.
} класса прочитать
Если метод записи имеет параметр A если воспользоваться свойством
set без свойства get, вспомогательное
свойство или сде­
v a lu e , почему этот параметр не ука­
зан в с к о б к а х как i n t v a lu e , как это
происходит с другими методами?
поле будет доступно только для записи.
Класс S e c r e t A g e n t создает свойство
лать запись в пего.
дальше * 225
что в имени?

r|Jo3b M H В руку карандаш


Форма использует экземпляр класса c a b l e B i l l (Счет за телевидение) с именем t h i s M o n t h
(Этот месяц) и при нажатии кнопки вызывает метод G e t T h i s M o n t h s B i l l () (Получить счет за
этот месяц). Укажите значение переменной a m o u n t O w e d (Сумма, которую я должен) после вы­
полнения кода.

c la ss С аЫ еВ Ш {
p r iv a te in t r e n ta lF e e ;
p u b lic C a b le B ill( in t r e n ta lF e e ) {
t h i s . r e n ta lF e e ^ r e n ta lF e e ;
d isco u n t = f a ls e ;
}
p r i v a t e i n t p a y P e rV ie w D isc o u n t;
p r iv a te b ool d isco u n t;
p u b lic b o o l D isco u n t {
set {
d isc o u n t = v a lu e ;
i f (d isc o u n t)
p a y P e r V i e w D i s c o u n t = 2;
e ls e
p a y P e r V i e wD i s c o u n t = 0;
}
}
p u b lic in t C a lc u la t e A m o u n t ( in t p a y P erV iew M o v iesO r d ered ) {
retu rn ( r e n t a l F e e - p a y P e r V ie w D is c o u n t) * p ay P erV ie\» M o v iesO rd ered ;
}

Значение
переменной
amountOwed?
1. C a b l e B i l l j a n u a r y = new C a b l e B i l l ( 4 ) ;
M e s sa g e B o x .S h o w (ja n u a r y .C a lc u la te A m o u n t( 7 ) . T o S t r i n g O );

2. C a b l e B i l l f e b r u a r y = new C a b l e B i l l ( 7 ) ; Значение
f e b r u a r y .p a y P e r V ie w D is c o u n t = 1; переменной
M e ssa g e B o x .S h o w (fe b r u a r y .C a lc u la te A m o u n t( 3 ) . T o S t r i n g O ) ; amountOwed?

3. C a b l e B i l l m arch = new C a b l e B i l l ( 9 ) ;
m a r c h .D isc o u n t = tr u e ;
M e ssa g e B o x .S h o w (m a r c h .C a lc u la te A m o u n t( 6 ) . T o S t r i n g O ); Значение
переменной
amountOwed?

226 глава 5
инкапсуляция
Часзцо
'^ а д а В а е М ы е в C# регистр имеет значение. Внутри 4. В некоторых методах, особенно это ка­
Б оП роС Ь ! одного метода можно иметь две пере­ сается конструкторов, имена параметров
менные с именами P a r t y и p a r t y . совпадают с именами полей. В итоге пара­
Почему имена одних полей начи­ Это не помешает компиляции кода. Вот метр маскирует поле, то есть операторы
наются с прописной буквы, а других — несколько советов по выбору имен для методов, которые используют это имя,
со строчной? Это что-то означает? переменных, которые упростят чтение ссылаются на параметр, а не на поле. Эта
программы в будущем. проблема решается при помощи ключе­
0 ; Да, означает. Для вас. Но не для ком­ вого слова th is, достаточно добавить
пилятора. С# все равно, какие имена вы вы­ 1. Имена полей закрытого доступа должны его к имени, и компилятор поймет, что вы
бираете для переменных. Выбор странных начинаться со строчной буквы. имеет в виду поле, а не параметр.
имен затруднит чтение кода в будущем. Вы 2. Имена свойств и полей общего доступа
можете запутаться в одноименных перемен­ должны начинаться с прописной буквы.
ных, все отличие имен которых заключается 3. Имена параметров методов должны
в регистре первой буквы. начинаться со строчной буквы.

Код содержит ошибки. Напишите, в чем, по вашему мнению, они за­


ключаются и как их исправить.

c la ss G u m b allM ach in e {
p r iv a te in t g u m b a lls;

p r iv a te in t p r ice ;
p u b lic in t P rice
■{
get
{
retu rn p r ic e ;

.p u b lic G u m b a llM a ch in e(in t g u m b a lls, in t p r ice

g u m b a lls = t h i s . g u m b a lls;

p u b lic str in g D isp e n se O n e G u m b a ll(in t p r i c e , in t c o in sln se r te d )


{
if ( t h i s .c o in sln s e r te d >= p r i c e ) { // проверка поля
g u m b a lls -= 1;
retu rn "Вот ваш а ж е в а т е л ь н а я р е з и н к а " ;
} e lse {
retu rn "Сумма н е д о с т а т о ч н а " ;
}

дальше > 227


инкапсуляция предотвращает ошибки

^ ^ о з ь м и В руку карандаш
Вот какие значения должна иметь переменная a m o u n t O w e d
'ешение после выполнения кода:
Значение
переменной
1 . C a b l e B i l l j a n u a r y = new C a b l e B i l l ( 4 ) ;
amountOwed?
M e s s a g e B o x . S h o w {j a n u a r y . C a l c u l a t e A m o u n t ( 7 ) . T o S t r i n g () )
Z8
2. C a b l e B i l l f e b r u a r y = new C a b l e B i l l { 7 ) ;
f e b r u a r y . p a y P e r V i e w D i s c o u n t = li- Значение
M e s s a g e B o x .S h o w C fe b r u a r y - C a lc u la t e A m o u n t (3) . T o S t r i n g O переменной
amountOwed?
3. C a b l e B i l l m a r c h = n e w C a b l e B i l l (9);
m a r c h .D isc o u n t = tr u e ; не компилируется
M e ssa g e B o x .S h o w (m a r c h .C a lc u la te A m o u n t( 6 ) . T o S t r i n g O );
Значение
переменной
amountOwed?

42.

Возьми В руку карандаш


Вот какие ошибки содержит код:
'ешение
о тн о с и т с я не к полю, а к п а -
Р^мет рц конструктора. Эт а строчка
присваивает ПАРАМЕТРУ значение, воз­ Ключевое слобо у ,1s
вращаемое методом чтения Price кошп
р ш еще даже не задан! Строчка c m Z m
6 m oвремя как gumballs -
за м ен и ть имя парам е-
^ р а конструктора на Price (с прош еной это параметр.
Э т от парамет р м а -
p u b lic G u m b a llM a ch in e(in t g u m b a lls, in t
i > I скирует закрытое поле
Р Price, в то время как
{ метод должен прове­
g u m b a lls = t h i s .g u m b a lls; рят ь значение вспом о­
p r ice = P rice ; гательного поля price.
}
p u b lic str in g D isp e n se O n e G u m b a ll(in t p r i c e , in t c o in sln se r te d )
{
Ключевое слово this a h is.c o in sln se r te d >= p r i c e ) { // п р о в ер к а поля
не им еет отношения g u m b a lls - 1;
к данному параметру. retu rn "Вот ваш а ж е в а т е л ь н а я р е з и н к а " ;
Оно должно } e lse {
присут ст воват ь retu rn "Сумма н е д о с т а т о ч н а " ;
рядом С парамет ром
price. ^ ‘

228 глава 5
инкапсуляция

Используем полученную информацию, чтобы заставить программу Кэтлин


ненке работать корректно.

Заставим счетчик Dinner Party считать правильно


Исправить ошибку можно при условии, что метод C a l c u l a t e C o stO fD ec o r a tio n s < вы-
зывается при каждом изменении параметра N u m b e r O f P e o p l e .

NumberOfPeople 10;

.CalculateCostOfDecorations()

CmouMocM.t> оформления должна пере-


счимывамься при каждом изменении
количества гостей. '^'’егРоХ^

CalculateCostO возвращает $650 Заставив програм му пересчиты­


вать ст оимост ь оформления при
каждом изменении количества го­
стей, мы гарантируем коррект ную
работу метода CalculateCostQ.
Добавление свойств и конструкторо
Нужно инкапсулировать класс D i n n e r P a r t y . Для начала сделаем параметр N u m b e r C f -
P e o p le свойством, вызывающим метод C a l c u l a t e C o s t O f D e c o r a t i o n s О . Затем нужно
добавить конструктор, гарантировав корректную инициализацию экземпляра. И наконец
останется заставить форму использовать этот конструктор.
★ Создайте свойство для переменной N u m b e r O f P e o p l e , снабженное методом записи,
вызывающим метод C a l c u l a t e C o s t O f D e c o r a t i o n s ( ) . Вам потребуется вспомога­
тельное поле n u m b e r o f P e o p l e .
★ Методу записи N u m b e r O f P e o p l e потребуется переменная, которую он будет пере­
давать в качестве параметра методу C a l c u l a t e C o s t O f D e c o r a t i o n s ( ) . Добавьте за­
крытое поле типа b o o l с именем f a n c y D e c o r a t i o n s , запись в которое осуществля­
ется при каждом вызове метода C a l c u l a t e C o s t O f D e c o r a t i o n s ( ) .
★ Чтобы добавить конструктор, вам нужны три параметра: количество гостей. Healthy
O ption и Fancy Decorations. В момент инициализации объекта D i n n e r P a r t y форма
вызывает два метода, поместите их в конструктор:
d in n e r P a r t y .C a lc u la t e C o s t O f D e c o r a t io n s ( f a n c y B o x .C h e c k e d ) ;
d in n e r P a r ty . S e tH e a lth y O p tio n (h e a lth y B o x .C h e c k e d );

★ Вот как выглядит конструктор для формы. Все остальное остается без изменений:
p u b l i c F o rm lО {
In itia liz e C o m p o n e n tO ;
d i n n e r P a r t y = new D i n n e r P a r t y ( ( i n t ) n u m e r i c U p D o w n l . V a l u e ,
h e a lth y B o x .C h e c k e d , fa n c y B o x .C h e ck ed );
D isp la y D in n e r P a r ty C o st0 ;
}

дальше > 229


решение упражнения

Итак, вот как нужно было написать программу для расчета стоимости
мероприятий:

После закрытия поля


c l a s s D in n erP a rty { nu m be rof People форма не может
c o n s t i n t C ostO fF oodP erP erson = 25; вносить 6 него изменения до пере­
счета стоимости оформления.
p r i v a t e i n t n u m b ero fP eo p le; Оилидка, которая чуть не стоила
p u b l i c i n t N u m b erO fP eo p le { К э т л и н ее лучшего клиента, и с-
get r e t u r n n u m b e rO fP eo p le ; } у^равлена!

i
set
n u m b ero fP eo p le = v a lu e ;
C a lc u la te C o s tO fD e c o r a t i o n s (fa n c y D e c o r a t i o n s )
} При помои^и свойства вы га -
Р ^ ‘^ирует е, что стоимость
p riv a te bool fa n c y D e co r a tio n s; оформления пересчитывается
при каждом изменении коли­
p u b lic d e c im a l C o stO fB e v er a g e sP er P e r so n ; чества гостей.
p u b lic d e c im a l C o s tO fD e c o r a tio n s = 0;

p u b l i c D i n n e r P a r t y {i n t n u m b e r O fP e o p le , b o o l h e a l t h y O p t i o n , b o o l f a n c y D e c o r a t i o n s )
N u m b erO fP eo p le = n u m b e r O fP e o p le ;
t h i s . fa n c y D e co r a tio n s = f ancyD ecorat io n s; ^ будьте внимательны c КЛЮ-
S etH ea lth y O p tio n (h ea lth y O p tio n ); О чевым " словом this. И м ен ­
C a lc u la teC o stO fD ec o r a tio n s(fa n c y D e co r a tio n s); но оно поможет различить
} парамет р и закрытое поле
numberOrPeople.
p u b lic v o id S e tH ea lth y O p tio n (b o o l h ea lth y O p tio n )
i f (h ea lth y O p tio n ) {
C o s t O f B e v e r a g e s P e r P e r s o n = 5 . 0 0 M;
{

Перед fancyDecorations
\
} e lse { нужно помест ит ь кл ю ­
C o s tO fB e v e r a g e s P e r P e r s o n = 20. 00M;
чевое слово tkis.j чтобы
} отличить название з а ­
} крытого поля от назва­
p u b lic v o id C a lc u la teC o stO fD ec o r a tio n s(b o o l fancy) {
ния параметра.
fa n c y D e co r a tio n s = fa n cy ; _____________
_________ Информация об осо­
i f (fan cy) { ^
бом оформлении
C o s t O f D e c o r a t i o n s = (N u m b erO fP eo p le 1 5 . 0 0 М ) + 50М;
должна храниться
} e lse {
C o s t O f D e c o r a t i o n s = (N u m b erO fP eo p le 7 . 5 0 М) + ЗОМ;
в поле, чтобы ею
смог воспользовать­
} ся метод записи
}
NumberOfPeople.
p u b lic d e c im a l C a lc u la t e C o s t ( b o o l h e a lth y O p tio n ) {
d e c im a l t o t a lC o s t = C o s tO fD e c o r a tio n s
+ ( (C o stO fB e v er a g e sP er P e r so n + C ostO fF ood P erP erson ) * N u m b e rO fP eo p le );

if (h ea lth y O p tio n ) {
r e t u r n t o t a l C o s t ■ . 9 5 M;
} e lse {
retu rn to ta lC o st;
}

230 глава 5
6 н а с Л е Д ‘^ ® а н и е

Генеалогическое древо
объектов
Входя в крутой поворот, я вдруг
понял, что унаследовал свой
велосипед от Двухколесного,
но забыл добавить метод Тормоза()...
В итоге двадцать шесть и
и лишение прогулок на целый
месяц.

Иногда люди хотят быть похожим но своих родителей.


Вы встречали объект, который действует почти так, как нужно? Думали ли
вы о том, какое совершенство можно было бы получить, изменив всего
несколько элементов? Именно по этой причине наследование является
одним из самых мощных инструментов с#. В этой главе вы узнаете, как
производный класс повторяет поведение родительского, сохраняя при
этом гибкость редактирования. Вы научитесь избегать дублирования
кода и облегчите последующее редактирование своих программ.
с днем рождения, крошка

Организация дней ро)кдения — это то}ке работа Кэтлин

Созданная нами программа работает, и Кэтлин с ней не рас­


стается. Теперь она организует не только званые обеды, но
и дни рождения, а их стоимость рассчитывается по другой
схеме. Эту схему хотелось бы добавить в программу.

------
Ваша программа сможет
посчитать стоимость дня
рождения?

Эт и пункты
остались без
изменений.

О и е н к а стои м ости дней рождения


• За каждого гостя оплата $25.
• Обычное оформление стоит $7.50 на человека плюс
первоначальный взнос $30. Стоимость особого оформ­
ления увеличивается до $15 на человека, а первона­
чальный взнос — $50.
• Если участников меньше четырех, готовим (8-дюймо­
вый) торт ($40), более четырех — (16-дюймовый) торт
($75).
• Надпись на торте по $.25 за букву. На первом торте по-
^ мешается до 16 букв, на втором — до 40.
Приложение должно рассчитывать стоимость вечеринок
обоих типов. Каждую форму расчета нужно поместить на
свою вкладку.

232 глава 6
наследование

Нам ну)кен класс BirthdayParty


Чтобы программа получила возможность рассчиты­
вать стоимость вечеринок другого типа, нам нужно
поменять форму.
Вы займетесь BirthdayParty
эт им через м инут
оплим и н ут у,
у
Вот как мы это будем делать: ^ а пока предст авим NumberOfPeople
о6иА,ую карт ину. CostOroecorations
CakeSize
Создание класса BirthdayParty CakeWriting
Новый класс будет подсчитывать стоимость, исходя из
выбранного варианта оформления, а также количества CalculateCostOfDecorationsO
CalculateCostO
букв на торте.

О Добавление к форме элемента TabControl


Работать с вкладками просто. Выберите нужную и пере­
тащите на нее элементы управления.

О Перетаскивание элементов управления Dinner Party на первую


вкладку
После этого они будут работать точно так же, как и раньше, просто что­
бы их увидеть, потребуется перейти на нужную вкладку.

О Добавление элементов управления Birthday Party на вторую


вкладку
Вы выберете интерфейс для расчета стоимости дней рождения так же,
как создавали в свое время интерфейс для расчета стоимости званых обе­
дов.
О Связывание нового класса с элементами управления
Вам потребуется добавить ссылку B i r t h d a y P a r ty на поля формы и код
для новых элементов управления.

_ Чаап°
<аДаБаеМые —
Б о їїр о с ь і

>Почему нельзя просто создать экземпляр DinnerParty, что же мне поместить в этот новый класс?
как это делал Майк, когда ему потребовалось сравнить три
маршрута?
О Перед тем как приступить к созданию класса, нужно понять,
какую проблему он будет решать. В данном случае вы должны
^ ! Потому что новый экземпляр класса D i n n e r P a r t y годится поговорить с Кэтлин, ведь это она будет пользоваться програм­
только для расчета стоимости званых обедов. Два экземпляра одно­ мой. Впрочем, у вас есть ее заметки! Определить поля, методы
го класса полезны, когда требуется работать с данными одного типа. и свойства класса можно, продумав его поведение (что он дол­
Но для хранения других данных, вам потребуется другой класс. жен делать) и его состояние (что он должен знать).

дальше > 233


другой тип вечеринок
Поля и свойства, со ­
держащие инф ор­
Планиробщик мероприятий, берсия 2.0 мацию о денежных
суммах, должны
^принадлежать т ипу
Начните новый проект, мы сделаем для Кэтлин версию программы, decimal.
которая сможет рассчитать стоимость дней рождения и званых обе­
дов. Начнем с инкапсулированного класса B i r t h d a y P a r t y , который BirthdayParty
и будет выполнять все расчеты. NumberOfPeople
CostOfDecorations
CakeSize
CakeWriting
-ажнение! CalculateCostOfDecorationsO
CalculateCostO
Новый класс BirthdayParty
Вы уже знаете, что нужно делать со свойством N u m b e r O f ­
P e o p l e и методом C o s t O f D e c o r a t i o n s , вы с ними встреча­
лись в классе D i n n e r P a r t y . Добавим в новый класс сначала их.
★ Добавьте поле общего доступа типа i n t с именем
C a k e S iz e (Размер торта). Потом вы добавите закры­
тый метод C a l c u l a t e C a k e S i z e () (Вычислить размер
торта), присваивающий полю C a k e S i z e значение 8 или
16 в зависимости от количества гостей. Но начнем мы
с конструктора и метода записи N u m b e r O f P e o p l e .

Убедитесь в наличии оператора using


в верхней части класса, так как вам п о ­
требуется метод MessageBox. ShowQ.
c la ss B irth d a y P a rty {
p u b lic const in t C o stO fF o o d P erP erso n = 25;
Для инициализации объекта
B irthdayParty необходимы дан­
p u b lic d e c im a l C o s tO fD e c o r a tio n s = 0; ные о количестве гостей, виде
p r iv a te bool fa n c y D e co r a tio n s;
оформления и надписи на торте.
^ В эт ом случае при вызове метода
p u b lic in t C a k eS iz e ;
/ CalculateCostO стоимость будет
/ правильно рассчитана.
p u b lic B irth d a y P a rty (in t n u m b e rO fP eo p le ,
bool fa n cy D eco r a tio n s, str in g c a k e W ritin g )

th is.n u m b e r O fP e o p le = n u m b e rO fP eo p le ;
Для подсчета стоимости
надписи на торт е конст рук­
t h i s . fa n c y D e co r a tio n s = fa n c y D e co r a tio n s; тор вызывает метод записи.
C a lc u la teC a k e S ize (); Чтобы понять, не является ли
t h i s . C a k eW r itin g = c a k e W r itin g ; эт от парамет р слиилком
большим, сначала нужно у з ­
C a lc u la teC o stO fD ec o r a tio n s(fa n c y D e co r a tio n s);
нать размер торта.

Конст рукт ор задает свойства,


а зат ем начинает вычисления.

234 глава 6
★ Для хранения информации о надписи на торте потребуется свой­ наследование
ство C a k e W r i t i n g типа s t r i n g . Этот метод записи проверяет пара­
метр C a k e S i z e , так как максимальное количество букв зависит от
размера торта. Затем при помощи метода v a l u e . L e n g t h проверяет­
ся длина строки. Если строка оказывается слишком длинной, метод
записи вызывает окно с текстом, «Слитком много букв для 16-дюй- Эт о более сложное свой­
ство. Оно проверяет,
мового торта» (или 8-дюймового).
помест ится ли на т о р ­
★ Кроме того, вам потребуется метод C a l c u l a t e C a k e S i z e (): те строка. Информация
о максимально воз­
можной длине хранится
p riv a te v o id C a l c u l a t e C a k e S i z e () { в переменной maxLengtk.
if ((Numbc^^w.^,.
N u m b e r O f P e o p l e <= 4 ) Если строка слишком
r’o’ Memos O? длинная, появляется со -
C abk-caC
e S-ii zryea
e =— 8Q;. W
задает »^оле записи обш,ение об ошибке, а п о ­
e l s ee т ом вспомогательное
C a k eS iz e = 16; поле обрезается до нуж ­
Ca\cu\ckteCo$tQj- ного размера.
}

p riv a te str in g c a k e W r itin g =


p u b lic str in g C a k e W r itin g {
get { retu rn t h i s . c a k e W r itin g ; } Здесь свойство CakeWriting определяет,
set { помест ится ли строка на торте.
in t m axL ength; Его метод записи проверяет размер
(r;,keqize — 8) торта. Зат ем используется свойство
^ ' Lenath вспомогательного поля, чтобы
m axL ength = 1 6; проверить длину. Слишком длинные
Вы зам е­ e lse строки обрезаются.
тили, что
скобки от ­ m a x L e n g t h = 4 0 ;
сут ст вую т ? i f ( v a l u e . L e n g t h > m a x L e n g t h ) {
Одиночные M e s s a g e B o x . S h o w ("Слишком м н о г о б у к в д л я " + C a k e S i z e + " д ю й м о в о г о т о р т а " )
операторы
i f (m axL ength > t h i s . c a k e W r i t i n g . L e n g t h )
не т ребует ­
ся заключать m axL ength = t h i s . c a k e W r it in g .L e n g t h ;
в скобки. t h i s . c a k e W r itin g = c a k e W r it in g . S u b s t r i n g (0, m axL ength);

} Метод Substring() обрезает строку до ука


e ls e ^ ^ й длины. В?М /онадобится перезагру­
t h i s . c a k e W r itin g = v a lu e; зить надпись в текстовом поле при измене
нии т екста или размера торта.

После

вполне допустима запись. _^v


•, ^^ if (m yValue = = 3 6 )
for ( in t i = О; i < Ш I+ + ) ^ »
- myVflfue S';
poTheJoh(i)i

дальше ► 235
кэтлин это понравится

Продолжим работу над классом BirthdayParty.

Завершим создание класса B i r th d a y P a r ty , добавив метод C a l c u l a t e C o s t O . Но


если в классе D in n e r P a r ty вы брали стоимость оформления и прибавляли к нему
стоимость напитков, теперь нужно будет добавить еще и стоимость торта.

Тип decimal выбран, плак как


мы работаем с деньгами.

p u b lic d e c im a l C a lc u la te C o s tO {
d e c im a l T o ta lC o s t = C o stO fD eco ra tio n s + (C ostO fF oodP erP erson * N u m b e rO fP eo p le );
d e c im a l C ak eC ost;
if (C a k eS iz e == 8)
C akeC ost = 40M + C a k e W r i t i n g . L e n g t h * .2 5 M ; 8 данном случае метод
e lse CalculateCostQ вместо
стоимости напитков
C akeC ost = 75M + C a k e W r i t i n g . L e n g t h . 2 5 М; прибавляет стоимость
r etu rn T o ta lC o st + C ak eC ost; торта.
}

p r iv a te in t n u m b e rO fP eo p le ;
p u b lic i n t N u m b erO fP eo p le { Метод Саке^гШ пд должен не
только обрезать строку, но
get { r e t u r n n u m b e rO fP eo p le ; } ^ и запускать метод записи п^и
set { изменении количества гостей.

\
n u m b e rO fP eo p le = v a lu e;
C a lc u la teC o stO fD e c o r a tio n s(fa n c y D e c o r a tio n s);
C a lc u la teC a k e S ize ();
При изменении коли­
th is-C a k e W ritin g - c a k e W r itin g ; чества гостей класс
^ Подобный метод вы уже сначала пересчитывает
} использовали в классе размер т орт а и, ис­
DinnerParty. ^ пользуя метод запи-
си, заставляет метод
p u b lic v o id C a lcu la teC o stO fD ec o r a tio n s(b o o l fancy) { CakeWriting обрезать
fa n c y D e co r a tio n s = fancy; текст.
if (fan cy)
C o stO fD eco ra tio n s = (N u m b e rO fP e o p le * 1 5 . 00M) + SOM;
e lse
C o stO fD eco ra tio n s = ( N u m b e r O f P e o p l e * 7 . SOM) + 30M;
}

%
236 глава 6
наследование

© Элемент упрааления TabControl


Перетащите на форму элемент управления T a b C o n tro l
и измените его размер. Измените название каждой вкладки
с помощью свойства T abP ages. Редактирование свойств
каждой вкладки осуществляется в отдельном окне диало­
га. При помощи свойства T e x t присвойте вкладкам имена Д д ы те вкладкам
названия при noMouju
D inner Party и Birthday Party соответственно. свойсплва TabCollection.

О Вставка элементов управления на вкладку Dinner


Party banner 2 О
Party
Откройте программу Party Planner (из главы 5) в отдель­ Dinner Party ; Birthday PartTj ^ -^
ном окне ИСР. Выделите элементы управления, скопируй­
те и вставьте на вкладку Dinner Party. Щ елкните внутри Number of People
вкладки, чтобы гарантировать правильное размещение [5 Щ
элементов (в противном случае вы увидите сообщение
ІУІ Fancy Decorations
о невозможности добавить компоненты в контейнер типа
T a b C o n tro l). lII Healthy Option ^

Таким способом вы добавите только элементы управления, Cost i*


N
а не связанные с ними обработчики событий. Нужно так­
же проверить свойство (Name) в окне Properties для каж­
дого элемента. Убедитесь, что все элементы управления /
сохранили имена, а также произведите двойной щелчок Эти элементы управления
на каждом из них, чтобы добавить пустой обработчик со­ доступны только на окладке
бытий. Dinner Party.

Пользовательский интерц)ейс для вкладки Birtliday Party


На вкладке Birthday Party должны присутствовать элемент NumericUpDown для количества
гостей, элемент CheckBox для особого оформления и элемент L a b e l с трехмерной рамкой
для итоговой суммы. Кроме того, потребуется элемент управления T extB ox для ввода над­
писи на торте.

Перейдите на вкладку
■i* Party Planner 2.0 Birthday Party и до­
anner Party Birthday Party. бавьте новые элем ен­
ты управления.
ЭИ.
Number cf People
iv Добавьте элемент управ­
ления TextBox с именем
4»|2! Fm cy Decorations cai<eWrittng для ввода надпи­
си на торте. Свойству Text
Cake W i№ g присвойте значение Нарри
Happy Birthday Birthdau. Эта надпись бу­
дет выводиться по ум олча­
$
J нию.

дальше ► 237
закончим создание формы

Продолжим работу над формой...


О Соединяем все вместе
Все отдельные части уже готовы, осталось только написать код, который заставит форму ра­
ботать.
★ Вам потребуются поля со ссылками на объекты B i r t h d a y P a r t y и D i n n e r P a r t y , ко­
торым будут присвоены начальные значения при помощи конструктора.
★ Код для обработчиков событий, связанных с различными элементами управления
с вкладки Dinner Party, возьмите из главы 5. Вот как должен выглядеть код для формы:

Связанный с формой конструктор п р и -


p u b lic p a r tia l c la ss Form l : F orm { сваивает экземпляру BirthdayParty н а -
D in n erP a rty d in n e r P a r ty ; чальные значения подобно т ом у, как это
B irth d a y P a rty b ir th d a y P a r ty ;
5ьіло сделано для экземпляра Dinnerparty.
p u b lic F o rm lО {
In itia liz e C o m p o n e n t(); М/
d i n n e r P a r t y = n e w D i n n e r P a r t y ( ( i n t ) n u m e r i c U p D o w n l ’. V a l u e ,
h e a lth y B o x .C h e c k e d , fa n c y B o x .C h e ck ed );
D isp la y D in n e r P a r ty C o st0 ;

b i r t h d a y P a r t y = new B i r t h d a y P a r t y ( ( i n t ) n u m b e r B i r t h d a y .V a l u e ,
fa n c y B irth d a y .C h e ck ed , c a k e W r itin g .T e x t);
D isp la y B ir th d a y P a r ty C o st();
}

// Обработчики событий для fancyBox, healthyBox и numericUpDownl


// a также метод DisplayDlnnerCost() аналогичны показанным
/ / в упражнении Dinner Party в конце главы 5.

★ Добавьте код к обработчику событий элемента NumericUpDown, чтобы задать свой­


ство N um berO fPeople и заставить функционировать флажок Fancy Decorations.

p riv a te v o id n u m b e rB ir th d a y _ V a lu eC h a n g ed (o b je ct sender, E ventA rgs e) {


b ir th d a y P a r ty .N u m b e r O fP e o p le = (in t)n u m b e rB ir th d a y .V a lu e ;
D i s p l a y B i r t h d a y P a r t y C o s t () ; Обработчики событий элементов CheckBox
} и NumericUpDown такие же, как и для
/ вкладки dinner party.
p r iv a te v o id fa n cy B irth d a y _ C h eck ed C h a n g ed (o b ject sender, E ventA rgs e) {
b ir th d a y P a r ty .C a lc u la te C o stO fD e c o r a tio n s(fa n c y B ir th d a y . C h eck ed );
D isp la y B ir th d a y P a r ty C o st();
}

238 глава 6
наследование

Откройте страницу Events в окне Properties и добавьте к текстовому полю c a k e W r itin g


новый обработчик событий T e x t C h a n g e d .

jPfoperties I-- П x | Когда вы выделите поле


cakeVJriting и дважды
1 cakeWriting System.Wmdows.Forms.TextBox щелкнете на строчке
TextChanged в списке
Events окна Properties,
7 extAlignChanged ИСР добавит обработ­
T«9stCh3nqed H H cateW ritm g_iextC haii9e<i 1 чик co6MmuUj который
1 Validated r-- .1
будет запускаться при
каждом изменении т е к ­
ста в поле.

p r iv a te v o id c a k e W r itin g _ T e x tC h a n g e d (o b je c t sen d er, E ven tA rgs e) {


b ir th d a y P a r ty .C a k e W r itin g = c a k e W r itin g .T e x t;
D isp la y B ir th d a y P a r ty C o st0 ;

★ Добавьте метод D i s p l a y B i r t h d a y P a r t y C o s t {) ко всем обработчикам событий, чтобы


итоговая сумма автоматически обновлялась при любых изменениях.

СпособJ которым форма об-


! рабатывает надпись на т орт е,
p r iv a te v o id D i s p l a y B i r t h d a y P a r t y C o s t () { о ч е н ь пот ому что Класс
n p o c m j

c a k e W r itin g .T e x t = b i r t h d a y P a r t y . C a k e W r itin g ; BirthdayParty


форма должна всего лишь задать
d e c im a l cost = b ir th d a y P a r ty . C a lc u la teC o st () ; объекта. Bce ост аль-
b ir th d a y C o s t .T ex t = c o s t . T o S tr in g ( "c") ; объект сделает самостоя-
н о е

] тельно.

Способ обработки надписи, количества го­


стей и размера т орт а встроен в методы
записи ЫumberOfPeopie и СакеМпЬ'пд. п о ­
эт ому форма должна всего лишь передать
им нужные значения.

.ВОТв ы и закончили создание ф орм ы !

дальше > 239


работает!

О ТТрогроААма готова!
Убедитесь, что программа работает корректно. Если надпись на тор­
те слишком длинная, должно появляться окно с сообщением. Про­
верьте правильность расчета конечной суммы. Если все функциони­
рует, значит, работа сделана!

Запуст ит е программу и откройте


DmerPaty [ftrthday Pariy.l вкладку Pinner Party. Убедитесь, что
она работает точно т ак же, как
Nuntw erf People и старая программа Party Planner.
12
|ÿ| FancyDecorations
В НеаИтуOption

Q m * I$770.00 Проверим правильность


расчетов для й-О гостей.
Еда $2.5 X й-О =
^ 6 -дюймовый т орт ст оит
$75, обычное оформление:
Перейдите на вкладку Party pTanneJli $7.50 Х'Ю - $75,
Birthday Party. единовременный взнос: $30,
Убедитесь^ ч т о значение 1 Dimer Paify p Brthday Paity буква на т орт е по цене
поля Cost меняется при $.7.5 за букву = $5.25.
изменении количества NtnteofPeopte
гостей и установке [s fel;
флажка Fancy Pecorations.
Fancy Décorions Итого $ 2 5 0 +
Cdîe Wiîing $ 7 5 + $ 7 5 + $30 +
Happy Brthday $ 5 .2 5 = $435.25.
...........J
Все правильно!
Cost 1$328.50

Dinner Bithday Party


При вводе информации
в поле Саке Writing Nwtoerof Ре<^
обработчик событий __ 10 а
TextChanged должен
обновлять значение
поля Cost.
в Ратсу Decwations
СЛе Witing
%
;Happy Birthday Myrtle
Сое* $435.25

240 глава 6
наследование

Дополнительный Взнос за мероприятия с большим


количеством гостей
Благодаря программе дела Кэтлин пошли в гору, и теперь она мо­
жет себе позволить брать дополнительную плату за мероприятия
с очень большим количеством гостей (более 12 человек). Как же до­
бавить к программе еще один платеж?
★ Метод D i n n e r P a r t y . C a l c u l a t e C o s t () должен проверять
значение переменной N u m b e r O f P e o p l e , и если возвращае­
мое значение превышает 12, добавлять $100.
★ Аналогично для метода B ir t h d a y P a r t y . C a lc u la t e C o s t ().

Подумайте, как добавить еще один платеж к классам D i n n e r P a r t y


и B i r t h d a y P a r t y . Какой код следует написать? С какими элемента­
ми этот код должен быть связан?
Кажется, что это просто... но что может случиться при сосущество­
вании трех одинаковых классов? А четырех? А двенадцати? А что
если в будущем вам потребуется отредактировать код? Вы представ­
ляете себе, как трудно менять одшшковьш образом множество род­
ственных классов?

То есть мне придется снова и


снова писать один и тот же код?
Вот спасибо! Может быть, есть
какой-то другой способ?

Вы правы ! П о в торени е од но го и то го ж е кода


в разны х кл ас сах неэф ф екти в но. И к то м у ж е
ув ел и чи в ается в ероя тно сть ош и бок.
К счастью, в C# существует более продуктивный
способ создания связанных друг с другом классов:
наследование (inheritance).

дальше ► 241
не тратьте золото, если нужен только блеск

Наследование
Классы D i n n e r P a r t y и B i r t h d a y P a r t y не случайно имеют одинаковый код. При написании программ
C# часто создаются классы, соответствующие процессам из реального мира, и эти процессы, как прави­
ло, связаны друг с другом. Ваши классы имеют одинаковый код, так как процессы, взятые за их основу
(дни рождения и званые обеды), имеют одинаковые признаки.
Количество
гостей и ст ой-
DinnerParty BirthdayParty Mocmi? оф орм ­
NumberOfPeople ^ ►МитЬеЮГРеорІе ления для дней
CostOTOecorations ......... ►СозЮГОесогаІіопз рождения обраба­
HealthyOption CakeSize
CostOffieveragesPerPerson
тываются п о ч -
К э т л и н в обоих CakeWriting •VI« т ак же, как
случаях нужно Ч CalcuiateCostOTOecorationsO ^ ►CalculateCostOTOecorationsO званые обеды.
рассчитать ( _CalculateCost() ^ ►CalculateCostO
стоимость SetHealthyOptionO
мероприятия

Збаные обеды u дни |)о;кдения отн осятся к п|>иемам


Если существуют несколько классов, являющихся частными случаями другого, более общего класса,
можно заставить их наследовать от этого класса. При этом они становятся классами, производными
(subclass) от базового (base class).

Party Несмотря на сходство,


Numl)eЮfPeople окончательная ст ои­
Количество приглаш ен- , CostOTOecorations мост ь каждого п р и ­
ных и стоимость оф орм ­ ема рассчитывается
ления нужно знать для у^-разноми. Поэтому
приема любого т ипа, п о ­ CalculateCostOTOecorationsO сходные поведения логич­
эт ому данные пармет ры CalculateCostO _______ но пом ест ит ь в базовый
нужно помест ит ь в базо­ класс, а отличные —
вый класс. в производные классы.

Оба производных
С класса наслеЭу-
Стрелка в диа­ ю т процедуру
грамме указы­ DinnerParty расчета ст ои­ BirthdayParty
вает, что класс NumberOfPeople мости оф орм ­ NumberOfPeople
DinnerParty HealthyOption ления от ба­ CakeSize
унаследован CostOffieveragesPerPerson зового класса, CakeWriting
от класса Party. поэт
,, ому
0О них /?она ­
не вклю
CalculateCostQ CalculateCostQ 6z-----------------
SetHealthyOptionO чена.

242 глава 6
наследование

Модель классов: о т общего к частному


Наследование в С# является копией процессов, происходящих в реальном мире. И ерархи я присут­
ствует в виде перехода от общих вещей к частностям. В модели классов классы, расположенные ниже
в иерархии, наследуют от вышестоящих.

О бщ ее О бщ ее
8 модели классов Сыр Лю5ая
А м о ж е т наследовать птица —■
животное,
от Ежедневного
продукта, который, но не любое ^
в свою очередь, животное — ( Ж и в отно е
наследует от класса Еда. птица.

Еж еднев ны й прод укт П ти ца

Сы р Д ля домашнего Певчая птица


питомца достаточно
Класса Певчая
птица, а вот для
орнитолога, изучающего
семейство т,т[с1ае
Ч ед дер недопустимо пут ат ь П ересм еш ни к
северного и ю ж ^го
пересмешников.
В ы держ анны й
верм онтский чед дер С еверны й п ер есм еш н и к

Нижние члены иерархии многое


о т высших. Пересмешники
Ч астное Ч астное
Г м т й ^ т с я и разм нож аю т ся, как
У и все животные.
Если в рецепте указан чеддер,
можно использовать и выдер­
жанный вермонтский чеддер.
Но если вам требуется им ен­
но «В ерм онт », недопустимо Нас-ле-до-вать, глаг.
использовать простой «чеддер иметь признаки родителя
вообще». ^
или предка. Она хочет, чтобы
ребенок унаследовалее большие
карие глаза.

дальше > 243


прогуляємся в джунгли

Симулятор зоопарка
Львы, тигры и медведи... о, боже! А еще бегемоты, волки и случай­
но затесавшаяся кошка. Вам нужно написать симулятор зоопарка.
(Не бойтесь, сам код писать не потребуется, на данном этапе доста­
точно создать диаграмму классов, представляющую всех животных).
Мы получили небольшой список животных, которые должны по­
пасть в программу. Каждому животному будет соответствовать объ­
ект со специфическими свойствами.
Программа должна легко читаться и редактироваться другими про­
граммистами, если позднее потребуется добавить другие классы или
других животных.
с чего же начать? Перед обсуждением отдельны х животных нужно
понять, что они имеют общ его —выделить характеристики, подхо­
дящие всем видам. Именно на их основе будет построен класс, от ко­
торого будут наследовать другие классы.

Что у животных общего


Посмотрите на шесть животных. Как связаны лев, беге­
мот, тигр, кошка, волк и далматин? Именно эти общие
для всех свойства лягут в основу базового класса.

244 глава 6
наследование

Наследование позВоляет избе)кать


дублирования кода В произВодных классах О Построение базового
класса
Так как дублирующийся код сложно редактировать и еще сложнее Поля, свойства и методы базо­
читать, выберем методы и поля для базового класса Animal, кото­ вого класса дадут всем живот­
рые будут написаны т о л ь к о о д и н раз и которые будут унаследованы ным возможность наследовать
всеми производными классами. Начнем с полей общего доступа: общее состояние и поведение.
Логично, что этот класс дол­
★ P i c tu r e : картинка, которую можно поместить в PictureBox. жен называться Animal (Ж и­
★ Food: тип пищи. Пока у этого поля только два значения: meat вотное).
(мясо) и grass (трава).
★ H unger: переменная типа i n t , показывающая, насколько жи­
вотное хочет есть. Она меняется в зависимости от количества
выданного корма.
★ B o u n d a rie s : ссылка на класс, в котором хранится информация
о высоте, длине и расположении вольера. A n im al
★ L o c a tio n : координаты X и Y, описывающие местоположение Picture
животного. Food
H unger
Кроме того, в классе Animal присутствуют четыре метода, которые Boundaries
могут быть унаследованы: Location
★ M akeN oise ( ) : метод, позволяющий издавать звуки.
★ E a t {) : поведение при получении предпочитаемого корма.
★ S le e p ( ) : метод, заставляющий животное спать.
★ Roam ( ) : метод, учитывающий перемещения по вольеру.

Можно было сделать


u другой выбор- Напри­
мер, написать класс
Z o o O c c u p a n t j учы ты -
вающий расходы на со­
держание животных,
или класс A t t r a c t i o n ,
показываюш,ий п р и ­
влекательность для
посетителей. Но МИ
остановились на классе
Animal. Вы согласны?

дальше > 245


программистов не кормить

Ж ивотные издают звуки


Свойства и методы из ба­
Львы рычат, собаки лают, а бегемоты, насколько зового класса Ат'та! не обя­
мы знаем, вообще не издают конкретных звуков. зательно использовать
в производных классах в не-
Каждый класс, производный от Animal, унаследует / изменном виде. Вы можете
метод M akeN oise О , но коды этих методов будут { вообш,е их не использовать!
различаться. Когда производный класс меняет по­
ведение унаследованного метода, говорят о п ере­
кр ы ти и (override). _ 'V
W Что каждое животное из клас­
са iAnimal делает по-своему
Ч то Вам ну)кно перекрыть? или не делает вообще?
Все животные едят. Но если собака любит мясо, Что животное из каждого производно­
то бегемоту подавай воз травы. Как может выгля­ го класса делает такого, чего осталь­
деть код подобного поведения? Как собака, так ные животные не делают? Если собака
и бегемот перекроют метод E a t (). Бегемота этот ест собачью еду, то ее метод E a t ( ) дол­
метод заставить потреблять, скажем, 10 кг травы жен перекрыть метод A n im a l. E a t ( ).
за раз. В то время как вызванный для собаки метод Бегемоты умеют плавать, поэтому для
E a t () уменьшит запасы пищи в зоопарке на одну них определен метод Swim ( ), который
банку собачьего корма весом 300 г. в классе Animal вообще отсутствует.

Г
Производный класс на­ ( Сено — это вкусно!
следуем от базового
все его поведения, но вы ^ Я бы съел стог-
можете их отредак­ V. другой прямо сейчас.
тировать. Сено?! Нет, мне
хочется мяса!
О

A n im al
Picture
Food
Hunger
Boundaries
Location W TVPM
Mal^eNoiseO
Для некоторых животных требуется перекрыть методы
Eat()
MakeNoise () и Eat (). А для кого нужно перекрыть метод
SleepO
Sleep О или R o a m O ? И нужно ли это делать вообще? Подумаі^-
Roam()
те также, для каких животных будут перекрываться свойства.

246 глава 6
наследование

Разбиваем )кивотных на группы


«Выдержанный вермонтский чеддер» — это вид сыра, который относится к ежеднев­
но потребляемым продуктам и в свою очередь входит в категорию «еда». Эта последо­
вательность представлена наглядной моделью классов. К счастью для нас, в C # такие
вещи легко сделать. Можно создать цепочку классов, наследующих друг от друга. И вы
получите бс130вый класс F o o d с производным классом D a i r y P r o d u c t , который, в свою
очередь, является базовым для класса C h e e s e , содержащего в себе производный класс
C h e d d a r , передающий свои признаки классу A g e d V e r m o n t C h e d d a r .

Поиск классов, имеющих ллного


общего
Вам не кажется, что волки и собаки во
A n im al
многом похожи? Они относятся к семей­
ству псовых и имеют сходное поведение. Picture
Скорее всего, им придется по вкусу одна и Food
та же еда. И спят они одинаковым спосо­ H unger
Boundaries
бом. Теперь рассмотрим домашних кошек,
Location
тигров и львов? Они одинаково переме­
щаются по месту своего обитания. Навер­ IVIal<eNoise() He добавить ли нам
ное, имеет смысл создать для них базовый EatO
класс Canine (Псовые)
класс F e l i n e (Кошачьи), который будет от которого будут
SleepO
^^яследовать как
производным от класса A n i m a l . Это по­ Roam ()
собаки, так и волки^
зволит избежать дублирования кода.
\

О м класса
Anim al наследу
ются все чет ы­
ре метода, но
перекрываются
только м ет о ­
ды MakeNoiseO
и EatQ.

Вот почему Нй
диаграмме классов
показаны только
эти два метода.

дальше * 247
расширяем объекты

иерархия классов
Конструкция, в которой под базовым классом располагаются про­ Animal
изводные классы, которые, в свою очередь, являются базовыми Picture
для других классов, называется иерархией (class hierarchy). По­ Food
добный подход не только позволяет избежать многократного ду­ H unger
блирования кода, но и делает код намного более читабельным. На­ Boundaries
пример, при просмотре кода симулятора зоопарка, наткнувшись Location
на метод или свойство из класса Feline, вы сразу поймете, что они
имеют отношение к кошкам. Иерархия становится картой, позво­ IVIakeNoiseO
ляющей отследить происходящее в программе. Eat()
SleepO
Roam ()
Завершение построения иерархии
Вам осталось добавить классы Feline и Canine,
и иерархия будет готова.

Класс Fefine перекрывает


метод RoamQ, поэтому Feline
все унаследованные
свойства будут иметь
дело с новым методом,
а не с т ем , который дыл
определен в классе Animal.
V.

s c e KOIMKU двигаются
Wolf MakeNoiseO
поэт а!
мет од RoamO дляны^
общий. Но они п Т MakeNoiseO Волки и соба­
Eat() ки п и т а ю т ­
разному пит аю т ся < MakeNoiseO MakeNoiseO
издают разные s S y i ^ Eat() ся одинаково,
^оэт ом у методы ^ ^ поэт ому ик "
и MakeNoiseO общий метод
Унаследованные ' EatQ помещен
от класса Anim ai в класс Canine
^^Рекрываются.

248 глава 6
наследование

Производные классы расширяют


и-е-рар-^-я, сущ.
базовый ' р а с п о л о ж е н и е групп о д н а
Вы не ограничены методами, которые производ­ ПОД д ругой в с о о т в е т с т в и и
ный класс наследует от базового... впрочем, вы это
уже знаете! В конце концов, вы же уже создавали с и х р ан го м . Президент ком­
классы. А при наследовании класс просто расши­ пании прошел весь путь от
ряется за счет добавления к базовому классу полей,
свойств и методов. Можно легко добавить собакам
курьера до верхов корпоратив-
метод F e tc h О (Принести дичь). Новый метод ной иерархии.
не будет ничего наследовать и ничего перекры­
вать — ведь он определен только для псовых и ни­
как не повлияет на классы Wolf, Canine, Animal, A n im al
H ippo и любые другие.
Picture
Food
H unger
создаем новый обьекм Род Dog s p o t = new D o g ( ) ; Boundaries
Location

вызываем м ем од класса Род s p o t . M a k e N o is e ( ) ;


IVIakeNoiseO
Eat()
SleepO
вызываем м ем од класса Anim al s p o t . Roam ( ) ;
Roam ()

вызываем м ем од класса Canine s p o t . E a t {) ;

Canine
вызываем м ем од класса Canine s p o t.S le e p ();

вызываем м ем од класса Род s p o t . F e tc h Eat()


SleepO

C # Всегда начинает с наиболее индивидуального метода Dog

Перемещаться собаку заставляет всего один метод из класса A n i m a l . А вот


какой из методов M a k e N o i s e ( ) нужно вызвать, чтобы собака залаяла? IVIai<eNoise()
FetchO
Понять это несложно. Методы класса D o g представляют собой действия
всех собак. А методы класса C a n i n e —действия всех псовых. Методы класса
A n i m a l являются описанием поведения, общего для всех животных. Поэто­
му если нужно заставить собаку лаять, C# сначала «заглянет» в класс D o g ,
чтобы найти поведение, присущее именно собакам. Если таковое отсутству­
ет, будет проверен класс C a n i n e , а потом класс A n i m a l .

дальше ^ 249
как низко вы можете паст ь?

Синтаксис наследобания
При наследовании имена производного и базового классов разделя­
ются двоеточием (:). Производный класс получает все поля, свойства
При наследо­
и м етоды базового класса.
вании все поля,
Vertebrate
NumberOlegs
c la ss V erteb rate свойства и ме­
{
p u b lic in t N u m berO fL egs; тоды базового
Eat()
p u b lic v o id E atO
// код, заставляющий есть
{
класса автома­
}
} тически добавля
Класс Bird (Птица) наследует от ются в производ
класса v e rte b ra te (Позвоношое).

Bird c la s s B ir d : '^ T e r t e b r ^ t ^
ный класс.
Wingspan
{
p u b lic d o u b l e W in g sp a n ;
p u b lic v o id F ly O { расш иряет е класс,
добавив в конец его
FlyO // код, заставляющий летать обьявления двоеточие
} и им я базового класса.

p u b lic b u tto n l_ C lic k (o b je c t sen d er, E ven tA rgs e) {


Так как tw e e ty — B ird tw e e ty = new B i r d ( ) ;
э т о экзем пляр
обьект а Bird, он C tw e e ty .W in g sp a n = 7 .5 ;
тшк как класс Bird
и м еет все методы tw e e t y .F ly (); производный по о^нош ени^о
и поля эт ого о б ь ­ к классу V ertebrate, все эк
tw e ety .N u m b e ro fL e g s = 2
екта.

Часто }
t w e e t y .E a t {); У зем пляры Bird имеиут поля
и мет оды , определенные
в классе Vertebrate.

^ а Д а В а е М ы е ______________________________
Бо11р»ос;ь1
Поведение базового класса не только никак не меняется, он даже
Почему стрелка указывает от производного класса к ба­ не знает о появлении производных классов. Методы класса, поля
зовому? Не логичней было бы рисовать ее наоборот? и свойства никак не затрагиваются. А вот производный класс свое
поведение меняет. Все его вновь создаваемые экземпляры полу­
0 ; Это было бы не совсем точно. Заставляя один класс насле­ чают свойства, поля и методы базового класса. И все это происхо­
довать от другого, вы встраиваете это отношение в производ­ дит благодаря единственному двоеточию! Стрелка на диаграмме
ный класс, а базовый остается без изменений. Это имеет смысл, является частью производного класса и поэтому она нацелена
если подумать о происходящем с точки зрения базового класса. на базовый класс.

250 глава 6
наследование

в руку карандаш
Посмотрите на эти модели и объявления классов и обведите
некорректные операторы.

Aircraft c la s s A ir cr a ft {
AirSpeed p u b l i c d o u b le A irS p eed ;
Altitude p u b lic d o u b le A lt it u d e ;
p u b lic v o id T akeO ffO {
p u b lic v o id LandO { . . .
Tal<eOff()
Land()
c la s s F ir eP la n e : A ir c r a ft {
p u b lic d o u b le B u ck etC a p a city ;
p u b lic v o id F illB u c k e tO { ... };
}
FirePlane p u b l i c v o i d F i r e F i g h t i n g M i s s i o n () {
Bucl(etCapacity
F i r e P l a n e m y F i r e P l a n e = n e w F i r e P l a n e ()
new F i r e P l a n e .B u c k e t C a p a c i t y = 5 0 0 ;
A i r c r a f t . A l t i t u d e = 0;
m y F ir eP la n e . T a k e O ff();
FillBucl<et()
m y F ir e P la n e .A ir S p e e d = 1 9 2 .5 ;
m y F ir eP la n e . F il l B u c k e t ();
A ir c r a f t .Land();
}

Sandwich c l a s s S a n d w ic h {
Toasted p u b lic b o o le a n T oasted ;
SlicesOfBread p u b lic i n t S lice sO fB r ea d ;
p u b lic in t C o u n tC a lo r ie sO { • }
}
CountCaloriesO
c l a s s BLT ; S a n d w i c h {
p u b lic i n t S licesO fB a co n ;
p u b l i c i n t A m ou ntO fL ettu ce;
p u b lic i n t A d d S id e O fF r ie sO { • }
}
BLT p u b l i c BLT O r d e r M y B L T O {
SlicesOfflacon
BLT m y S a n d w i c h = n e w B L T ( ) ;
AmountOfLettuce
B L T .T o a sted = t r u e ;
S a n d w i c h . S l i c e s O f B r e a d = 3;
m y S a n d w ic h .A d d S id e O fF r ie s 0 ;
AddSideOfFriesO
m y S a n d w i c h . S l i c e s O f B a c o n += 5 ;
M e s s a g e B o x . S h o w ( "B м о е м с э н д в и ч е "
+ m y S a n d w ic h .C o u n tC a lo r ie s + " к а л о р и й " .);
r e t u r n m y S a n d w ich ;
}

дальше ► 251
я знаю, как заставить летать пингвина...

Возьми в руку карандаш_ _ _ _


56Ш6НИ6
„ Вот какие операторы следовало обвести как неработающие.

Aircraft c la s s A ir c r a ft {
AirSpeed p u b lic d o u b le A irS p eed ;
Altitude p u b lic d o u b le A lt it u d e ;
p u b lic v o id T ak eO ffO { ... };
p u b lic v o id LandO { };
Tal<eOff() }
LandO
c l a s s F ir e P la n e : A ir c r a f t {
p u b lic d o u b le B u ck etC a p a city ;
p u b lic v o id F illB u c k e tO { ... };
}
FirePlane p u b lic v o id F ir e F ig h tin g M issio n 0 {
BucketCapacity
F i r e P l a n s my F i re.P l a n s = n e w F i r e P l a n e 0 ;
F ir e P la n e .B u c k e tC a p a c ity = isbO;
CAirc?afTTS]EItii3 e =
FillBuci(et()
m y F ir e P la n e T r a k e O ff( ) T Э т и оператору
m y F ir e P la n e .A ir S p e e d = 1 9 2 .5 ;
m y F ir eP la n e . F il l B u c k e t (); r r & r
C g\i!ccral:t. Land o 7 D __
имени экзеМ1^лярй
^уР1>еР1ли.е.

Sandwich c l a s s S a n d w ic h {
Toasted p u b lic b o o le a n T oasted ;
SlicesOfBread p u b lic in t S lice sO fB r ea d ;
p iib lic in t C o u n tC a lo r ie sO
} Э т и свойства п р и ­
СоипЮа1опе8() надлежат экзем пляру,
c l a s s BLT : S a n d w i c h { в то врем я как о п е­
p u b lic i n t S licesO fB a co n ;
рат оры пы т аю т ся
вызыоать их по имени
p u b lic i n t A m ou ntO fL ettu ce;
классов.
p u b lic i n t A d d S id eO fF riesO
}
BLT
p u b l i c BLT O r d e r M y B L T O {
SlicesOffiacon
^ L T , j m^San d w i c h = n e w BLT () ;
AmountOfLettuce
B L T .T o a sted = t r u e ;
^ S a n d w ic h .S lic e sO fB r e a d = 3
r r y S a n d w i c h . A d d S i d e O ' f b'r l e s ' () скобки (f
AddSideOfFriesO
m y S a n d w i c h . S l i c e s O f B a c o n += 5 ; \
M ess" a g e B o x .S h o w (" B м о ем с э н д в и ч е
+ m y S a n d w ic h .C o u n tC a lo rie s + " к а л о р и й " .)
andw ich,---------- ----------------------------------- — J '

252 глава 6
наследование

При наследобании поля cßoücmßa и методы базового класса


добавляются к производному...
Наследование является простым, если производно­ c la s s B ir d {
му классу нужны все методы, свойства и поля базово­ p u b lic v o id F ly O {
го класса. / / к о д , заставляю щ ий пти ц у л е т а т ь
}
Здесь Pigeon (го-^
лубь) производный p u b lic v o id L ayE ggsО { . . . };
класс от ßird, п о ­
эт ому ему принаб- p u b lic v o id P reen F ea th ers0 { ... };
л е ж а т все методы }
этого класса — FlyO
(летать?),
(откладывать яйца), c l a s s P ig e o n : B i r d {
Pigeon Preenfeatkers (чм - p u b l i c v o i d Coo 0 { .
СооО ст ит ь перья), }
а также его сод-
ственный метод соо()
(ворковать). c l a s s P e n g u in ; B i r d {
p u b lic v o id S w im O { ... }

...не не все птицы летаю т! }

Что делать, если базовый класс имеет метод, кото­ p u b lic v o id B ird S im u la to r 0 {
рый в производном классе требуется отредактиро­
вать} P ig e o n H a r r ie t = new P i g e o n ( ) ;
У ЗКЗеМУ1АЯр
обьекта Penguin P en g u in I z z y = new P e n g u i n 0 ;
(пингвин). Он
H a r r ie t. F ly O ;
унаследовал м е -
FlyO, и т е ­ H a r r ie t.C o o 0
Как класс Pigeon, так
перь ничто не и класс Penguin на­
Удерживает его I z z y .F ly (); следуют у класса
от полета! Bird, так что оба
они получают м е -
Pigeon Penguin тоды Fly(), LayEggsQ
Соо() Swlm() и PreenFeathersQ.

Но f l лет ат ь!
нйслсЭует о т класса Bird,
У дедняг просто нет выбора'

Гола«“
ШТУРМ
Если бы этот код писали вы, как бы вы избавили пинг­
em. вина от необходимости летать?

дальше ^ 253
перекрытие вручную

Перекрытие Алето0 о 6
Иногда нужно, чтобы производный класс унаследовал не все поведения базо­
вого, а только часть их. Чтобы изменить унаследованное ненужное поведе­
ние, достаточно п ерекры ть (override) метод.

Клю чевое слово virtu a l


Чтобы производный класс получил возможность перекрывать методы, используйте ключевое
слово virtual.

class Bird { Э т о ключевое слово


поклзы ваем , ч т о п р о ­
public virtual void F l y O { изводный класс мож ет
перекры т ь м ет од Р1у().
/ / КОД, заставл$по1Щ 1Й птрщу летать

}
}
о Д обавление одно им енного м етода в производны й класс
Переопределенный метод должен иметь такую же сигнатуру, то есть то же самое возвращае­
мое значение и параметры. В его объявлении используется ключевое слово o v e r r i d e .

class Penguin : Bird { м ет од


S ec ^ производный
класс идентичный м ет од
public override void Flyf) {
/ MessageBox.Show("Пингвины не летают!")

}
Используйте ключевое слово
При перекры т ии си гн а т у­
ра нового м ет ода должна
override для добавления в произво­
совпадать с сигнат урой
исходного м ет ода из базового дный класс методов, замещающих
класса. В случае с м ет одом
Fly эт о означает о т с у т ­
ст вие возвраш,аемого значе­
методы унаследованные. Перекры­
ния и парам ет ров.
вать можно методы, помеченные
в базовом классе словом virtual.
254 глава 6
наследование

Sandwich
Вместо базового класса мо)кно в зя ть один из производных Toasted
SlicesOfBread

Наследование позволяет использовать производный класс вместо базового.


К примеру, если метод R e c i p e ( ) берет объект C h e e s e , в то время как класс CountCaloriesO
A g e d V e r m o n t C h e d d a r наследует от класса C h e e s e , методу R e c i p e () можно
передать экземпляр A g e d V e r m o n t C h e d d a r . В результате метод R e c i p e () будет
иметь доступ только к полям, методам и свойствам класса C h e e s e , но не «увидит»
элементы класса A g e d V e r m o n t C h e d d a r .

BLT
Допустим, у нас имеется метод, анализирующий объекты S a n d w ic h : SlicesOfBacon
AmountOfLettuce
p u b lic v o id S a n d w ic h A n a ly z e r (S a n d w ic h sp ec im en ) {
in t c a lo r ie s = s p e c im e n .C o u n tC a lo r ie s0 ;
AddSideOfFriesO
U p d a te D ie tP la n (c a lo r ie s);
P e r fo r m B r e a d C a lc u la tio n s(sp e c im e n .S lic e sO fB r e a d , sp e c im e n .T o a s te d );

© Методу можно передать объект сэндвич, а можно с беконом, салатом и поми­


дорами BLT (сэндвич). Свойства этого специального мы наследуем от класса
S a n d w ic h :

p u b lic b u tto n l_ C lick (o b jec t sender, E ven tA rgs e) {


BLT myBLT = n e w B L T ( ) ;
S a n d w ic h A n a ly ze r(m y B L T ); Подробно oS э т о м
Mt>i поговорим
} 6 с л е д у ю щ е й гллбе!

По диаграмме классов всегда можно спуститься вниз — ссылочной переменной


можно присвоить экземпляр одного из производных классов. Но движение вверх
по диаграмме классов запрещено.
Значение myBLT м о ж ­
p u b lic b u tto n 2 _ C lick (o b je ct sen d er, E ventA rgs e) { но присвоит ь люоои
переменной класса
S a n d w ic h m y S a n d w ich = new S a n d w ic h ( ) ;
S a n d w c h j т ак как
BLT myBLT = n e w B L T ( ) ; 0 L T — э т о подвид
S a n d w i c h s o m e R a n d o m S a n d w i c h = myBLT; сэндвича.
BLT a n o t h e r B L T = m y S a n d w i c h ; // < --- ЭТО HE КСЙЯ1ИЛИРУЕТСЯ! ! !
}

ка ком пилироват ься не^дудет

дальше ► 255
немного практики

а = б 56
съ лесь „ Ниже показана короткая программа, у которой
Ь = 5 11
отсутствует фрагмент кода! Вам нужно сопо­
а = 5 65
т нт сО О ёщ гииа ставить фрагменты кода (слева) с возможными
результатами. Не все строчки, приведенные
под заголовком «Результат», должны использо­
И нструкции: ваться, хотя некоторые могут использоваться
больше одного раза.
1. З апо л ни те четы ре п роб ел а в коде.
2. С о вм естите ф р агм енты и результаты .

c la ss А { c la ss С : В {
p u b li c i n t iv a r = 7; p x j b l i c ______ s t r i n g m 3() {
p u b l i c ______________ s t r i n g m l ( ) { r e t u r n " C ' s m3, " + ( i v a r + 6 ) ;
r e t u r n " A 's m l,
} ^ о ч к а входа в п р о грам м у, иона
} ^ не п о к й з ы б а е т формы, а т олько
p u b l i c s t r i n g m 2 () { вызывает окно с сообщением.
r e t u r n " A ' s m2, c l a s s M ixedS { ^
} p u b l i c s t a t i c v o i d M a i n ( s t r i n g [] a r g s ) {
p u b l i c ______________ s t r i n g m 3 () { A a = new A O ;
в Ь = new В О Подсказка: К ак
r e t u r n " A ' s m3,
с с = new С ()
следует подум айт е,
} чт о именно эт о
} А а2 = new С О ,- 4 / означает.
str in g q =
c la ss В ; A { Вставьте сюда
p u b l i c __ ___________ s t r in g m l() { t фрагмент (три
r e t u r n " B 's m l, строчки)
} Sy stem .W in d o w s. Form s. M essa g eB o x . S h o w (q );
}
}

Фрагменты q += b . m l O ; Результат:
кода:
q += c . m 2 0 ;
q += a . m3 ( ) ; } A 's m l. A ' s m2, C ' s m3. 6

B 's m l. A ' s m2. A ' s m3.


q += c . m l O ;
q += c . m 2 0 ;
q += c . m 3 0 ;
} A 's m l.

B 's m l.
B ' s m2.

A ' s m2.
A ' s m3.

C 's m 3, 13

q += a . m l O ;
B 's m l. C 's m2. A ' s m3.
q += b . m 2 0 ;
q += c . m S 0 ;
} B 's m l. A ' s m2. C 's m 3, 6

q += a 2 . m l ( ) A 's m l. A ' s m2. C 's m 3, 13


q += a 2 . m 2 ( )
q += a 2 . m 3 ()
} (Не пользуйтесь средств ам и ИСР, нам ного
полезнее будет реш и ть задачу на бум аге!)

256 глава 6
наследование

Возьмите фрагменты кода из бассейна и поместите их на пустые строки. Каж­


дый фрагмент можно использовать несколько раз. В бассейне есть и лишние
фрагменты. Вам нужно создать набор классов, которые будут компилировать­
ся и работать как единая программа. Не обольщайтесь, задача сложней, чем
кажется на первый взгляд!.

class TestBoats {
c l a s s R ow boat ........................................ {

public ........... rowTheBoatO { ........................ Main () { X

re tu rn " s tro k e n a ta s h a " ; .............. Подсказка:


........ Ы - new BoatO ; ЭИЛО М О Ч ~
} Sailboat b2 - new () ; входа
Rowboat ....... =-M6WK0WBcat() ; п р о гр а м ­
му.
b2.setLength(32) ;
xyz = b l . ....... () ;
class ........... { xyz += b3. ....... 0 ;
private int xyz += ...... .moveO;
........... void ........ .) { System.Windows .Forms .MessageBox.Show (xyz) ;
length = len;
}
} }
public int getLengthO { class ............... : Boat {

public ..................... 0 {
return " ................ ";
}
public move() { )
return
}

дальше > 257


немного практики

А съ лесь „
а = 6
Ь = 5
56
11
сО О егг^еН ий а = 5 65
ажнеше c la ss А { c la ss В : А {
)^ешение p u b lic virtual s t r i n g ml О { p u b lic override s t r i n g ml 0 {

p u b lic virtual s t r i n g m 3 () { c la ss С : В {
} p u b lic override s t r i n g m 3() {

Вы всегда можете использовать конкретное вместо q += b . m l ( ) ;


общего, при наличии строчки кода, в которой тре- q += c . m 2 () ;
буется класс C a n i n e , можно создать ссылку на класс q += а . т З О ; A 's m l. A ' s m 2, C 's m3, 6
D o g . Поэтому строчка: -------------
B 's m l. A ' s m 2. A ' s m 3.
А а 2 = new С О ; q += c . m l ( )
q += с . m2 О A 's m l. B ' s m2. C 's m 3, 6
означает, что вы создаете экземпляр С и ссылку из q += с .т З ()
класса А с именем а 2 указывающую на него. Впро­ B 's m l. A ' s m 2. C ' s m 3. 13
чем, имена А, а 2 и С не очень наглядны, поэтому при­ q += a .m lO
ведем несколько примеров со значимыми именами: B 's m l. C ' s m2, A ' s m 3.
q += b.m2 0
S a n d w ic h m y S a n d w ich = new B L T (); q += c.m3 0 A 'S m l. B ' s m2, A ' s m 3.
C heese in g r ed ie n t= new A g e d V er m o n tC h ed d a r ( ) ;
q += a2 .m l( ) ; B 's m l. A ' s m 2, C ' s m3. 6
S o n g b ir d tw e e ty = new N o r t h e r n M o c k in g b ir d ( ) ; q += a 2 .m 2 ( ) ;
q += a 2 . m 3 {); A 'S m l. A ' s m2, C 's m3. 13

eiHeHue j=*e^ca Б бассейне


c l a s s T estB oats {
c l a s s Rowboat^ 8oat { public static void Main(){
public string rowTheBoatO { xyz =
return "strok e natasha";
B oat bl = new B oat();
)
S ailbo at b2 = new . .SAlXfeftat... () ;
)
c la s s Boat { Rowboat ...l?3......= new Rowboat ();

priv a te i n t length ; b 2 . s e t L e n g t h (32);

public void setL ength ( in t len > xyz = b l . M0Ve_ 0 ;


{ xyz += b3. .mp.V'2.. 0 ;
le n g t h = len; xyz += b2 _.move () ;
} System.Windows. Forms.MessageBox.Show(xyz) ,
p u b li c i n t getLengthO { }
retu rn length }
) class ..SAlib.Oat...'- Boat {
public virtual string move{) {
p u blic .override string 0 {
return " d r ift
return " hoist sail ";
)
}
258
)
наследование
_ Часто
ЧаДаБаеМые

В ребусе в бассейне точка входа Можно ли наследовать от класса, 1 ); Почему я могу двигаться по диа­
указывала наружу, означает ли это, содержащего точку входа? грамме классов только вверх?
что в программе отсутствует форма
Forml? ^ ! Да. Точка входа должна быть 0 2 Классы, расположенные в диаграмме
статическим методом, но этот метод сверху, являются более общими. Именно
^ ; Для нового проекта Windows может и не принадлежать к статиче­ от них наследуют более детализиро­
Application, ИСР создает все необходимые скому классу. (Ключевое слово s t a t i c ванные классы (скажем Рубашка или
файлы, включая файл Program.cs (содер­ означает невозможность создания экзем­ Автомобиль могут наследовать от
жащий статический класс с точкой входа), пляров класса, но его методы доступны с классов Одежда или Транспорт). Если
и файл Forml. CS (содержащий пустую момента запуска программы. В ребусе в вам нужен транспорт, вам подойдет как
форму Forml). бассейне метод T e s t B o a t s . M a i n {) автомобиль, так и мотоцикл или даже
можно вызвать из любого другого метода поезд. Но если вам требуется именно
Попробуйте при создании нового проек­ без объявления ссылочной переменной автомобиль, вы не сможете выбирать все
та выбрать вариант Empty Project вместо или создания экземпляров при помощи транспортные средства.
Windows Application. Добавьте файл оператора new.)
с классами через окно Solution Explorer Именно так работает наследование. При
и введите код из решения ребуса в у» я не понимаю, почему эти методы наличии метода с параметром Транс­
бассейне. Так как в программе должно по­ называют «виртуальными», они впол­ порт и при условии, что класс мото­
являться окно диалога, необходимо доба­ не реальные! цикл наследует от класса Транспорт,
вить ссылку на форму. Щелкните правой вы можете передать методу экземпляр
кнопкой мыши на строчке References в Мотоцикл. Если же параметром метода
Q ; Ключевое слово virtual относится
окне Solution Explorer, выберите коман­ является Мотоцикл, вы не сможете
к способам обработки методов в .NET.
ду Add Reference, в открывшемся окне передать объекг Транспорт, так как это
Используется так называемая таблица
перейдите на вкладку .NET и выберите может оказаться поезд, и С# не будет
виртуальных методов, которая отслежи­
строчку System.Windows.Forms. Затем вы­ знать, что делать, при попытках метода
вает какие методы были унаследованы,
берите команду Properties в меню Project получить доступ к свойству Руль.
а какие перекрыты.
и укажите в списке output type вариант
Windows Application.

Запустите программу и посмотрите на


результат! Поздравляем, вы только что
Методам, параме­
создали программу с нуля!
тры которых ра­
Если бллл нужно вспомнить,
ботают е базовым
цто такое метод Maii^O
и точка входа, перечитайте классом, можно
начало главы Z.
передавать экзем­
пляры производпо
го класса.

дальше ► 259
они вам и в самом деле нужны

А я не понимаю, зачем нужны ключевые слова


virtual и override. Если они отсутствуют, ИСР показывает
предупреждение. Но программа все равно запускается! Так какая
разница? Нет, я, конечно, могу писать эти слова, если так делать
«правильно», но кажется, меня просто заставляют выполнять
лишнюю работу.

Е сть важ ная причина!


Ключевые слова v i r t u a l и o v e r r i d e не являются формальностью. Они меня­
ют способ работы вашей программы. Впрочем, мы не заставляем верить нам на
слово, давайте рассмотрим пример.

На эт от раз вместо приложения


/ VJindows Forms будет создано
f консольное приложение! Оно не
Л р^аж нение! им еет формы.

А/
О Создайте консольное п ри л ож ени е, выбрав вариант Console application.
Добавьте пять классов через окно Solution Explorer: J e w e ls (Драгоценности), S a fe (Сейф),
Owner (Владелец), L o c k sm ith (Слесарь) и J e w e lT h ie f (Вор).

О Код для новых классов.


Добавьте следующий код:
c la ss J e w els {
p u b lic str in g S p a rk le 0 { Если при создании нового про-
retu rn " S p a r k le, sp a r k le !" ;
Endow s
Forms application вы§р^ть вариант
Console A pplication, ИСР c o s Z Z
т олько новый класс с именем
метой Program , содержаищй пуст ой ме~
c la ss S afe {
p riv a te J e w els con ten ts = new J e w e l s ( ) ;
Обратите
внимание, p r i v a t e s t r i n g sa fe C o m b in a tio n = "12345"; Z LT Л ониий
что сло­ p u b lic J e w e ls O p e n (s tr in g c o m b in a tio n )
во private р м ь с я в слеЛ)н>и1их главах.
{
скрывает i f ( c o m b i n a t i o n == s a f e C o m b i n a t i o n )
перем ен­ retu rn co n ten ts;
ные contents
и combination. e l s e
retu rn n u ll; Слесарь (Locksmith) м о ­
жет подобрать цифровую
} комбинацию, вызвав метод
p u b lic v o id P ic k L o c k (L o c k sm ith lo c k p ic k e r ) {
PickLockO и передав ему
lo ck p ic k e r .W r ite D o w n C o m b in a tio n (sa fe C o m b in a tio n ) ! ссылку на себя. При п о ­
мощи эт о 1л комбинации
— сейф вызывает свой метод
VJriteDownCombination{).
260 глава 6 %
c la ss Owner { наследование
p r iv a te J ew els retu rn ed C on ten ts;
p u b lic v o id R e c e iv e C o n te n ts(J e w e ls safeC on ten ts) {
retu rn ed C on ten ts = safeC on ten ts;
C o n s o l e . W r i t e L i n e ( "Thank y o u f o r r e t u r n i n g my j e w e l s ! + sa fe C o n te n ts. S p a rk le ());
}
}
О Класс J e w e lT h ie f насл едует от класса Locksm ith. Locksmith
Воры драгоценностей —это бывшие слесари! Они могут подобрать код
и открыть сейф, но вместо того чтобы вернуть содержимое сейфа вла­
дельцу, они крадут его!
c la ss L o ck sm ith {
OpenSafeO
p u b lic v o id O p en S a fe(S a fe safe, Owner o w n e r ) { WriteDownCombinationO
s a f e . P ic k L o c k (th is); Return ContentsO
J e w els safeC on ten ts = sa fe .O p e n (w r itte n D o w n C o r a b in a tio n );
R etu rn C on ten ts (sa fe C o n te n ts, ow ner) ; Метод OpenSafeQ из класса
} Locksmith подбирает ключ,
открывает сейф и возвращает
его содержимое владельца. JewelThief
p r iv a te str in g w ritten D o w n C o m b in a tio n = n u l l ; private stolenJewels
p u b lic v o id W r ite D o w n C o m b in a tio n (str in g c o m b in a tio n ) {
w ritten D o w n C o m b in a tio n = c o m b in a tio n ;
}
ReturnContentsO
p u b lic v o id R e tu r n C o n te n ts(J e w e ls sa feC o n ten ts, Owner o w n er ) {
o w n e r .R e c e iv e C o n te n ts (sa fe C o n te n ts ) ;
}
}
c la ss J e w elT h ie f : L o ck sm ith {
p r iv a te J ew els sto le n J e w els = n u ll;
p u b lic v o id R e tu r n C o n te n ts(J e w e ls sa feC o n ten ts, Owner o w n er ) {
sto le n J e w els = safeC on ten ts;
C o n s o l e . W r i t e L i n e ( " I'm s t e a l i n g th e con ten ts! ' s to le n J e w e ls . S p a rk le ());
} Объект JewelThief насле­
} дует методы OpenSafeQ
и WriteDownCombinationQ. Ho
О М ето д M ain O для класса Program . когда метод OpenSafeQ вызы-
П^иезатгуасайтепроеражчуШолробуйтеугадать, что будет
написано в консоли., блдЭеле?цу> вор их забирает себе!
c la ss Program {

Метод
sta tic

v o i d M a i n ( s t r i n g []
^
args) { возьми 6 руку карандаш
ReadKeyQ не Owner () ;
допускает Safe sa fe = new S a f e O ;
завершения
программы J e w e lT h ie f je w elT h ie f = new J e w e l T h i e f О
Внимательно посмотрите код программы
пока поль­ je w e lT h ie f.O p e n S a fe (s a fe , ovm er);
и запишите сообщение, которое появится
зователь
не нажмет C o n so le.R e a d K ey O ; в консоли. (Подсказка: Определите, какие
клавиилу. } свойства класс J e w e l T h i e f наследует от
} класса L o c k s m i t h ! )

% дальше k 261
скры т ь и обнаруж ит ь

Произбодный класс умеет скрыбать методы


Запустите пpoгpaммyJewelThief. Так как это консольное приложение, вывод результатов осуществляет­
ся через командное окно. Вот как это выглядит:

file;///C:/Usera/aftctraw/Dgcuwents/V№ttai Studio aaiO/Pfojerts/Chapttr 6 соёе/lawei


r h a n k y ou f o r 3*etm inin g je w e ls! S p a rk le , s p a rk le !

Вы ожидали, что программа напишет кое-что другое? Например:


I'm ste a lin g th e co n ten ts! S p a rk le, sp a r k le ! (Я к р а д у д р а г о ц е н н о с т и ! К руто!)

Но кажется, наш вор JewelThief повторяет действия слесаря Locksmith! Как это могло произойти?

Сравнение скрытия иперекрытия методов


Причиной, по которой объект J e w e l T h i e f при вызове метода R e t u r n C o n t e n t s {) ведет себя как объ­
ект L o c k s m i t h , является способ, которым класс J e w e l T h i e f объявил этот метод. Подсказка находится
в предупреждениях, которые посылает приложение:

О О Errors ji ^ 1 Warning О M essages [

Description

A 1 'iewel_Thief J«ive(Thtef.ReturnContentsCJewel_Thief.Jewets, Jewel_Thief.Owner)‘ hides inherited member


'ieweLThief.Locksmith.ReturnContents(jewel_Thief,Jewels, Jewel_Thief.Owner)'. To make the current member
override that implementation, add the override keyword. Othenwise add the new keyword.

Так как класс J e w e l T h i e f наследует от класса L o c k s m i t h


и по идее замещает метод R e t u r n C o n t e n t s () своим соб­
Если имя и сигнатура соз­
ственным, кажется, что этот метод перекрывается. Но на
самом деле это не так. Объект J e w e l T h i e f скрывает метод
даваемого метода совпада­
R e t u r n C o n t e n t s (). ет с именем и сигнатурой
При скрытии производный класс замещает (технически
он «переобъявляет») одноименный метод базового клас­ наследуемых методов, но­
са. В итоге в производном классе оказываются два метода
с одинаковым именем: один унаследованный и один опре­ вый метод производного
деленный самостоятельно.
класса скрывает метод
базового класса.
262 глава 6
наследование

Для бызоВа скры ты х методов используйте различные ссылки


Объект скрывает метод R e t u r n C o n t e n t s (), что заставляет его действовать как объект
J e w e lT h ie f
Одну версию данного метода объект J e w e l T h i e f наследует от объекта L o c k s m i t h , затем
L o ck sm ith .
он определяет вторую собственную версию, и в итоге мы имеем два разных метода с одинаковыми име­
нами. Это означает, что нужны различные способы их вызова.
На самом деле при наличии экземпляра J e w e l T h i e f для вызова нового метода R e t u r n C o n t e n t s О
можно использовать ссылочную переменную J e w e l T h i e f . А если для вызова используется ссылочная
переменная L o c k s m i t h , будет вызван скрытый метод R e t u r n C o n t e n t s { ) .

// Производный класс J e w e l T h i e f скрывает метод базового класса L o c k s m i t h ,


// поэтому вы можете увидеть поведение одного объекта
// в зависимости от того, какой ссылкой он вызывается!

// При вызове объекта J e w e l T h i e f ссылочной переменной L o c k s m i t h вызывает


// метод R e t u r n C o n t e n t s O из базового класса
L o c k sm ith c a lle d A s L o c k s m it h = new J e w e l T h i e f ( ) ;
c a lle d A sL o c k sm ith .R e tu r n C o n te n ts(sa fe C o n te n ts, o w n er );

// При вызове объекта J e w e l T h i e f ссылочной переменной J e w e l T h i e f вызывается


// его собственный метод R e t u r n C o n t e n t s ( ), а одноименный метод
/ / и з базового класса скрывается.
J e w e l T h i e f c a l l e d A s J e w e l T h i e f = new J e w e l T h i e f О ;
c a lle d A sJ e w e lT h ie f.R e tu r n C o n te n ts(sa fe C o n te n ts, o w n er);

Скрывая методы, пользуйтесь ключевым словом new


Внимательно прочитайте это предупреждение. Мы знаем, что предупреждения никто не читает, но на
этот раз сделайте исключение. Чтобы осуществить текугцую реализацию, добавьте ключевое слово
override. Если предполагается сокрытие, используйте ключевое слово new.
Вернемся в программу и добавим ключевое слово new.
n ew p u b l i c v o i d R e t u r n C o n t e n t s ( J e w e l s s a f e C o n t e n t s , Owner o w n e r ) {

Сразу же после этого сообщение об ошибке исчезнет. Хотя программа все равно не станет работать так,
как нужно! По-прежнемувызывается метод R e t u r n C o n t e n t s ( ) , определенный для объекта L o c k s m i t h .
Почему так происходит? Дело в том, что вызов этого метода осуществляется через метод, определенный
в классе Locksmith, а именно через L o c k s m i t h . O p e n S a f e (), несмотря на то, что его начальные значения
были заданы в классе J e w e l T h i e f . Если бы J e w e l T h i e f просто скрывал метод R e t u r n C o n t e n t s () из
базового класса, его собственный метод R e t u r n C o n t e n t s () никогда бы не был вызван.

П о д у м а й т е , к а к з а с т а в и т ь о б ъ е к т J e w e lT h ie f п е р е к р ы т ь , а н е с к р ы т ь м е т о д
R e tu r n C o n te n ts O ? С м о ж е т е д о г а д а т ь с я д о т о го , к а к п е р е в е р н е т е с т р а н и ц у ?

дальше > 263


так вот зачем нуж ны эт и клю чевы е слова

Ключебые сдоба override и virtual


Нам требуется, чтобы класс J e w e l T h i e f всегда использовал свой собственный метод R e t u r n ­
C o n t e n t s О , вне зависимости от способа вызова. Именно так должен работать механизм наследова­
ния, и именно это называется перекры тием . Вы можете легко заставить класс это сделать. Для начала
воспользуйтесь ключевым словом o v e r r i d e при объявлении метода R e t u r n C o n t e n t s ( ) :

class JewelThief {

override public void ReturnContents


(Jewels safeContents, Owner owner)
Ho этого недостаточно. Попытавшись скомпилировать программу, вы получите сообщение об ошибке:

Error List П X
O 1 Error I j \ 0 Warntngs j ij) 0 Messages

, Description
.... . . , .. ..............
O 1 'ieweLThief.JewelThief.ReturnCor?tetitsPewel_Thief.JwelSi Jewel_Thief,Owner}'; cannot override irtherited member
'Jewel_Thief.Locksmith,ReturnContentsOewetThief Jewete, iewel_Thief.Owner)’ because it is not marked virtual, abstract, or override

Сообщение предупреждает, что объект J e v ^ e l T h i e f не может перекрыть унаследованный метод


R e t u r n C o n t e n t s {) из-за отсутствия ключевых слов v i r t u a l , a b s t r a c t или o v e r r i d e в объявлении
класса L o c k s m i t h . Эту ошибку легко исправить! Используйте в объявлении метода R e t u r n C o n t e n t s ( )
в классе L o c k s m i t h ключевое слово v i r t u a l .

class Locksmith {

virtual public void ReturnContents


(Jewels safeContents, Owner owner)

Теперь, запустив программу, вы увидите следующее:

ijifile;///C;/Users/andrew/Documents,/V'suat Studio 2010/Projects/Chepter 6 code/Jewel Thief/bin/De... Г с з^ Г


in g t h e c o n t e n t s ! S p a s'k le , s p a r k l e ?

Этого мы и добивались!

264 глава 6
наследование

Чтобы перекрыть метод


базового класса, всег­
да помечайте его клю­
Именно так. В большинстве случаев чевым словом virtual.
методы требуется перекрывать,
но имеется и возможность скрыть их.
И всегда используй­
Работая с производным классом, который явля­
ется расширением базового, вы, скорее всего,
те ключевое слово
будете использовать перекрытие методов. По­
этому, если вы заметили, что компилятор пред­
override, когда хотите
упреждает о скрытии методов, не оставляйте
это без внимания! Подумайте, действительно
перекрыть метод в
ли вы хотите скрыть метод или, может быть,
вы просто забыли написать ключевые слова
производном классе.
v i r t u a l и o v e r r i d e . Корректное использова­
ние ключевых слов v i r t u a l , o v e r r i d e и new Если это не сделать,
позволяет избежать проблем, с которыми вы
столкнулись в последней программе! некоторые методы вне­
запно могут оказаться
скрытыми.
дальше ► 265
обходнойпуть

Ключебое сдобо base


Иногда возникает необходимость доступа к перекрытым методам или свой­ Vertebrate
ствам базового класса. К счастью, существует ключевое слово b a s e , дающее МитЬеЮЯ-едз
доступ к любым методам базового класса.

Q Все животные едят, поэтом класс V e r t e b r a t e (Позвоночные) дол­


жен иметь метод E a t (), в качестве параметра которого используется Eat(j
объект Food (Еда). SwallowO
DigestO
c la ss V erteb rate {
p u b lic v ir tu a l v o id E at(F ood m o r sel) {
S w a llo w (m o r se l);
D ig e s t ();

© Хамелеоны ловят пищу языком. Поэтому класс C ham eleon наследует от класса V e r t e b r a t e ,
переписывая при этом метод E a t ().
c la ss C h am eleon : V erteb rate {
p u b lic o v e r r id e v o id E at(F ood m o rsel) {
C a tch W ith T o n g u e(m o rsel);
S w a llo w (m o r se l)
D ig e s t ();
}
}

О Воспользуйтесь ключевым словом b a s e для вызова перекрытого метода и вы получите доступ


как к новой, так и к старой версии метода E a t ().
c l a s s C h am eleon : V e r t e b r a t e {
p u b lic o v e r r id e v o id E at(F ood m o r sel) {
C atch W ith T on gu e (m o r s e l) ; строчка вызывает м е -
b a se .E a t(m o r se l); -------" mod Eat 0 w3 базового класса,
^ om которого ипгАРМет
наследует объект
} Chameleon.

Вы получили некоторое представление о процедуре наследования, но можете ли вы расска­


зать, каким образом наследование облегчает редактирование кода в будущем?

266 глава 6
наследование

Если В базоВом классе присутствует конструктор, он дол)кен остаться


U В производном классе добавь^
Если в классе присутствуют конструкторы, то все классы, кото- нец обьявлеш ^ ‘^‘^Р оку в ко-
рые от него наследуют, д о л ж н ы в ы з ы в а т ь х о т я б ы о д и н и з э т и х ра npj}u36adH ozo°i^^^'^‘^ ° ~
к о н с т р у к т о р о в . П ри этом конструктор производного класса мо- "бде^
жет иметь свои собственные параметры. товТ а л консплоиь'
Р « 3 оазового класса
class Subclass : BaseClass { ч
-^public Subclass(список параметров)
Это кон- : base(список параметров базового класса) {
ст рукт ор
производного II сначала вьшолняется конструктор базового классса
класса. / / а потом все остальные операторы

Конструктор базового класса Вызывается первым ^Упраж нение!


Убедитесь в этом сами!

О Создайте базовый класс с конструктором , вызывающим окно д и ал о га


Добавьте к форме кнопку, которая инициализирует базовый класс и вызывает окно диалога:
c l a s s M y B a seC la ss {
p u b lic M y B a se C la ss(s tr in g b a seC la ssN e e d sT h is) {
M essa g e B o x .S h o w (" T h is is th e base c la s s : " + b a se C la ssN e e d sT h is);

}
, Эт от парам ет р нужен
' базовому конструктору.
_ _ Эта ошибка
© „ ч-
Добавьте производиыи класс, но не вызывайте конструктор означает, что
Добавьте к форме кнопку, которая делает то же самое для производного класса: производный
ґ ^ с іа з з M y S u b cla ss : M y B a seC la ss{
Выберите К О - p u b l i c M y S u b c l a s s ( s t r i n g b a s e C l a s s N e e d s T h i s , i n t a n o t h e r V a l u e ) { ^ о б в
манду Build » M essa g eB o x . show (" T h is i s t h e s u b c l a s s : " + b a s e C la s s N e e d s T h is ^^/^^acca
Build Solution, + « and " + a n o th erV a lu e) ; ч
u Ш получи- ________________________ ____________________________ )
Me сообіцение a 1 No overload fo r m e th o d 'MyBaseClass' takes 'O' a rg u m e n ts ^ -------------------- -—
об ошибке. }

© Заставьте сначала вызывать конструктор из базового класса


Затем инициализируйте производный класс и посмотрите, в каком порядке по­
явятся два окна диалога!
c la ss M y S u b cla ss : M y B a seC la ss{
Так МЫ послали p u b l i c M y S u b c l a s s ( s t r i n g b a s e C l a s s N e e d s T h i s , i n t a n o t h e r V a l u e ) базово­
базовому классу ; b a s e (b a seC la ssN e e d sT h is) с т р о к а вызовет. ошибке
парам ет р, ко- ^ го класса, ^ о г о сооОЩ работать,
торыи т ребует - ц „е изменился исчезнет, и программ
ся его конст рук­
тору. дальш е * 267
кэтлин все еще нужна ваша помощь

Теперь мы готовы завершить программу для Кэтлин!


После последнего обновления программа Кэт­ D innerP arty B irthdayP arty
лин получила возможность рассчитывать сто­
имость дней рождения. Теперь Кэтлин хочет NumberOfPeople NumberOfPeople
брать еще по $100 за вечеринки с количеством CostOfDecorations CostOroecorations
гостей больше 12. Сначала казалось, что вам CostOfBeveragesPerPerson CakeSize
придется набирать весь код заново, но теперь, HealthyOption CakeWriting
познакомившись с процедурой наследования, CalculateCostOfDecorationsO CalculateCostOroecorationsO
вы знаете, как этого избежать. CalculateCostO CalculateCostO
SetHealthyOptionO

Е сли все сд ел ать прав и л ь но, д о стато чн о будет


йражнение о тр е д а кти р о в а ть д в а кл ас са, н е за тр а ги в а я с а м у ф о р м у!

О Новая модель классов


У нас останутся классы D i n n e r P a r t y и B i r t h d a y P a r t y , но теперь они будут наследовать от
единого класса P a r t y . Так как эти методы должны иметь одинаковые свойства, методы и поля,
редактировать форму не потребуется. Просто некоторые параметры переместятся в базовый
класс P a r t y , и мы будем перекрывать их по мере необходимости.

Party
NumberOfPeople
CostOfDecorations

CalculateCostOroecorationsO
CalculateCostO

D innerP arty B irthdayP arty


NumberOfPeople к?■ NumberOfPeople
CostOroecorations CostOroecorations
CostOfBeveragesPerPerson CakeSize
HealthyOption t CakeWriting
CalculateCostOroecorationsO CalculateCostOfDecorationsO
CalculateCostO CalculateCostO
SetHealthyOptionO

268 глава б
о Б а з « .я й клосс P a rty
Создайте класс P a r t y и укажите уровень доступа public. Рассмотрим диаграм­
му классов и решим, какие свойства и методы следует перенести из классов
D in n e r P a r ty и B i r th d a y P a r ty в класс P a r ty .
★ Переместите свойства N um berO fPeople и C o s tO fD e c o ra tio n s , так как
они определены как для D in n e r P a r ty , так и для B i r th d a y P a r ty .
★ Проделайте то же самое с методами C a lc u la te C o s tO f D e c o r a tio n s O
и C a lc u la te C o s t (). Не забудьте перенести и закрытые поля, необходи-
Вскоре вы п о- методов. (Помните, что производные классы видят только
знаком ит есь------ 3 ^ „ г. ^
с ключевым ПОЛЯ общего доступа. После переноса закрытого поля в класс P a r ty , клас-
словом protected. D in n e rP a r ty и B ir th d a y P a r ty потеряют к нему доступ.)
по ле°^я^прои^ ^ потребуется конструктор. Внимательно посмотрите на кон-
водного класса, структоры B i r t h d a y P a r t y и D in n e rP a r ty : не имеют ли они что-нибудь
осмавляя его общее.
закрытым для
всего ост аль- Теперь добавим платеж $100 к мероприятиям с количеством гостей бо-
ного. лее 12. Это касается как званых обедов, так и дней рождения, поэтому
поместим этот платеж в класс Party.

О П у сть класс D in n e rP arty насл едует от класса P a rty


Теперь, когда класс P a r t y взял на себя часть функций класса D in n e rP a r ty , в по­
следнем можно оставить только функции, связанные с обедами.
★ Убедитесь, что конструктор работает. Он делает что-то, чего не делает
конструктор класса P a rty ? Оставьте только эти функции.
★ Все параметры и методы варианта Healthy Option должны остаться в классе
D in n e rP a rty .
★ Если мы хотим сохранить код формы, то не сможем перекрыть метод Эта процедура
C a lc u la te C o s t (), так как форма должна передать в него логическую пе- назы вает ся^^^
ременную h e a lth y O p tio n . Поэтому мы просто добавим в класс еще один
метод C a lc u la te C o s t О , работающий с другими параметрами. Исполь- Д^^сддотренй
зуйте для его объявления код из начала данной главы. Теперь запись b a s e . ^ главе 8.
C a lc u la te C o s tO дастдоступ к методу C a lc u la te C o s t () в классе P a r ty .

О П усть класс B irth d a y P a rty такж е насл едует от класса P a rty


Для класса B i r th d a y P a r ty проведите аналогичную процедуру, перенеся все об­
щие признаки в базовый класс.
★ Какие действия конструктора B i r t h d a y P a r t y не являются частью клас­
са P a rty ?
★ Вам нужно посчитать стоимость торта внутри класса B ir th d a y P a r ty .
Это затрагивает метод и свойства, поэтому их потребуется перекрыть.
★ Свойства тоже можно перекрывать! Присваивая значение переменной
b a s e .N um berof P e o p le , вы обращаетесь к методу записи в базовом клас­
се. Ключевое слово b a s e потребуется вам как для чтения, так и для за­
писи.

дальше > 269


решение упражнения

•ЗЖНбНИб Вот как следовало отредактировать классы DinnerParty и BirthdayParty, чтобы они
унаследовали свойства базового класса Party. Именно это позволило вам доба-
cLlcnrlc вить новый платеж ($100), не редактируя при этом форму!

Э т о т код был перемещен


c la ss P arty
из классов DinnerParty
и BirthdayParty в класс
{ Party.
c o n s t i n t C ostO fF oodP erP erson = 25;
p r iv a te b o o l fa n cy D eco ra tio n s;
К онст рукт ор класса
p u b lic d e c im a l C o s tO fD e c o r a tio n s =
Party содержит ф унк­
ции, которые раньше
p u b l i c P a r t y ( i n t n u tn b erO fP eop le, b o o l f a n c y D e c o r a t i o n s ) несли конструкторы
t h i s . fa n c y D e co r a tio n s = fa n c y D e co r a tio n s; классов DinnerParty
th is .N u m b e r O fP e o p le = n u m b e rO fP eo p le ; и BirthdayParty.
}

p r iv a te in t n u m b e rO fP eo p le ;
p u b l i c v i r t u a l i n t N u m b erO fP eo p le { Перед парамет ром
g e t { r e t u r n n u m b e rO fP eo p le ; } «_ « > := :_________
NumberOfPeople т ребует ­
set {
ся вставить ключевое сло­
во virtual, так как класс
n u m b e rO fP eo p le = v a l u e ; BirthdayParty должен его пере­
C a lcu la teC o stO fD eco ra tio n s(fa n c y D eco r a tio n s) крыть (в эт ом случае измене­
ние количества гостей меняет
} диаметр торта).
Стоимость оформления
p iib lic v o id C a lc u la teC o stO fD eco ra tio n s(b o o l fancy) {
для мероприятий обоих
fa n c y D e co r a tio n s = fan cy;
\ типов рассчитывается
i f (fan cy)
по одной и той же схеме,
C o stO fD eco ra tio n s = (N u m b erO fP eo p le * 1 5.00М ) + БОМ; у х .о э т о му ее логично п о ­
e ls e м ест ит ь в класс Party.
C o stO fD eco ra tio n s = (N u m b erO fP eo p le * 7.50М ) + ЗОМ;
}

p u b lic v i r t u a l d e c im a l C a lc u la te C o s tO {
d e c im a l T o ta lC o s t = C o s tO fD e c o r a tio n s + (C ostO fF ood P erP erson * N u m b e rO fP eo p le )
i f (N u m b erO fP eo p le > 12)
{
T o ta lC o st += lOOM;
^ М етоЭ вычисления стоимости
}
r etu rn T o ta lC o st; пом ет ит ь
словом vlrtuaL так как его п о -

270 глава 6
наследование

c la s s B irth d a y P a rty : P arty {


p u b l i c i n t C a k eS iz e ;

p u b l i c B i r t h d a y P a r t y ( i n t n u m b e rO fP eo p le , b o o l f a n c y D e c o r a t i o n s , s t r i n g c a k e W r itin g )
: b ase(n u m b erO fP eo p le, fa n c y D e c o r a tio n s )
C a lc u la teC a k e S ize (); К онст рукт ор ост авляет
th is .C a k e W r itin g = c a k e W r itin g ; основной объем работы За-
C a lc u la te C o stO fD e c o r a tio n s(fa n c y D e c o r a tio n s) зовами классу. И вызывает
} мет од CalculateCakeSizeQ, как
делал и старый конструктор
p r iv a te v o id C a lcu la teC a k eS ize! { и з класса B ir th d a y P a r ty
i f ( N u m b e r O f P e o p l e <= 4 )
C a k e S iz e = 8;
М етод Са1сиЫеСаке$1леО помещ ен
e lse
C a k eS iz e = 16;
в класс B irth d a yP a rty, т ак диам ет р
т о р т д учит ы вает ся т олько при
} расчет ее ст оим ост и дней рождения.
расчет
p r i v a t e s t r i n g c a k e W r i t i n g = ""
p u b l i c s t r i n g C a k e W r itin g { По эт ой же причине свой­
g e t { r e tu r n t h i s . c a k e W r itin g ; } ст во CakeW riting ост ает ся
set { в классе B irthdayP arty.
i n t m axL ength;
i f ( C a k e S i z e == 3)
m a xL ength = 16;
e ls e
m axL ength = 40;
i f ( v a l u e .L e n g t h > m axL ength) {
M e s s a g e B o x .S h o w ( " T o o many l e t t e r s f o r a " + C a k e S i z e + " i n c h c a k e " )
i f (m axL ength > t h i s . c a k e W r i t i n g . L e n g th )
m axL ength = t h i s . c a k e W r i t i n g . L e n g t h ;
t h i s .c a k e W r i t in g = c a k e W r i t in g .S u b s t r in g (0, m axL ength);
} e ls e
t h i s . c a k e W r itin g = v a lu e ; М етод CalculateCostQ и з ­
дается в п е р е к р ы т и и ,, т ак
^кст чала^вш исляет е
}
cmouMocmt>
p u b l i c o v e r r i d e d e c i m a l C a l c u l a t e C o s t O0 { . т о м ко т и т е добавить ее
d e c im a l C ak eC ost; к сум м е,
i f ( C a k e S i z e == 8) т а л м ет од CalculateCostQ
C a k e C o s t = 40M + C a k e W r i t i n g . L e n g t h * .25M ; из класса Party.
e lse
C a k e C o s t = 75M + C a k e W r i t i n g . L e n g t h * .25 M ;
r e t u r n b a s e . C a l c u l a t e C o s t () + C a k e C o s t ;
} 3dect> свойство NumberOfPeople
должно перекры ват ь одноименное
p u b l i c o v e r r i d e i n t N u m b erO fP eo p le {
свойство из класса P arty, т ак как
g e t { r e t u r n b a se.N u m b e rO fP e o p le ; }
м ет оду записи нужно пересчит ат ь
set {
^ разм ер т орт а. При эт о м вызы-
b a se.N u m b e rO fP e o p le = v a l u e ; \ вает ся base.NumberOfPeople, чт о
C a lc u la teC a k e S ize ();
провоцирует выполнение мет ода
t h i s . C a k e W r itin g = c a k e W r itin g ;
записи из класса Party.

--------- ► ||]=*оДоЛЖение н а с. 272 -

дальше ► 271
отличная работа!
— Э т о п оследний к л а с с ,
в программе Кэтлин.
'Код формы остался без
х/у изменений.) Это поле общего доступа
ажненке V
c l a s s D in n erP a rty : P a rty
используется только
I / расчете стоимости званых
реш ение { (V. обедов и поэтому остается
p u b l i c d e c i m a l C o s t O f B e v e r a g e s P e r P e r s o n ; в эт ом классе.

p u b lic D in n e r P a rty (in t n u m b e rO fP eo p le , b o o l h e a lth y O p tio n ,


b o o l fa n c y D e co r a tio n s) воспроизве-
■ b a se(n u m b ero fP eo p le, fa n c y D e c o r a tio n s) { emu функциональ
S e tH ea lth y O p tio n (h ea lth y O p tio n ); H o m b старого класса
C a lc u la teC o stO fD ec o r a tio n s(fa n c y D e co r a tio n s); Dinnerparty, новый
} конструктор вызы­
вает сначала кон­
p u b lic v o id S e tH ea lth y O p tio n (b o o l h ea lth y O p tio n ) { ст рукт ор Party,
if (h ea lth y O p tio n ) а зат ем метод
SetHealthyOptionO-
C o s t O f B e v e r a g e s P e r P e r s o n = 5.00M ;

Метод
C ostO fB everagesP erP erson = 20 .0 0 M ;
}

p u b lic d e c im a l C a lc u la t e C o s t ( b o o l h e a lth y O p tio n ) {


d e c i m a l t o t a l C o s t = b a s e . C a l c u l a t e C o s t ()
+ (C ostO fB everagesP erP erson * N u m b e rO fP eo p le );
К Классу DinnerParty нужен другой м е-
if (h e a lth y O p tio n )
V . mod CalculateCostQ, поэтому вместо
retu rn to ta lC o st ' •95М; перекрытия МЫ его перегрузим- При
e ls e помощи ключевого слова base вызыва-
retu rn to ta lC o st; ется метод CalculateCostQ из класса
} Party, затем к результату прибавля­
ется стоимость напитков и вычисля-
ется скидка за «здоровый» вариант-
Программа работает!
Теперь мой бизнес пошел в гору, О т ом , как именно р а ­
спасибо огромное! ботает перегрузка, вы
узнаете в главе 8.
С тойте! В п р о гр ам м е все ещ е остал ась по тенц и ал ь ная ош ибка!
В классе D i n n e r P a r t y сейчас два метода C a l c u l a t e C o s t ( ) : один унаследованный от класса
P a r t y , а другой созданный нами. Этот класс не инкапсулирован, поэтому остается возможность
вызвать неправильный метод C a l c u l a t e C o s t ( ) :
D in n e r P a r t y d in n e r = new D i n n e r P a r t y (5, t r u e , tru e);
d e c im a l c o s t l = d i n n e r .C a l c u l a t e C o s t ( t r u e ) ;
d e c im a l c o s t 2 = d i n n e r . C a l c u l a t e C o s t ();

c o s t l получит значение 261.25, в то время как c o s t 2 - значение 250. Проблема... Иногда в базовом
классе существует код, к которому вы не хотите обращаться напрямую. Или вы не собирались созда­
вать экземпляры класса P a r t y , но такая возможность сохранилась. А что будет, если человек, редакти­
рующий наш код, создаст экземпляр класса P a r t y ? Явно не то, что мы планировали.
О том, как решить эту проблему, вы узнаете в следующей главе!
272 глава 6
наследование

Система управления ульем


Теперь ваша помош;ь нужна пчелиной матке! У!лей вы­
шел из-под контроля и ей нужна программа, которая
поможет им управлять. Улей полон рабочих пчел, име­ ШЗ
ется и список заданий. Нужно распределить задания
между пчелами с учетом их специализации.
т&
Постройте систему, управляющую поведением рабо­
чих пчел. Вот как она должна функционировать:

М атка раздает задания рабочим


Существует шесть видов работ. Некоторые пчелы уме­
ют собирать нектар и делать мед, другие могут строить
улей и защищать его от врагов. Есть пчелы, которые
вообще могут выполнять любую работу. Вам нужно на­
писать программу, дающую пчеле задание, которое она
в состоянии выполнить.
Пчелы трудятся
посменно, а боль­
шинство рабом
выполняемся
в несколько смен.
Мамка вводим
число смен в поле
Shifts и щ елкаем
на кнопке Assign
this job, чтобы
дать задание сво­
бодным пчелам.

При наличии до­


ступных пчел про­
Сущ ествуем ш есмь видов рабом. М а м ­ грамма дает им T h e j o b ‘H o n e y m a n u f s c tu r in g ' wilt b e d o n e in 3 sh ifts

ке все равно, чем занимается отдельная задание и отчиты~_


пчела. Она всего лишь указывает, что баемся перед м а м ­
нужно сдслаиль, й провр/яммй опрсделяа^ кой, выводя окно:
наличие доступных рабочих и дает им Пре
задание. будем закончено за
м ри смены».
Время работать
Раздав задания, матка заставляет пчел отрабатывать
очередную смену щелчком на кнопке «Работать! Следу­
ющая смена». Программа отчитывается, какие пчелы
работали в эту смену, какую работу они выполняли и
сколько смен им еще осталось трудиться именно над
этим заданием.
дальше ► 273
помоги пчелиной матке

Построение осноб
Этот проект делится на две части. Начнем с обзора системы управления
ульем. Вам потребуются два класса - Queen (Матка) и W orker (Рабочий), -
которые следует инкапсулировать. Не обойдемся мы и без связанной с эти­
ми классами формы.
Иногда в диаграмме классов м огут т
появиться закрытые поля и типы.
- ,-^ б ъ е к т Queen указывает, какую работу нужно сделать.
Q ueen
Массив объектов W o r k e r отслеживает текущую занятость
private workers: WorkerQ j
каждой рабочей пчелы. Эта информация хранится в закры­
private shiftNumber: int
тых полях W o r k e r [ ] .
Форма вызывает метод A ssignW ork {) (Назначить задание),
передавая строку с названием работы и переменную типа int с
AssignWorkO
количеством смен. При наличии свободной пчелы, которая в
WorkTheNextShiftO
состоянии выполнять эту работу, возвращается значение true.
Кнопка Work the next sh if t вызывает метод W o r k T h e N e x t ­
S h i f t O , заставляющий каждый объект Worker отработать
одну смену и затем проверяющий его состояние, чтобы сфор­
Свойства C u m n tJ o b мировать отчет.
(ТекущееЗадание) и ShiftsLeft
(ОставилиесяСмены)
предназначены только для чтения.
W o rk e r Посмотрим на функции массива Worker.
CurrentJob: string Свойство C u r r e n tJ o b дает понять объекту Queen, какую ра­
ShiftsLeft: int боту выполняет каждый рабочий. Если рабочий на момент
проверки ничем не занят, возвращается пустая строка.
private jobslCanDo: string[] ^ Раздача заданий рабочим происходит при помощи метода
private shiftsToWork: int D o T h isJo b {). Если рабочий не занят и в состоянии выпол­
private shiftsWorked: int нять указанную работу, метод возвращает значение true.
DoThisJobO Метод W o rk O n eS h iftO заставляет рабочего отработать
WorkOneShiftO одну смену, а также отслеживает оставшееся количество
смен. Если работа закончена, возвращается пустая строка.
Это означает, что он готов к выполнению следующего зада­
ния.

^ момент времени х т -
U p o ЗЦ О , как
уСоБерШеНСЩБоБатпь
систему угд^аБления уЛьеМ
Tij^u поМоЩи наследования,
Чшпагедге на с.
наследование

Итак, приступим к построению системы управления ульем, которая облег­


*^праж нгниг
чит жизнь пчелиной матке.

О П о стр о ен и е qx>pMbi
Форма будет очень простой, всю работу предстоит выполнить классам Q u e e n и W o r k e r .
Добавьте закрытое поле Q ueen и две кнопки, вызывающие методы A s s i g n W o r k () и
W o r k T h e N e x t S h i f t О . Еще потребуется элемент C o m b o B o x , управляющий работой
пчел (пункты этого раскрывающегося списка были перечислены на предыдущей стра­
нице), элемент N u m e r i c U p D o w n , две кнопки и текстовое поле для отчета о сменах.
Вид формы показан ниже.
Кнопка nextShift вы­
Элемент ComboBox нд- ^ Beehw« Menagement System зывает метод
зывается workerBeeJob. WorkTheNextShiftQ из
Используйте свойство . Wsika-BeeAssignma^s класса Queen, возвраща­
Items для ф орм ирования^ Wotka-beejsrfj Ms ющий строку с от че­
списка. DropPownStyle W oittme т ом о сменах.
присвойте значение collector neadshe
PropPownListj чтобы Assign toabee
пользователь мог вы­ Рассмотрим отчет,
бирать значения только Reportforahft#12 созданный объектом
из раскрывающего­ Woiker#1 !Sdoing 'NectarcoSector'for1moredifts Queen. Он начинается
ся списка. Поле Shifts WorkerЙ2wi bedonewth 'Eggcare'sftertHsЙЛ с номера смены, за ­
Woike-#3isdoing'aingрйго!’for1moreshfts
является элементом Woiker#4firtshedthejob т ем следует спи-
NumericUpPown с им е­ coK дел работников.
i
Woiker#4isnotwraking
нем shifts. Используйте esc-

Д айт е элементу
TextBox имя report,
а его свойству MultiLine последоёательности
значение true.
" \ г \п ’] чтобы доба­ ^
Конструктор каждого объекта
p u b lic F o rm lо { вить знак переноса
чает массив строк с информацией о т ом ,
In itia liz e C o m p o n e n tO ; в строку. пчела.
какие работы может выполнять
W o r k e r [] w orkers = n e w W o r k e r [4]
w o r k e r s [0 ] = n e w W o r k e r ( n e w s t r i n g [] " N ectar c o ll e c t o r " , "Honey m a n u f a c t u r in g " });
w o r k e r s [1 ] = n e w W o r k e r ( n e w s t r i n g [] "Egg c a r e " , "Baby b e e tu to rin g " });
w o r k e r s [2 ] = n e w W o r k e r ( n e w s t r i n g [] "H ive m a in t e n a n c e " , " S tin g p a tr o l" }) ;
w o r k e r s [3 ] = n e w W o r k e r ( n e w s t r i n g [] " N ectar c o l l e c t o r " , "Honey m a n u f a c t u r in g " .
"Egg c a r e " , "Baby b e e tu to r in g " , "H ive m a in t e n a n c e " , " S tin g p a tr o l" });

q u e e n = new Q ueen (w o r k e r s ) форме потребуется поле Queen с именем queen. Массив


} W orker нужно будет передать конструктору объекта Queen.

0 Создание классов W o rk e r и Q ueen


Метод Q u e e n . A s s i g n W o r k О циклически просматривает массив w o r k e r и пытается
дать работу каждому oбъeктyw orkerпpипoм oщ и метода D o T h i s J o b ( ) . Объект W o r k e r
в массиве строк j o b s I C a n D o проверяет свою способность выполнить предложенную ра­
боту. Если да, закрытому полю s h i f t s T o W o r k присваивается количество смен, параме­
тру C u r r e n t J o b —название работы, а переменной s h i f t s W o r k e d —значение «ноль».
Отработанная смена увеличивает эту переменную на 1. При помощи предназначенного
только для чтения свойства S h i f t s L e f t матка оценивает, сколько смен еще нужно от­
работать.
решение упражнения

ненке c l a s s W orker { ' ^он ст рукт ор за ­


дает свойство
ешение p u b l i c W o r k e r ( s t r i n g [] j o b s l C a n D o )
t h i s . jo b s lC a n D o = jo b slC a n D o ; JobslCanDo, пред­
1 ставляющее собой
строковый массив.
p u b lic in t S h iftsL eft { Он закрыт, т ак как
Свойство ShiftsLeftj f get { предполагается, что
предназначеное только r e t u r n sh iftsT o W o rk - sh iftsW o rk ed ; м ат ка только п р о ­
для чтения, показывает, } сит рабочего вы­
сколько смен осталось } полнит ь р а б о т у , но
отработать до завер­ не должна при этом
шения задания. p r iv a te s t r in g cu rren tJob = проверять. Может
p u b lic s t r i n g C u rren tJob { ли он это сделать.
get {
Свойство C urrentJob r etu rn cu rren tJob ;
(т олько для чтения)
показывает м ат ке, }
}
какие работы следу
ет провести. p r iv a te s t r i n g [] j o b s l C a n D o ;
p riv a te i n t sh ifts T o W o rk ;
p r iv a te i n t sh iftsW o r k e d ;

p u b lic b o o l D o T h is J o b ( s t r in g jo b , i n t n u m b erO fS h ifts)


if (J^ S tring. IsN u llO r E m p ty ( c u r r e n t J o b ) )
Матка использует метод retu rn f a ls e ;
PoThisJob() обьекта worker
f o r ( i n t i = 0; i < j o b s lC a n D o .L e n g th ; i+ + )
для назначения заданий р а -
i f ( j o b s l C a n D o [ i ] == j o b ) {
дочим, рабочий проверяет cu rren tJob = job;
свойство JobslCanDo, чтобы
th is .s h ifts T o W o r k = n u m b erO fS h ifts;
понять, может ли он выпол­
s h if t s W o r k e d = 0; Оператор означающий
нять данную работу.
retu rn tru e; НЕТ—- проверяет, является
} ли строка пустой и им еет ли
retu rn fa lse ;
значения null. Именно таким
} образом осуществляется про­
верка несоблюдения условия.
p u b l i c b o o l W o rk O n e S h iftO {
i f (S tr in g .IsN u llO r E m p ty (c u r r e n tJ o b ))
Матка использует retu rn f a ls e ;
метод WorkOneShiftO sh iftsW o r k e d + + ;
обьекта worker, i f (s h ifts W o r k e d > sh ifts T o W o rk ) {
чтобы заставить s h iftsW o r k e d
рабочих отработать sh iftsT o W o rk = 0 Сначала проверяется поле
следующую смену. c u r r e n t J o b = и //.’ currentJob. Если рабочий ничем
Метод возвращает retu rn tru e; не занят, возвращается значение
значение true, если false, и метод прекращает рабо-
это последняя смена e ls e п^У-В противном случае парамет р
текущего работника. retu rn fa lse ; ShiftsWorked увеличивается на 1 ,
В отчете появляется ^ и проверяется, сделана ли работа
строчка, что после } сравнения с i^apaMo^poM
этой смены пчела ShiftsToWork). При положительном
свободна. результ ат е мет од возвращает
true.

276 глава 6
наследование

c l a s s Q ueen {
p u b l i c Q u e e n ( W o r k e r [] w o r k e r s ) {
th is .w o r k e r s = w orkers;
}
p r iv a te W o r k e r [] w o r k e r s ;
к о м /т к Т з н « ч е £ < е поля заЭ ает конструктор.
p r iv a te i n t S h iftN u m b er = 0;

p u b lic b o o l A s sig n W o r k (str in g jo b , i n t n u m b erO fS h ifts) {


f o r ( i n t i = 0; i < w o r k e r s .L e n g t h ; i+ + )
i f (w o r k e r s [i].D o T h is J o b (jo b , n u m b erO fS h ifts))

retu rn fa lse r
\ \ умеет делать работы икЛаи-

" " " ' s h i ^ N ^ e r : : ; ^


s t r i n g r e p o r t = " О т ч е т д л я с м ен ы #" + s h i f t N u m b e r + ‘' \ r \ n " ;
: ^ f o r ( i n t i = 0; i < w o r k e r s .L e n g th ; i+ + )
. Г"
^e i^o d {
x°'^f<Thet^e . ( w o r k e r s [ i ] . W o r k O n e S h i f t {) )
r e p o r t += " Р а б о ч и й #" + ( i + 1) + " з а к о н ч и л р а б о т у \ г \ п "
i f ( S t r i n g . IsN u llO rE m p ty (w o r k e rs [i] .C u r r e n tJ o b ) )
r e p o r t += " Р а б о ч и й #" + ( i + 1) + " н е р а б о т а е т \ г \ п " ;
e ls e
<Ky i f ( w o r k e r s [ i ] . S h i f t s L e f t > 0)
r e p o r t += " Р а б о ч и й #" + ( i + 1 ) + " в ы п о л н я е т + w o r k e r s [ i ] . C u rren tJob
+ "' ещ е " + w o r k e r s [ i ] . S h i f t s L e f t + " с м е н \ г \ п " ;
e ls e
r e p o r t += " Р а б о ч и й #" + ( i + 1) + " з а к о н ч и т ' "
+ w o r k e r s [ i ] . C u r r e n t J o b + "' п о с л е э т о й с м е н ы \ г \ п " ;
}
retu rn rep ort;
^ Поле queen используется ф ор­
} мой Эля хранения ссылок н«
Вы знаете код для конструктора. Вот код для остальной части формы: Queen, который, о toow
^ очередь, содержит массив ссы-
Q ueen q u e e n ; \ ' док на оЗьекты worker.
p r i v a t e v o id a s s ig n J o b _ C lic k ( o b j e c t se n d e r , E ven tA rgs e) {
i f ( q u e e n . A s s i g n W o r k ( w o r k e r B e e J o b . T e x t , ( i n t ) s h i f t s . V a l u e ) == f a l s e )
M e s s a g e B o x . S h o w ("Для э т о г о з а д а н и я р а б о ч и х н е т
+ w orkerB eeJob.Text + "М атка г о в о р и т . . . " )
e lse
M e s s a g e B o x . S h o w ( " З а д а н и е '" + w o r k e r B e e J o b . T e x t + б у д ет зак ончен о ч ер ез
+ s h i f t s . V a l u e + " с м е н " , "М атка г о в о р и т . . . " ) ;
} Кнопка assignJoh вызывает
мет од AssignWoirkQ объекта
p r i v a t e v o i d n e x t S h i f t _ C l i c k ( o b j e c t s e n d e r , E v e n t A r g s e ) { queen, дающий пчелам за ­
r e p o r t .T e x t = q u e e n .W o r k T h e N e x tS h ift0 ; дания и отображает окно с
сообщением о т ом , пригоден
^ ^ К н о п к а nextShift заставляет м ат ку раздать задания на ли выбранный рабочие^ для
( следуюищи) смену. При эт ом в текстовом поле формы указанной деятельности.
появляется отчет.

дальше > 277


все мы только пчелы

ж нт Р або та ещ е не закончена! 1\/1атке требуется система учета потребляемого в улье меда.


Великолепный ш анс применить на практике свои знания!

Сколько же м ед а потребляет ул е й
Матке только что позвонила пчела-бухгалтер и сказала, что в улье недостаточно меда. Бух­
галтеру нужна информация о количестве потребляемого меда, чтобы понять, не нужно ли
часть рабочих, занимающихся заботой о потомстве, перевести на добычу меда.
★ Все пчелы потребляют мед, поэтому его должно быть много.
★ Занятые рабочие потребляют больше меда. Больше всего его требуется в момент на­
чала работы, потом потребление падает. В последнюю смену пчела съедает 10 единиц
меда; в предпоследнюю - 11; перед этим - 12 и т. д. То есть если пчела работает (ее
переменная S h i f t s L e f t больше нуля), количество потребляемого меда можно вы­
числить, прибавив 9 к S h i f t s L e f t .
★ Неработающая пчела ( S h i f t s L e f t равно нулю) съедает 7.5 единиц меда за всю смену
★ Это данные для обычных пчел. Если вес пчелы превышает 150 мг, она ест на 35%
меда больше. (К матке эта информация не относится.)
★ Чем больше занятых рабочих, тем больше меда потребляет матка, так как больше
сил тратится на управление ульем. Количество рабочих смен матки равно количе­
ству смен пчелы, которой осталось работать дольше всего.
★ Матка потребляет в смену на 20 единиц меда больше, если работает не больше
2 пчел или меньше, или на 30 единиц больше, если работают 3 пчелы и более. Так
как вес матки составляет 275 мг, к ней не относится правило 35%.
★ Данные о количестве потребляемого меда нужно добавить в конец отчета об отра­
ботанных сменах.

о Создайте класс Вее для учета потребления м еда


Пчелы должны знать свой вес, чтобы определить, не съедают ли они на 35% меда больше.
Создайте метод G e t H o n e y C o n s u m p t i o n (), вычисляющий потребление меда. Этот
метод понадобится как рабочим пчелам, так и матке, у которой, впрочем, способ по­
требления немного отличается, поэтому класс worker может просто наследовать дан­
ный метод, а класс queen будет перекрывать его.
★ Методу G e t H o n e y C o n s u m p t i o n О нужна информация о количество смен, которые
осталось отработать, поэтому добавим свойство S h i f t s L e f t , предназначенное толь­
ко для чтения, и пометим его ключевым словом virtual, чтобы дать возможность пере­
крыть его в классе worker. Свойство это возвращает нулевое значение.
★ Так как потребление меда зависит от веса пчелы, конструктор В е е должен иметь
поле для хранения данной информации. Другим классам эти сведения не потребу­
ются, поэтому пометим их ключевым словом private.

|)\. Поля и свойства следует делать закрыты-


\ — Мы по умолчанию и открывать, только когда
к ним тредуется доступ из других классов.

278 глава 6
наследование

Подсказка: Воспользуйтесь появлением со­


общением об ошибке «по overload'» Авпйипґ
т

О Класс W o rk e r долж ен наследовать от класса Вее


Конструктор класса W o r k e r должен брать вес пчелы и передавать его конструктору базо­
вого класса. Вам остается только добавить ключевое слово o v e r r i d e к методу S h i f t L e f t .
После этого каждая рабочая пчела получит возможность вычислить свое потребление
меда, и ваша работа по редактированию класса W o r k e r будет завершена!

Класс Q ueen такж е долж ен наследовать от класса Вее


С к л а сс о м Q u e e n п р и д е т с я п о в о з и т ь с я , т а к как и м е н н о з д е с ь п р ои сходи т подсчет количе­
ства м еда и ф о р м и р у ется отчет.

★ Перекройте метод В е е . G e t H o n e y C o n s u m p t i o n о и добавьте к нему дополни­


тельные вычисления. Нужно определить количество рабочих пчел, от этого за­
висит потребление меда маткой. Количество смен матки равно количеству смен
самой занятой рабочей пчелы.
★ Обновите метод W o r k T h e N e x t S h i f t ( ), добавив к отчету информацию о потре­
блении меда. Вам потребуется цикл, который будет добавлять эти сведения для
каждого рабочего, а также вычислять пчелу с самым высоким потреблением, цикл
должен располагаться до кода, отправляюш;его пчел на работу (тогда матка будет
знать количество меда, съеденное во время текущей смены). Также нужно учесть
потребление меда самой маткой и добавить в конец отчета строчку «Total Honey
Consumption: ххх units» (Суммарное потребление меда).
★ Обновите конструктор для класса Q u e e n , как вы это делали с конструктором для
класса W o r k e r .
В классе Queen наберите public
public override
override и нажмите пробел.
E quafe(objectobj)
Появится список доступных ^
♦ G etHeshCodeO
Эля перекрытия методов. По-
еле выбора нужного варианта ■ ♦ IZ Z Z ir"
S ’ ShiftsLeft { g e t }
название базового метода п о ­ ToStringO
явится автоматически.

Корректны й учет всех данны х о пчел ах


После редактирования конструкторов Q u e e n и W o r k e r требуется внести изменения
и в способ их вызова. В конструкторах появился новый параметр W e i g h t , который следу­
ет указать:
★ Worker Вее #1: 175mg; Worker Bee # 2 :1 14mg; Worker Bee #3: 149mg;
Worker Bee #4: 155mg; Q ueen Bee: 275mg
Это последнее изменений, которое требовалось внести в форму!

дальше ► 279
решение упражнения

Перед вами класс Вее, в ко-


ажнение ^ ° р о м происходит основной
•^°^счет потребления лледа З а -
І'^еш ение ^ е м эти данш е и с п о ^ ц ю т с я
классами Worker и Queen.
Благодаря насле­
К онст рукт ор класса Вее
c la ss Bee {
p u b lic B e e(d o u b le w eig h t) {
задает поле Weight и м е -
п^од HoneyConsumptionO
дованию вы легко
}
t h is .w e ig h t = w eig h t;
Рлссчитываюищй пот ре­
бление меда рабочей пчелой смогли добавить
p u b lic v ir tu a l in t S h ifts L e ft
g e t { r e t u r n 0; }
{ в классы Qjieen
}
и Worker поведе­
p r iv a te d o u b le w e ig h t;

p u b lic v ir tu a l d o u b l e G e t H o n e y C o n s u m p t i o n () {
ние, учитьшающее
d o u b le c o n su m p tio n ;
i f ( S h i f t s L e f t == 0)
П чела, к о т о р о й
____о с т а л а с ь всего 3-
потребление меда.
сменй^ п отреб л я­
e lse
c o n su m p tio n = 7 .5 ;
е т 3-0 единии, меоа,
если смен Я, т о 13-
Только представь­
con su m p tio n = 9 + S h i f t s L e f t ;
if (w e ig h t > 150)
c o n s u m p t i o n *= 1 . 3 5 ;
и т. д. Свободная
от работы пчела те, что вместо
(ShiftsLeft = О ) по -
r e t u r n co n su m p tio n ; щаребляет 7.5. этого вам при­
} Пчелы с весом более

больше меда.
шлось бы вводить
повторяющийся
В форме меняется только код вручщвд!
p u b lic Forml О конструктор.
In itia liz e C o m p o n e n t(і

W o r k e r [] w orkers = new W o r k e r [ 4 ] ;
w o r k e r s [0 ] = new W orker(new s tr in g [] { " N e c t a r c o l l e c t o r " , "Honey m a n u f a c t u r i n g
w o r k e r s [1 ] = new W orker(new s tr in g [] { "Egg c a r e " , "Baby b e e t u t o r i n g " } , ( l l 4 )
w o r k e r s [2] = new W orker(new s tr in g [] {" H iv e m a in t e n a n c e " , " S t in g p a t r o l " ) T ^ 4 9
w o r k e r s [3] = new W orker(new s tr in g [] {" N ectar c o lle c t o r " "Honey m a n u f a c t u r in g "
"Egg c a r e " , "Baby b e e t u t o r i n g " , "H ive m a i n t e n a n c e " , " S t in g p a t r o l " },
q u e e n = new Q u e e n ( w o r k e r s ) ;

8 конструкторы класса
Worker добавляется информа­
ция о весе рабочей пчелы.

280 глава 6
наследование

p u b l i c W o r k e r ( s t r i n g [] jo b slC a n D o , in t w eig h t)
: b a se(w eig h t ) {
t h i s . jo b s lC a n D o = jo b slC a n D o ;
}
p u b l i c ..o v e r r id e in t S h iftsL eft {
// ... th e rest of th e c la ss is th e sam e ..
. Сначала мы делаем класс Queen
производным от класса Вее.
c l a s s Q u een : B e e { М ат ка весит Z 7 S м г, именно э т о т парам ет р
p u b l i c Q u e e n ( W o r k e r [] w o r k e r s ) передает конст рукт ор данного класса к о н ст р ук ­
: b a s e (275) { ^
т ору класса Вее.
th is.w o r k e r s = w orkers;
}

p u b lic .tr i„ g » o r k T h e N e « S h if t() " f " ' ’‘ '""О «ж Зого

d o u b le to ta lC o n B u m p tlo i. - 0; Эанто“ “
fo r (in t і = 0; і < w o r k e r s . L ength; i+ + ) оощее потребление.
to ta lC o n su m p tio n += w o r k e r s [ і ] . G e t H o n e y C o n s u m p t i o n ( ) ;
to ta lC o n su m p tio n += G e t H o n e y C o n s u m p t i o n ( ) ;

// ... Здесь идет исходный код метода за исключением оператора re tu rn

г report += "Общее п о т р е б л е н и е м е д а : " + t o t a l C o n s u m p t i o n + единиц"

С

м ет о д а W o r k ^ N e x tS h iftQ
о с т а л а с ь без и зм ен ен и й , в ы т о л ь к о д
8 эт ом классе перекрывает ся
м ет од CietHoneyConsumptionQ
в о т ч е т да н н ы е о п о т р е о л е н и и м е о а .
класса Вее. Чтобы учест ь п о ­
p u b l i c o v e r r i d e d o u b l e G e t H o n e y C o n s u m p t i o n () { т ребление меда м а т ко й , вы н а ­
d o u b le c o n s u m p t io n = 0; ходит е рабочего с сам ы м высо­
d o u b le la r g e s t W o r k e r C o n s u m p t io n = 0; ким пот реблением и добавляете
in t w ork ersD o in g J o b s = 0; 2 0 или 3 0 (в зависимост и от
^ for (in t i = 0; i < w o r k e r s . L ength; i+ + ) количест ва занят ы х пчел).
Ґ --- {
Э т от цикл if ( w o r k e r s [ i ] . G etH o n ey C o n su m p tio n 0 > la rg estW o r k e rC o n su m p tio n )
определяет la rg estW o rk erC o n su m p tio n w o r k e r s [ і ] .G etH o n ey C o n su m p tio n ();
самое самое if (w orkers [і] . S h i f t s L e f t > 0)
высокое w o rk e rsD o in g J o b s+ + ;
пот ребление
меда среди }
рабочих пчел. c o n s u m p t i o n += l a r g e s t W o r k e r C o n s u m p t i o n ;
if (w o rk ersD o in g J o b s >= 3)
con su m p tio n += 3 0 ;
Если т рудят ся 3 пчелы и более,
e ls e

retu rn
c o n s u m p t i o n += 2 0 ;
co n su m p tio n ;
Ч м а т ке т ребует ся 3 0 дополни­
т ельны х единиц меда-, в п р о т и в ­
ном случае ей нужны всего ЯО до -
полни т ельны х единиц.

дальше > 281


все мы только пчелы

Совершенствуем систему управления ульем при noMouiu наследования


Основа нашей системы готова, теперь воспользуемся наследованием для учета количества
меда, съедаемого пчелами. Каждая пчела потребляет мед, а больше всего его нужно матке.
Поэтому создадим базовый класс Вее, от которого будут наследовать классы Queen и W orker. т
Класс Вее содержит поведе­
ние, описывающее пот ребле- ''
ние меда. Так как оно зависит
от количества оставшихся Вее ^когда на диаграмме
смен, свойство ShiftsLeft было i^accoS показываются
перемещено в эт от класс и public ShiftsLeft: int возвращаемые значения
помечено ключевым словом и закрытые члены.
virtual, чтобы в классе Worker
оно могло быть перекрыто.
Все пчелы потребляют мед, п о - ^ virtual
этому в базовый класс добавлен GetHoneyConsumptionQ:
метод C ie tH o n e y C o n s u m p tio n Q , double Классу w orker нужно
который могут наследовать унаследовать методы от
классы q u e e n и w o r k e r . Но матка класса Вее и перекрыть
и рабочий потребляют мед по-
метод ShiftsLeft.
разному, поэтому метод помечен
словом v i r tu a l, чтобы его можно
было перекрывать
------ - Q ueen W o rker
В от чет для м а т ­
ки следует добавить private workers: Worker[] CurrentJob: string
данные о потреблении private shiftNumber: int ShiftsLeft: int
меда. Так как м а т ­
ка сама пот ребля­
ет мед, она должна private jobslCanDo: stringQ
унаследовать от AssignWorkO private shiftsToWork: int
класса Вее метод WorkTheNextShiftO
QetИoneyConsumption() private shiftsWorked: int
и перекрыть его. DoThisJobO
WorkOneShiftO

шв

ство имен брумнуи?. -------


'о ^

/
%
и а б с т р а т с ш н ы е К Л ассь!

Пусть классы
держат обещания

Действия значат больше, чем слова.


Иногда возникает необходимость сгруппировать объекты по в ы полняем ы м
ф ункци ям , а не по кпассам, от которых они наследуют. Здесь вам на по­
мощь приходят интерф ейсы , они позволяют работать с любым классом, от­
вечающим вашим потребностям. Но чем бо ль ш е в озм ож ностей, тем вы ш е
ответственность , и если классы, реализующие интерфейс, не вы пол нят
о б я зате л ь ств ... программа компилироваться не будет.
рабочие пчелы, объединяйтесь!

Вернемся к нашим пчелам


Было принято решение превратить систему учета из
предыдущ^ей главы в полноценный симулятор улья.
Вот как выглядит спецификация новой версии про­
граммы:

Симулятор улья
Для лучшего представления жизни в улье укажем спеии-
альные возможности рабочих пчел:
• Все пчелы потребляют мед и обладают весом.
. Матка раздает задания, следит за отчетами и отправ­
ляет рабочих на следующую смену.
• Рабочие трудятся посменно.
• Охранники «точат» жало, ишут врагов и жалят их.
. Сборшики меда ишут цветы, собирают нектар и воз-
врашаются в улей.

сохранять
от ви! в зависимости
от выполняемых ими функций

Многое остается неизменным


в новом симуляторе улья пчелы потребляют мед
тем же способом, что и раньше. Матка по-прежнему
должна раздавать задания и отслеживать, сколько
смен осталось отработать каждой пчеле. Рабочие
по-прежнему трудятся посменно. Просто их деятель­
ность претерпела небольшие изменения.

284 глава 7
интерфейсы и абстрактные классы

Классы для различных типов пчел


Вот иерархия с классами W o r k e r и Q u e e n , наследующими от класса Вее.
При этом класс W o r k e r имеет производные классы N e c t a r C o l l e c t o r
(Сборщик меда) и S t i n g P a t r o l (Охранник).

о оесе пчел

Так будут выглядеть


новые производные
классы.
J VJorke,

о рабочих сменах class StingPatrol : Worker


in t S tin g erL en g th ;
Worker Queen
Job WorkerQ bool en em y A lert;
ShiftsToWork ShiftNum­ p u b lic bool S h a rp en S tin g er (in t L ength)
ShiftsWorked ber {. . . }
ShiftsLeft
p u b lic bool L o o k F o r E n e m ie s ( ) { . . ' . }
DoThisJobO AssignWorkO p u b lic v o id S tin g (str in g E nem y){ . . . }
WorkOneShiftO WorkTheNextShiftO
HoneyConsumptionO

class NectarCollector ; Worker


{
in t N ectar;
StingPatrol NectarCollector p u b lic v o id F in d F lo w er s (){...}
StingerLength Nectar p u b lic v o id G a th erN ectar( ) { . . . }
EnemyAlert p u b lic v o id R e tu rn T o H iv e( ) { . . . }

SharpenStingerO FindFlowersO
LookForEnemiesO GatherNectarO
StingO RetumToHiveO

1 классы
>^анят ин~
Формации)
ШТУРМ
А как быть с пчелой,
совмещающей функции охранника
><отдельным и сборщика меда?
заданиям.

дальше > 285


интерфейсы для работы

интерфейсы
Наследовать класс может только от одного класса. Поэтому создание
двух производных классов S t i n g P a t r o l и N e c t a r C o l l e c t o r не помо­
Класс, реализу­
жет нам описать пчелу, которая в состоянии выполнять задания разны х ющий интерфейс,
типов.
Метод D e f e n d T h e H i v e () (Защита улья) из класса Q u e e n может заста­
должен вклю­
вить объекты S t i n g P a t r o l защищать улей. Но если матка захочет, что­
бы за эту работу принялись другие пчелы, она не сможет дать им команду:
чать в себя все
class Queen {
методы и свой­
}
private void DefendTheHive(StingPatrol patroller) { }
ства, указанные
в определении
'G интерфейса.

Объект N e c t a r C o l l e c t o r умеет собирать нектар, а экземпляры S t i n g P a t r o l борются с врагами.


Но даже если матка научит сборщиков нектара защищать улей, добавив методы S h a r p e n S t i n g e r ()
и L o o k F o r E n e m i e s О в определение их класса, она все р а в н о не сможет передать их своему методу
D e f e n d T h e H i v e (). Впрочем, можно воспользоваться двумя методами:

private void DefendTheHive(StingPatrol patroller);


private void AlternateDefendTheHive(NectarCollector patroller);

Даже если матка добавит м&тоды за ­


Ho это плохое решение. Вы получаете два фрагмента щиты обьекту NectarCollectorJ она не
кода, единственным различием которьк является то, сможет передать их своему методу
что один метод имеет параметр S t i n g P a t r o l , а вто­ PefendTheHiveQ, т ак как он ожидает
рой—N e c t a r C o l l e c t o r . ссылки StingPatrol. Приравнять же ссылку
StingPatrol объекту NectarCollector невоз­
К счастью, решить подобные проблемы позволяют можно. К
и н терф ей сы (interfaces). Они определяют, какие ме­ Можно добавить аналогичный метод
тоды должны присутствовать в классе. с названием AlternatePefendTheHiveQ, _
который будет ссылаться на объект
Методы, указанные в определении интерфейса, долж­ NectarCollector, но код получится
ны быть реализованы. В противном случае компилятор слиилком громоздким и неудобным. _
выдаст сообщение об ошибке. Код методов может быть
написан непосредственно в рассматриваемом классе Методы PefendTheHiveQ j
или же унаследован от базового класса. Интерфейс не и AlternatePefendTheHiveQ будут отличаться
только т ипом параметра. А чтобы заст а­
интересует происхождение методов и свойств, глав­ вит ь защищать улей объекты ВаЬуВееСаге
ное —чтобы при компиляции кода они были на своем или Maintenance , вам потребуются дополни-
месте. тельные, «альт ернативные» методы.

286 глава 7
интерфейсы и абстрактные классы

Ключевое слово interface

Чтобы добавить интерфейс к программе, писать методы И н т е р ф ш с ш ст оит давать


не нужно. Достаточно указать их параметры и тип воз­ имена, начинающиеся с п^описнои
вращаемого ими значения. И поставить в конце строки бцквы I. Нет, правила, одязмбаю-
щего вас т ак nocmynamt^, но э т о
точку с запятой.
делает код болев простым. Чтоды
Интерфейсы не хранят данные, поэтому вы не сможете u6edumt>ca, насколько это облегча­
добавить к ним поля. Но можно добавить определения ет жизнь, у с т а н о в и т е курсор на
свойств. Дело в том, что интерфейс определяет список ме­ пистун> строчку внутри любого
метода и наберите I— Inte/liSense
тодов класса с определенными именами, типами и параме­
сразу же покаж ет вам список до­
трами. Если вам кажется, что проблема может быть решена ступных интерфейсов NET
добавлением поля к интерфейсу, попробуйте вместо этого
добавить свойство, вполне вероятно, это именно то, что
вам было н у ж н о ^ ^ ^ _ ^ interface iStingPatrol
И нтерф ейс {
объявляется: Л ^sint
. AlertLevel { get;}
int StingerLength { get; set;}
bool LookForEnemiesO;
int SharpenStinger(int length)

Любому клас­ interface INectarCollector


су, реализующему { ЛЛЫНО и(^0МЯК9>^‘’
интерфейс, п о ­ void FindFlowersO;
надобится метод void GatherNectar
SharpenStingerO void ReturnToHive ;;;
С п а р а м ет р о м т ипа
int. ^
' tC »

Так как же помочь матке? Теперь она может создать метод, берущий Л(^0 ЭЛСМ6НТЫ ОТКОЫ“
в качестве параметра любой объект, который знает, как защитить улей: *
p r iv a te v o id D e fe n d T h e H iv e (IS tin g P a tr o l p a tr o lle r ) ТОГО интерфейса по
J умолчанию являются
открытыми. То есть
Этот метод может использовать объект S t i n g P a t r o l , N e c t a r S t i n g e r
или любую другую пчелу, знающую, как защитить улей. При реализации с помопц>ю интер-
I S t i n g P a t r o l м ето д D e f e n d T h e H iv e о гарантирует нал и ч и е у объек- ( g jjJ О П О е Д е Л Я С ”
т а с в о й с т в и м е т о д о в , н е о б х о д и м ы х д л я з а щ и т ы ул ья . i г

те открытые методы
Теперь, когда я знаю, и свойства любого
как защитить улей, мы
в безопасности! реализующего его
^ент О ^ класса.
дальше > 287
_ Чаоцо
немного от сборщика нектара и немного от охранника улья
------------^ а Д а Б а е М ы е ------------
БоЛ роС Ь!
Экземпляр NectarStinger Зачем симулятору улья интерфей­
сы? Ведь мы добавляем еще один класс
Используйте двоеточие, чтобы реализовать интерфейс. По­ NectarStinger, и все равно получаем
сле двоеточия сначала указывается класс, от которого про­ дублирующийся код?
исходит наследование, затем список интерфейсов. Если на­
следования не происходит, то интерфейсы перечисляются
в произвольном порядке. О ; Интерфейсы и не предназначены для борьбы
с'дублирующимся кодом. Они просто позволя­
ют использовать один и тот же класс в разных
.. ^ _ Э т о т класс нлслеЭчеил ом ситуациях. Вам требовалось создать класс рабочих
Как и в случае наследования, класса Worker и реализуем пчел, которые могут выполнять два задания.
для реализаиии инмерфейса иимерфейсы INectarCollector
Интерфейсы дают возможность получить класс,
используется Эвоемочие. IStingPatrol. ^
выполняющий произвольное количество заданий.
class NectarStinger^^^WorkerQlNectarCollector, Скажем, у вас есть метод P a t r o l T h e H i v e ( ) ,
IStingPatrol { ■\Анмердрейсы работающий с объектом S t i n g P a t r o l ,
public int AlertLevel { п еречи сляю т ся и метод C o l l e c t N e c t a r () для объекта
y' ^ get { return alertLevel; J через з а п я т у ю . N e c t a r C o l l e c t o r . При этом хочется, чтобы
.ласс
’e c t a r S t i n g ii^.r
er ^ класс S t i n g P a t r o l мог наследовать от
адаем вспо- класса N e c t a r C o l l e c t o r ИЛИ наоборот, ведь
ргдЬИс int StingerLength {
хогательное в каждом классе есть открытые методы и свойства,
get { return StingerLength;
,оле для сбой- отсутствующие у другого. А теперь подумайте, как
тва A l e r t L e v e l set {
можно создать класс, экземпляры которого могут
StingerLength = value;
I методе быть переданы обоим методам. Есть какие-нибудь
^ o o k F o r E n e m ie s Q - } идеи?
КаждоЩ Проблему решают интерфейсы. Создав ссылку
методу 6 1ЛН- public bool LookForSnemies() {•■•} IStingPatrol, вы можете указать на любой объ­
щ лерфейсе со- public int SharpenStinger(int length) екг, реализующий I S t i n g P a t r o l , какому
отб етстба “
е т т глоЪ в бы классу этот объекг ни принадлежал. Можно
к лассе. Иначе указать, как на объекг S t i n g P a t r o l , так и на
pxiblic void FindFlowersO {...}
у ^ рограм м а не объекг N e c t a r S t i n g e r ИЛИеще на что-нибудь.
public void GatherNectarO {...}
5 у д е т KOfAnu При этом вы можете использовать все методы и
лироват м я. pviblic void R e t u m T o H i v e O {.•.}
свойства, которые являются частью интерфейса
I S t i n g P a t r o l , независимо от типа объекта.
Вели врагов нем Разумеется, вам придется создать новый класс,
С оздан н ы й в а м и о б ь е к т пчела прячем жало, который и будет реализовывать интерфейс. Так что
N e c ta r S tin g e r с м о ж е т б ы - оспомогамельное этот инструмент не позволит избежать создания
п о л н я т » р а б о т у пчел как 1^оле StingerLenqtk дополнительных классов или сократить количество
и з к л а с с а N e c ta r C o lle c to r , т а к м еняем свое
дублирующегося кода. Он всего лишь дает возмож­
и из к л а с с а S t i n g P a t r o l .
значение.
ность получить класс для выполнения нескольких
работ без привлечения наследования. Ведь наследу­
ются все методы, свойства и поля другого класса.
Класс, реализующий интерфейс так же, как и обычный, соз­
дает экземпляры при помощи оператора new и использует ме­ Как при работе с интерфейсами избежать ду­
тоды: блирующегося кода? Можно создать отдельный
класс с именем s t i n g e r и кодом, относящимся
N e c t a r S t i n g e r b ob T h eB ee = new N e c t a r S t i n g e r ( ) ;
к укусам или сбору нектара. После чего объекгы
N e c ta r S tin g e r H N e c ta r C o lle c to r
b o b T h eB ee. L o o k F o rE n em ies{);
смогут создать закрытый экземпляр s t i n g e r ,
bobT heB ee. F in d F lo w e r s(); и для сбора нектара будут использовать его методы
и задавать его свойства.

288 глава 7
интерфейсы и абстрактные классы

Классы, {реализующие интерфейсы, дол)кны включать ВСЕ


методы интерфейсов
Реализация интерфейсов означает, что в классе должны присутство­
вать все объявленные в интерфейсе методы и свойства. Если это не
так, программа не компилируется. Если класс реализует несколько ин­
терфейсов, он должен включать в себя все свойства и методы каждого
их них. Впрочем, можете не верить на слово... У праж нение
Г -
О Создайте новое прилож ение и добавьте в н его класс IS tin g P a tro l.c s
В файл введите код интерфейса I S t i n g P a t r o l , приведенный пару страниц назад. Програм­
ма при этом будет компилироваться.

О Добавьте к проекту класс Вее


Но пока не добавляйте ни свойств, ни методов. Заставьте этот класс реализовывать интер­
фейс IStingPatrol:
c la s s Bee : IS tin g P a tr o l
{
}

О П о пы тайтесь ском пилировать проградш у


Выберите команду Rebuild в меню Build. Компилятор не запустится:

Error List
!O 4 Errors 0 Warnings 0 Messages
Description
Oi 'IStingPatroLExperimervt.Bee' does not implement interface member ’IStingPatroLExperiment.!StingPatrol.ShsrpenStingef(int)'
©2 IStingPatroL&tpsriment.Bee' does not implement interface member 'IStingPatrQl_Experiment,IStitT§Patrol.LooicFQrEnem(e50‘
©3 'IStingPatroLExperiment.Bee' does not implement interface member '!StingPatrol_E](perrment.lStingPatro!.Stin§erLength‘
0 4 'IStingPatroLExperiment.Bee' does not implement interface member ’IStingPatrol_Experiment.lStingPatrol.AtertLever

лизовали каждый метод интерфейса.

Добавьте в класс Вее методы и свойства


Добавьте методы L o o k F o r E n e m i e s и S h a r p e n S t i n g e r . Пока они не должны выполнять
никаких функций, они должны просто компилироваться. Добавьте метод чтения для пере­
менной A l e r t L e v e l типа i n t и методы чтения и записи для переменной S t i n g e r L e n g t h .
После этого программа снова начнет компилироваться!

дальше * 289
поваляем дурака

Учимся работать с интерфейсами


Использовать интерфейсы легко. Вы поймете это, немного по- ^ I
практиковавшись. Начните с создания проекта Windows Forms ^ jTLp’aX H eH ue.
Application. Перетащите на форму кнопку и приступим! '

Это класс T a l l G u y (Высокий парень) и код для кнопки, нажатие которой создает объект
и вызывает его метод T a l k A b o u t Y o u r s e l f () (Рассказ о себе). Пока ничего нового:
c la s s T a llG u y {
p u b lic str in g Nam e;
p u b lic in t H eig h t;

p u b lic v o id T a lk A b o u tY o u r se lf0 {
M e s s a g e B o x . S h o w ( "М еня зовут " + Nam e + " я ростом "
+ H eig h t + " с м ." );
}
}
p r iv a te v o id b u tto n l_ C lic k (o b je c t sender, E ven tA rgs e) {
T a llG u y ta llG u y = new T a l l G u y () { H eig h t = 190, Nam e = "Джимми" };
ta llG u y .T a lk A b o u tY o u r se lf();
1

© Создадим и н т е р ф е й с I C lo w n .

Вы уже знаете, что все элементы интерфейса должны быть открытыми. Проверим это ут­
верждение на практике. Создайте новый проект и объявите интерфейс:
i n t e r f a c e I C lo w n
М одиф икат ор public
Теперь попробуйте объявить внутри него закрытый метод: оку т ри и н т ер ф ей ­
са писат ь не нужно,
p r i v a t e v o i d Honk О ; дост уп ко всем его
м ет одам и свойст вам
Выбрав команду B u ild»B uild Solution, вы увидите сообщение: им еет ся по ум олчанию

01 The modifier 'private' is not valid for this item I

Удалите м одиф икатор p r i v a t e , сообщение об ошибке исчезнет.

© Попробуйте создать интерфейс I C l o w n самостоятельно и заставьте класс T a llG u y его реа­


лизовывать. Начните с создания класса I C l o w n . c s .
Интерфейс I C l o w n должен иметь не возвращающий значений и не имеющий параметров ме­
тод H o n k (Гудок) и предназначенное только для чтения свойство F u n n y T h i n g l H a v e (Смотри,
что у меня есть) типа s t r i n g с методом чтения, но без метода записи.

290 глава 7
интерфейсы и абстрактные классы

Q Вы записали интерфейс вот так? Это пример интерфейса, имею щ е­


го метод чтения, но не им ею щ е­
го метода записи. Помните, что
in te rfa c e IC lo w n интерфейсы не м огут им ет ь полей,
{ но когда вы реализует е свойство,
str in g F u n n yT h in glH ave { get; } предназначенное только для чтения
v o id H onk();
аля остальных объектов оно выгля­
дит как поле.
}

Заставим класс T a l l G u y реализовывать I C l o w n . Помните, что после двоеточия сна­


чала ставится имя базового класса (если такой имеется), а затем список интерфей­
сов через запятую. В данном случае базовый класс отсутствует, поэтому напишем:
c l a s s T a llG u y : IC lo w n ^ — Реализовывать интерфейс IClown будет класс Tailduy.

Убедитесь, что остальной код класса остался без изменений. Выберите команду
Build Solution в меню Build, чтобы построить решение. Появятся два сообш,ения об
ошибке. Вот одно из них: , „ „
Объявив, что класс
I TallGuy реализует ин-
'T a llG u y ' d o e s n o t im p lem en t i n t e r f a c e терф&ис IClown, вы
m e m b er ' I C l o w n . H o n k ( ) ' пообещали добавить
в эт от интерфейс все
методы и свойства, но
не сделали этого!

Как только вы добавите все свойства и методы, упомянутые в интерфейсе, сообш;е-


ние об ошибке пропадет. Поэтому добавьте предназначенное только для чтения
свойство F u n n y T h i n g l H a v e с методом чтения, который всегда возвращает строку
большие ботинки. И добавьте метод H o n k ( ) , вызывающий окно с надписью Би­
би!
Вот, как это выглядит:

p u b lic strin g F u n n yT h in glH ave {


get { retu rn "большие б о ти н к и "; '

} Интерфейсу нужен открытый


метод Нопк, не возвращающий
p u b lic v o id HonkO {
значения, но при эт ом совершен­
но не важно, что будет делать
M e s s a g e B o x . S h o w ("Б и -б и !' эт от метод. Если метод п р и ­
} сут ст вует и сигнатура совпада­
ет , программа будет компилиро­
ваться.
Теперь ничто не мешает компиляции кода! Сделайте
так, чтобы кнопка вызывала метод Honk () объекта
T a llG u y .

дальше > 291


интерфейсы не создают объекты

Ссылки на интерфейс Можно создать массив


но получит ь новш оЗъекты из ин
терфейса вы не
Предположим, у вас есть метод, которому требуется объ­ в а Л с т а т о ч н о будет
ект, умеющий выполнять метод F i n d F l o w e r s (). Под новые экземпляры классов, реализу ^
это условие подходит любой объект, реализующий ин­ шик интерфейс (Worker. Результд
терфейс I N e c t a r C o l l e c t o r . Это может быть объект т ом будет м кранящии набор
а с с и в ,

W o r k e r , объект R o b o t или объект D o g .


различнык объектов! \

Вы можете сослаться на этот объект и быть уверенными


в наличии нужных вам методов.

Это не работает...
IS ti ng Pat ro l dennis = n e w I S t i n g P a t r o l ();
Q I Cannot create an instance of the abstract class or interface |

Для интерфейсов ключевое слово n e w не работает, и это имеет смысл, ведь


методы и свойства не имеют реализации. Объектам просто неоткуда было бы
узнать, как себя вести.

...зато работает это:


N e c t a r S t i n g e r fred = n e w N e c t a r S t i n g e r ()
/ ^ I S t i n g P a t r o l g e o r g e = fred; объект може 1^
Помните, вы могли J, „ ^ ^ выполнят^ много
В первой строчке при помощи оператора n e w создается ссылка сЬункции, но ис-
передать ссылку
BLT любому клас­ с именем Fred, указывающая на объект N e c t a r S t i n g e r . А олм уя инт ер-
су, который дол­ сЬейснукз ссылку^
Со второй строчкой все намного интереснее, так как здесь п р и h получаете
жен ссылаться на
сэндвичи, так как пом ощ и и н тер ф ей са I S t in g P a t r o l создается новая ссы лоч­ дост уп т олько
класс 8LT являет ­ н а я п е р е м е н н а я . На первый взгляд код выглядит несколько к м ет одам , упо-
ся производным от странно. Но взгляните: мянуп^^>^^ в
класса Sandwich? терфейсе.
То же самое проис­ N e c ta r S tin g e r g in g e r = fred ;
ходит и в данном
случае, вы можете Третий оператор создает новую ссылку на объект N e c t a r S t i n g e r .
использовать объ­ Имя этой ссылки g i n g e r , и она указывает на тот же самый объ-
ект NectarStinger
_____ ект, что и ссылка f r e d . Оператор g e o r g e использует интерфейс
в любом методе или i s t i n g P a t r o l аналогичным способом.
операторе, ожида-
юш,ем istingPatrol.
Что же случилось?
Тут только один оператор n e w , так что появляется т о л ь к о о д и н
н о в ы й о б ъ е к т . Второй оператор создает ссылочную перемен­
ную g e o r g e , которая может указывать на экземпляр л ю б о г о
класса, р еал и зую щ и й и н т е р ф е й с i s t i n g P a t r o l .

292 глава 7
интерфейсы и абстрактные классы

Ссылка на интерфейс аналогична ссылке на объект

Вы уже знаете, как выглядят объекты в куче. Интерфейсная


ссылка является всего лишь еще одним способом обратиться
к уже знакомым объектам. Это очень просто!

О Создадим пару пчел


Вы уже не раз это делали.
S tin g P a tr o l b i f f = new S t i n g P a t r o l О ;
N e cta rC o llecto r b erth a = new N e c t a r C o l l e c t o r ( ) ;

т
Пусть класс StingPatrol реализует интерфейс
IStingPatrolj а класс NectarCollector — интерфейс
INectarCollector.

О Добавьте ссы лки на is tin g P a tro l и IN e c ta rC o lle c to r


Интерфейсные ссылки ничем не отличаются от ссылок
любого другого типа.
istin g P a tr o l d efender = b iff;
IN e c ta r C o lle c to r cu tieP ie = bertha;

Эти два оператора используют интерфе(лсы для


создания новых ссылок на
Инт ерф ейсны е ссылки м о гу т указы ват ь >^олько
н а экземпляр./ классов, реализую щ их инт ерф ейс.

о MHTepqiencHafl ссы лка позволяет сохранить объект


Объект исчезает, как только на него не остается ссылок.
Но никто не говорит, что все ссылки должны принад­
лежать одному типу! И нтерфейсные ссылки позволяют
спасти объект от удаления.
b iff = n u ll;
Этот объект не
исчезает из-за
ссылки defender.

Назначьте интерд>ейсной ссы лке новый экземпляр


На самом деле вам не нужны ссылки на объекты, можно
создать новый объект и сопоставить его с ссылочной ин­
терфейсной переменной.
IN e c ta r C o lle c to r g a th e r e r = new N e c t a r S t i n g e r ( ) ;

дальше > 293


мы ожидаем большое наследство

Оператор is
_ Часто
Иногда требуется понять, реализуется ли интерфейс определенным - ^ а Д а Б а е М ы е ------
классом. Предположим, что все рабочие пчелы представлены в виде Б оц|эос;ь1
массива Bees. В массиве можно хранить переменные типа W orker, так
как все рабочие пчелы принадлежат классу W orker или производным Свойства, добавляемые в
от него классам. интерфейс, выглядят как авто­
матически реализуемые. Неуже­
Но какая из рабочих пчел может собирать нектар? Для ответа на этот во­
ли при реализации интерфейса
прос нужно узнать, реализует ли класс интерфейс I N e c ta r C o ll e c to r .
я могу использовать только
Это можно сделать при помощи оператора i s . такие свойства?

Рабочие представлены в виде


массива Workers. Оператор
IS позволяет определить Массиб рабочих пчел
О ! Вовсе нет. Свойства внутри
интерфейсов действительно на­
т ип пчелы. поминают своим видом автомати­
чески реализуемые — посмотрите
Worker [] bees = new Worker [3]; на свойства Job и ShiftsLeft
IS определяет
н а л и ч и е методой в iworker на следующей стра­
bees[0] = new NectarCollector0; Тсвойст в, нужных нице. Реализовать свойство Job
для выполнения можно так:
bees[1] = new StingPatrol0; у к а з а н н о й работы.
bees [2] = new NectarStinger0; public Job { get;
private set; }
for (int i = 0; i < bees.Length; i++)
,5 срабниблеил UH- Модификатор private set
{ ^ 1^ ерф ейсы и дру ставится, так как автоматические
if (bees [i] /^i^INectarCollector)2Me типы данных. свойства требуют наличия как ме­
тода чтения, так и метода записи
(пусть даже и закрытого). Но вы
{ Эта запись означает, что если можете написать и другой код:

public job { get {


return "Бухгалтер"; } }
bees [i].DoThisJob("Nectar Collector", 3)
И программа все равно будет
f компилироваться. По желанию
Теперь,
можно добавить и метод записи.
ч т о эта
(При реализации же через автома­
г г ;Г и -^ тическое свойство вы всего лишь
решаете, будет ли метод записи
открытым или закрытым.)

Ш ТУРМ
Класс, не производный от класса worker, но реализующий ин­
терфейс INectarCollector, может ВЫПОЛНЯТЬ работу по сборке
нектара! Но так как класс Worker не является для него базовым, вы
не можете поместить его в массив с другими пчелами. Подумайте,
каким образом можно получить массив из пчел обоих видов?

294 глава 7
интерфейсы и абстрактные классы

На д и а гр а м м е кл ас со в
интерфейсы U наследование насл едование интер­
Когда один класс наследует от другого, он получает все его ме­ ф ей со в обозначается
тоды и свойства. Н аследование и н терф ей сов происходит еще пунктирной линией.
проще. Так как в интерфейсах отсутствуют тела методов, вам
уже не придется заботиться о вызове конструкторов и методов
базового класса. Наследующие интерфейсы просто накапливают
в себе методы и свойства своих родителей.
i
(interface)
O m созданного ^ IW orker
i n t e r f a c e IW o r k e r НЛММ интерфей
ca M orker м о ­ Job
{ г у т наследовать ShiftsLeft
все остальные
s t r in g Job { g e t; } интерфейсы.
DoThisJobO
in t S h ift s L e ft { ge t; } Worl<OneShift()

v o id D o T h is J o b (s t r in g jo b , in t s h ift s )

v o id W o r k O n e S h iftO

(interface) (interface)
Isting P atrol IN ectarC ollector
Класс реализует ^ м е т о д ы u cßoücmßa StingerLength Nectar
EnemyAlert
ICnacc, реализующий интерфейс, должен включить в себя все
свойства и методы этого интерфейса. В ситуации, когда один ин­
терфейс наследует от другого, все их свойства и методы также SharpenStingerO FindFlowersO
должны быть реализованы. Looi<ForEnemies() GatherNectarO
StingO RetumToHiveO

interface istingPatrol ; IWorker


{ Уже знакомый вам интерфейс
int AlertLevel { get;} istingPatrol теперь наследу­
ет от интерфейса M orker.
int StingerLength { get; set;}
Изменение кажется совсем
bool LookForEnemies(); небольшим, но для всех клас -
int SharpenStinger(int length); сов, реализуюи^их istingPatrol,
ситуация коренным образом
поменялась.
еис
(interface)
IW orlter
Job
ShiftsLeft
•■•HO и методы интерфейса
M orker, от которого п р о ­ DoThisJobO
исходит наследование. ■ WorkOneShiftO

дальше > 295


скрестить бульдога с носорогом

RoboBee 4 0 0 0 функционирует без меда

Создадим пчелу новой формации, КоЬоВее 4000, работа­ R oboB ee


ShiftsToWork
ющую на топливе. «Привив» ее интерфейсу интерфейс ShiftsWorked
11^огкег, вы даете ей возможность делать все то, что де­ ShiftsLeft
лает обычная пчела. Job

DoThisJobO

class Robot Г Г Г г Х Г к о М .
{
public void ConsumeGasO Класс RoboBee наследиет от
/г’ейлизует
} а н те ^ ф е а с (Worker. В итоге

class RoboBee м о ж е т выполнять ра6о~


Robot, IWorker f r. - обычной пчелы. ^
{
Класс RoboBee
private int shiftsToWork; реализует
private int shiftsWorked; все методы
интерфейса
public int ShiftsLeft iWorker-
{get {return shiftsToWork - shiftsWorked;}}
public string Job { get; private set; }
public bool DoThisJob(string job, int shiftsToWork){...}
public void WorkOneShiftO {...}
}

Остальные классы нашего приложения не «увидят» функ­


циональной разницы между пчелой-роботом и обычной Любой класс может
пчелой. Оба эти класса реализуют интерфейс ХКогкег, с
точки зрения программы действуют, как рабочие пчелы. реализовывать ЛЮБОЙ
Отличить объекты друг от друга позволит оператор интерфейс, если он
if (workerBee is Robot) {
Оператор 15 показы
вает, какой класс или
реализует все методы
// мы узнали, что workerBee
интерфейс реализует
^огкегВее и каково его и свойства этого интер­
}
// это объект Robot положение в иерархии
наследования. фейса.
296 глава 7
интерфейсы и абстрактные классы

1$ показывает Вам, что именно объект реализует


а$ показывает компилятору, как обработать э т о т объект
Иногда требуется вызвать метод, полученный объектом в процессе реализации
интерфейса. Но что делать, если вы не знаете, нужному ли типу принадлежит
объект? На помощь вам придет оператор 1б. А оператор аз позволит преобра­
зовать один совместимый ссылочный тип в другой.

IWorker[] bees = new IWorker[3];


beesEO] = new NectarStinger{)
Cor.
bees [1] = new RoboBeeO;
bees [2] = new Worker 0 ; Мы не можем вы­
Mw перебираем пчел.-- зывать для пчел м е ­
тоды интерфейса
INectarCollector- Ведь
for (int i = 0; i < bees.Length; i++) { пчелы принадлежат
т ипу M a rker и ничего
не знают о методах
C ^ if (bees[i] is INectarCollector) { INectarCollector.
- .M проверя­ INectarCollector thisCollector;
ем, реализу­
ет ли пчела
интерфейс thisCollector = bees [i] INectarCollector;
INectarCollector.
thisCollector.GatherNectarо ;
f
**' Теперь можно вызывать методы
интерфейса INectarCollector.
Возьми в руку карандаш
Д ля каждого из показанны х справа о пе ратор о в напиш ите, при каком значении счет­
чика пчел i он будет им еть значение t r u e . Зачеркните слева две строчки, которые
не компилируются.
Г-, „
i w o r k e r [] B e e s
-гг. 1 Г01
= new i w o r k e r [ 8 ] ;
1. (B e e s[i] is IN e c ta r C o lle c to r )

B e e s [0 ] = new N e c t a r S t i n g e r 0 ;
B e e s[l] = new R o b o B e e O ; ................................................................................................
B e e s [2] = n e w W o r k e r 0 ; 2 . (Bees[i] is istingPatrol)
B ees[3 ] = B ees[0] as IW orker;
B e e s [4] = i s t i n g P a t r o l ;
B e e s [5 ] = n u l l ;
3. (B e e s[i] is IW orker)
B e e s [6 ] = B e e s [0 ] ;
B e e s [7] = n e w I N e c t a r C o l l e c t o r {) ;

дальше > 297


не верь глазам своим

Кофебарка относится к Приборам A ppliance


Pluggedin
Color
Для задачи экономии электроэнергии функции отдельных приборов
не имеют значения. Вас заботит только то, что все они потребляют
электричество. Поэтому при написании программы учета электро­ ConsumePowerO
энергии можно ограничиться классом A p p lia n c e (Прибор). Но что­
бы отличить кофеварку от духовки, потребуется иерархия классов.
Методы и свойства, описывающие поведение кофеварки и духовки,
будут помещены в классы Cof fee M a k e r и Oven. Эти классы будут про­
изводными от класса A p p lia n c e , содержащего общие для них методы
и свойства.
C offeeM aker Oven
CoffeeLett Capacity
p u b l i c v o i d M o n it o r P o w e r ( A p p l i a n c e a p p l i a n c e ) {
// к о д д о б а в л е н и я д ан н ы х в домашнюю
// б а зу потребления эн ерги и 33 ^ ^ ^ должен
FlllWithWaterO PreheatO
} Э т о т код отслеживает, ^^^^лежиТающий MakeCoffeeO HeatUpO
сколько электроэнергии потребление ReheatO
тредцется для работы \ электроэнергии
кофеварки. \ g

C o f f e e M a k e r m i s t e r C o f f e e = new C o f f e e M a k e r ( ) ; Такое поведение вы


M o n ito r P o w e r (m is te rC o ff е е ) ; уже наблюдали в
предыдуш,ей главе,
Метод MonitorPowerQ т реби- когда передавали
ет ссылки на обьект Appliance методу, ожида­
V но ему можно передать ссыл- ' ющему ссылки на
Sandwich, ссылки
ку misterCofFee, так как класс
СоггееМакег является производным на BLT.
от класса Appliance.
Возьми в руку карандаш
*0Ш0НИ6 каких значениях счетчика пчел i оператор ы спр ава во звра­
щ аю т значение t r u e . С л е в а зачеркнуты две строчки, препятствую ­
щ ие компиляции.

IW ork er[] B e e s = new I W o r k e r [8 ]; 1. (Bees [i] i s I N e c ta r C o ll e c to r )


B ees[0] = new N e c t a r S t i n g e r 0 ; J
B e e s[l] = new R o b o B e e O ; Метод о и &
B ees[2 ] = new W o r k e r O ; NectarStinger()
реализует 2. ( B e e s [ i] is is tin g P a tro l)
B ees[3 ] = B ees[0] as IW orker; интерфейс
«iBiBoaili'iil-] — lO tei.n g P a faiwl-;'»“ istingPatrol.'^— q ^
B e e s [5 ] = n u ll;
B e e s [6 ] = B e e s [0 ] ; 3. ( B e e s [ i] i s IW orker)
-RoQia.r-Tl- 1 )

298 глава 7
интерфейсы и абстрактные классы

Восходящее приведение
Когда вы используете производный класс вместо базового, например, ссылаясь на кофеварку вместо
прибора, - это называется в о с х о д я щ и м п р и в е д е н и е м ( u p c a s t in g ) . Это очень мощный инструмент,
который вы получаете, построив иерархию классов. К сожалению, он работает только с методами и
свойствами базового класса. Другими словами, рассматривая кофеварку как прибор, вы не можете
заставить ее сварить кофе MakeCoffee() или налить воду FillWithWater(). Зато вы жожеше определить,
включена ли она в розетку, так как это состояние относится ко всем приборам (и именно поэтому свой­
ство P l u g g e d i n помещено в класс A p p l i a n c e ) .

Q Создадим объекты
Классы C o f f e e M a k e r и O ven создаются обычным способом:
C offeeM ak er m i s t e r C o f f e e = new C o f f e e M a k e r О ; ? Д л я начала получиМ
Т экземпляры оо-ьектоо
O ven o l d T o a s t y = n e w O v e n {) ; __ J O v e n и C offeeM aker.

@ A вдруг ном потребуется массив приборов?


Объект C o f f e e M a k e r нельзя поместить в массив O v e n [ ] , а объекту O v e n не место
в массиве C o f f e e M a k e r [ ]. Но они прекрасно уживутся в массиве A p p l i a n c e [ ]:
A p p l i a n c e [] k itch en w a re = new A p p l i a n c e [ 2 ] ;

k i t c h e n w a r e [0 ] = m isterC o ffee; МаССив,


ухОЗООЛЯСг^ '^75/?1лл.СЯ
k i t c h e n w a r e [1] = o ld T o a sty ; ^ Зля Э и хоб к и ,
место как ол^^р
^ а к U Эля кофебе^рки,
О Н е лю б о й прибор является духовкой
Ссылаясь на объект A p p l i a n c e , вы получаете доступ т о л ь к о к методам и свой­
ствам приборов. Вы н е м о ж е т е при этом воспользоваться методами и свойства­
ми объекта C o f f e e M a k e r даже если вы знаете, что речь и в самом деле идет о кофе-
powerConsumer -
варке. Поэтому корректны следующие операторы: ссылка
A p p l i a n c e pow erC onsum er = new C o f f e e M a k e r О ; класса Appliance
на ооьект
p o w er C o n su m e r .C o n su m eP o w e r О ; ^^'^рочка не будет КОМ- (CoffeeMaker.
пилироваться, т ак как
Но написав вот такую строчку: ^ м ет од pow erC onsu m er ра -
p o w e r C o n su m e r .M a k e c o ffe e О; ------------- сТвТ2 Т з Т е Т т а Х р Х п с е .
вы получите сообщение об ошибке:

'A p p lia n ce ' does not c o n ta in a 1 '’Ч Щ Р Г '


^ d e fin itio n fo r 'M a k eC o ffee' I ^^еМ бУ '

так как после восходящего приведения можно пользоваться только методами


и свойствами одного уровня с ссылкой, которую вы используете для доступа
к объекту.

дальше > 299


вверх легко, а вот вниз — рискованно

Нисходящее приведение
Теперь вы знаете, что рассматривая кофеварку и духовку как приборы, вы лишае­
тесь доступа к их собственным методам и свойствам. К счастью, суш;ествует проце­
дура н и с х о д я щ е г о п р и в е д е н и я ( d o w n c a s t in g ) . Узнать, относится ли рассматрива­
емый объект A p p lia n c e к классу C offeeM aker, можно при помощи оператора i s . В о т ссылка
Теперь ничто не мешает вам вернуться от класса A p p lia n c e к классу C offeeM aker АррИапсе,
при помощи оператора a s . указывающая
на обьект
CoffeeMaker
О Н ачнем с объекта C o ffe e M a k e r
Вот код, который мы использовали для восходящего приведения:
A p p l i a n c e pow erC on sum er = new C o f f e e M a k e r ( ) ;
p o w er C o n su m e r .C o n su m eP o w e r 0 ;

О H o как превратить Appliance обратно в класс C o ffe e M a k e r?


Для начала воспользуйтесь оператором i s для проверки совместимости.
if (pow erC on sum er i s C offeeM ak er)
/ / м ы можем осуществить нисходящее приведение! Ссылка javaJoe ука -
зывает на т от же
обьект, что и ссыл-
О Теперь когд а вы точно знаете, что ваш прибор, — коя>еварка...
__ I___ I_____ ка powerConsumer.
... вы можете воспользоваться оператором a s для нисходящего приведения, ‘^'^косится
чтобы снова получить доступ к методам и свойствам класса C offeeM aker. ^
Так как класс C offeeM aker наследует от класса A p p lia n c e , доступ к мето- вызвать
дам и свойствам базового класса у него тоже сохранится. MakeCoffeeQ.
if (pow erC onsum er i s C offeeM ak er) {
C offeeM aker ja v a J o e = p o w e r C o n s u m e r a s C o ffe e M a k e r ;
j a v a J o e . M akeC offе е ( ) ;

Неудачное нисходящее приведение бозбращаеш null


А что произойдет, если при помощи оператора a s попытаться преобразовать
объект Oven в объект C offeeM aker? Вы получите нулевой результат, и про-
грам м а п р ек р а т и т работу, т
if (pow erC onsum er i s C o ffe e M a k e r ) { Z' относится к классу Oven. По-
/ эт ому при попытке осуще­
O v e n f o o d W a r m e r = p o w e r C o n s u m e r as Oven; ст вит ь нисходящее приведе­
«етмшmil ние ссылка foodVJarmer даст
foodW arm er. P r e h e a t ( ) ; null. Вот что произойдет при
попытке использовать п у ­
} ст ую ссылку... ч

Nul№teferenceExceptkm wm ifftwreiled
I
300 глава 7
интерфейсы и абстрактные классы

Нисходящее и Восходящее приведение интерфейсов


Вы уже видели, что операторы i s и a s работают с интерфейсами. Значит, для них возможны операции
восходящего и нисходящего приведения. Добавим интерфейс I C o o k s F o o d к любому классу который
умеет подогревать еду. Добавим также класс M i c r o w a v e (Микроволновка). Наряду с классом O v e n он
будет реализовывать интерфейс I C o o k s F o o d . В результате вы получите три способа доступа к объекту
O v e n . А функция I n t e l l i S e n s e подскажет, какие операции вы можете производить в каждом из этих
трех случаев:
Любой класс,
O ven m is t e r T o a s t y = new O ven О реализующий
интерфейс
m iste rT o a sty . 1Соо1аРоо1^,
•m Capacity lint Oven.Capacit/ относится к
ж Color (interface) приборам, ко­
: ConsumePower
ICooksFood торые могут
Сразу после вво­ misterToasty — это ссылка Capacity разогревать
да мочки ф унк- Equals класса Oven, указываю- ИeatUp() еду.
ця IntelliSense GetHashCode ‘ ш,ая на обьект Oven, так
выведет список GetType что у вас есть доступ ко HeatUpO
всея возможных ' HeatUp всем свойствам ц м ет о - ReheatO
членов. Я * Pluggedin , дам этого класса... но это
Preheat
[ссылка менее общего типа, _
j поэт ому указывать она
Reheat
может только на объекты
класса Oven.
IC ooksF ood cooker; Microwave
Capacity Capacity
if (m ister T o a sty is IC ooksF ood )

cooker = m iste r T o a sty as IC ooksF ood;


PreheatO HeatUpO
cooker. HeatUpQ ReheatO
ReheatO MakePopcornO
lint ICooksFood.Capaclty
Equals Ссылка cooker принадлежит
GetHashCode интерфейсу ICooksFood
GetType и указывает на т от же
♦ HeatUp обьект Oven. Она дает Три ссылки на
* Reheat доступ только к членам
ToString
интерфейса ICooksFood, но
при эт ом может указы­
один и тот же
A p p lia n c e pow erC on sum er;
вать на обьект Microwave.
объект дают до­
if (m ister T o a sty

pow erC on sum er


is A p p lia n c e)

= m iste rT o a sty ;
ступ к различным
pow erC onsum er. методам и свой­
Ссылка powerConsumer [icolor Appllance.Color ствам, в зависи­
принадлежит клас­ ConsumePower
су АррИаме. Она дает
доступ к открытым
Equals мости от своего
GetHashCode
полям, методам и свой­
ствам этого класса. Но GetType типа.
при желании вы можете ^ Pluggedin
с ее помощью сослаться "^ T o S trin g
на обьект CoffeeMaker. дальше > 301
неглупые вопросы
_ Часто
^аД аБ аеМ ы е

Почему восходящее приведение Q ; Нет, компилятор не позволит вам это Можно ли реализовать интерфейс,
можно осуществлять всегда, а нисхо­ сделать. Интерфейс не должен содержать не вводя много кода?
дящее нет? операторов. Оператор в виде двоеточия, ре­
ализующий интерфейс, не имеет отношения
! Конечно! Средства ИСР позволяют
Q « Компилятор может предупредить к оператору, использующемуся при насле­
реализовать интерфейс автоматически.
вас о том, что восходящее приведение довании классов. Реализация интерфейса
Начните вводить код класса:
происходит неправильно. Более того, эта ничего не меняет в кпассе. Она всего лишь
операция не работает только когда вы гарантирует присутствие в кпассе методов,
c la ss
пытаетесь сопоставить объект классу, от перечисленных в интерфейсе.
M ic ro w a v e IC ooksF ood
которого не происходит наследования,
{ }
или интерфейсу, который этим объекгом Интерфейс выглядит как наложе­
не реализуется. Компилятор распознает ние ограничений, ничего не меняющих Щелкните на ICooksFood — под буквой I
невозможность подобной операции и вы­ в самом классе. Зачем мне его исполь­ появится маленькая полоска. Если задер­
водит сообщение об ошибке. зовать? жать на ней курсор, появится значок:

При этом компилятор не может проверить,


Q ; Если класс реализует интерфейс, [interface IC ooksFood
допустимо ли нисходящее присваива­
интерфейсная ссылка может указывать
ние, которое вы пытаетесь осуществить. TC ooksFood
на любой экземпляр этого класса. Это по­
Справа от оператора a s может распола­
зволяет оботись одним ссылочным типом,
1 Если щелкнут ь на значке
гаться любое имя класса или интерфейса. не удается, используйте
который работает с набором объектов
В случае, когда нисходящее присваивание комбинацию C trl-точка.
различного вида.
невозможно, оператор a s возвращает
значение n u l l . Компилятор допускает Щелкните на значке и выберите команду
Вот маленький пример. Лошадь, буйвол,
такое поведение, потому что бывают Implement Interface ‘ICooksFood’. Это ав­
мул и вол могут тащить телегу. Но в на­
случаи, когда именно оно и требуется. томатически добавит все члены, которые
шем симуляторе зоопарка H o r s e , Ох,
M u l e и s t e e r — разные классы. Для еще не реализуют интерфейс. Каждый из
Кто-то говорил мне, что интерфейс аттракциона катания вы хотите создать них снабжен оператором t h r o w s , о кото­
подобен контракту, но я не понимаю, массив животных, которые могут тащить ром мы подробно поговорим в главе 10.
почему. телегу. Но поместить в один массив живот­

о ! Да, в определенной степени это дей­


ствительно так. Заставляя класс реализо­
вывать интерфейс, вы как бы обещаете
ных из разных классов можно только в слу­
чае, когда все они наследуют от общего
базового класса. В нашем случае это
условие не соблюдается. Что же делать? Интерфейс напоми­
компилятору поместить в него определен­
ные методы, и компилятор следит, чтобы Вам потребуется интерфейс i P u l l e r нает список, с кото­
вы это обещание выполнили. с методами, отвечающими за перемеще­
ние телеги. Теперь вы можете объявить рым сверяется ком­
Хотя намного нагляднее представить массив:
интерфейс в виде списка. По этому пилятор, проверяя,
списку компилятор проверяет присут­ I P u l l e r [] p u lle r A r r a y ;
ствие в кпассе всех методов, упомянутых реализует ли ваш
в интерфейсе.
В этот массив можно поместить ссылку на класс определенный
Могу ли я поместить тело метода любое животное, реализующее интер­
в интерфейс? фейс Х Р и Н е г . набор методов.

302 глава 7
интерфейсы и абстрактные классы

Расширьте интерфейс IClown при помощи реализующих его IClown


классов. (interface)
ажненке FunnyThinglHave
Начните с интерфейса IC lo w n из Упражнения! на с. 290:
HonkO
i n t e r f a c e IC lo w n {
s t r i n g F u n n yT h in glH ave { get; }
v o id H onk{);
}

^ Создайте производный интерфейс I S c a r y C l o w n со свой­ FunnyFunny IScaryClown


(interface)
ством S c a r y T h i n g l H a v e типа s t r i n g и методом чтения, FunnyThinglHave
ScaryThinglHave
но без метода записи, а также с не возвращающим значе­
ния методом S c a r e L i t t l e C h i l d r e n ( ) .
HonkO ScareLiffleChildrenO

О Создайте классы для забавных и страшных клоунов:


★ Класс F u n n y F u n n y с закрытой строковой пере­
менной, хранящей список забавного. На основе
параметра F u n n y T h i n g l H a v e конструктор за­
дает значение закрытого поля. Метод Н о п к () Scarygpary..
выводит сообщение «Би-би! У меня есть », далее ScaryThinglHave

следует возвращаемое значение метода записи


FunnyTh i n g I H ave. ScareLiffleChildernO

★ Класс S c a r y S c a r y хранит в закрытой переменной це­


лое число, переданное конструктором в виде параметра
n u m b e r o f S c a r y T h i n g s . Метод чтения S c a r y T h i n g l H a v e
возвращает строку с числом из конструктора и словом «пау­
ков». Метод S c a r e L i t t l e C h i l d r e n О вызывает окно
с текстом «Ага! Попался!»
О А это неработающий код для кнопки. Вам нужно найти ошибки и заставить его работать.
p r i v a t e v o id b u t t o n l _ C l i c k ( o b j e c t se n d e r , E ven tA rgs e) {
S c a r y S c a r y f i n g e r s T h e C l o w n = n ew S c a r y S c a r y ( "больш ие ботинки" 14)
F u nn yF unn y som eF u n n yC low n = f i n g e r s T h e C l o w n ;
I S c a r y C lo w n s o m e O t h e r S c a r y c lo w n = som eF unn yC low n ;
s o m e O t h e r S c a r y c l o w n . H o n k (!
0
}
О
П йЛЬЦМ к л о у н «
Если не найдешь
ужасны-
ошибку... то...

дальше * 303
уберите страшных клоунов!

ажнение Вот как выглядит расширение интерфейса IClown с реализующими его классами.

]^ешение
in te rfa c e IC lo w n {
s t r i n g F u n n yT h in glH ave { get; }
v o id H onk();
}
in te rfa c e IS ca ry C lo w n : IC lo w n {
str in g S c a r y T h in g lH a v e { get; } Метод HonkQ
v o id S c a r e L ittle C h ild r e n 0 ; использует
} эт от метод
записи для
c la ss FunnyFunny : IC lo w n { отображений
p u b lic F u n n y F u n n y (str in g fu n n y T h in g lH a v e ) { сообщения, что
Можно еще раз
th is.fu n n y T h in g lH a v e = fu n n y T h in g lH a v e ; избавляет вас
} от дублирую- реализовать
эт от мет од и
p r iv a te str in g fu n n y T h in g lH a v e ; щегося кода. свойство инт ер­
p u b lic str in g F u n n yT h in glH ave {
Г фейса IClown,
get { retu rn "Б и-би! У меня е с т ь + fu n n y T h in g lH a v e ; но почему бы не
} унаследовать их
}
от FunnyFunny?
p u b lic v o i d HonkO {
M e s s a g e B o x . S h o w {t h i s . F u n n y T h i n g l H a v e ) ;

, ^ ScaryScary —j m o производный класс от


} который реализует интерфейс IClown,
Г -^-----fctown ^У^ет реализовывать интерфейс
c la ss ScaryScary i^ ijn n y F u n ^ IS ca ry C lo w n {
p u b l i c S c a r y S c a r y ( s t r i n g fu n n y T h in g lH a v e , in t n u m b erO fS ca ry T h in g s)
: b a se(fu n n y T h in g lH a v e) {
th is.n u m b e rO fS c a r y T h in g s = n u m b erO fS ca ry T h in gs;

p r iv a te in t n u m b ero fS ca ry T h in g s;
p u b lic str in g S caryT h in g lH a v e {
get { retu rn "У м е н я " + n u m b e r O f S c a r y T h i n g s + " пауков"; }

p u b lic v o id S c a r e L ittle C h ild r e n 0 {


M es sa g eB o x .S h o w (" A .a ! “ .») ;

} КЯК клоун может ^^к


Поэтому вы и с п о лй т Т Т '^^ к&страилным.
p riv a te v o id b u tto n l_ C lic k (o b je c t sen d er, E ven tA rgs e) { оператор as.
S c a r y S c a r y f i n g e r s T h e C l o w n = new S c a r y S c a r y ("больш ие б о т и н к и " , 1 4)y^
F unn yF u n n y som eF u n n yC low n = f i n g e r s T h e C l o w n ;
I S c a r y C l o w n s o m e O t h e r S c a r y c l o w n = s o m e F u n n y C l o w n OS SC Q P ySC Q P y;
so m e O th e r S c a r y c lo w n .H o n k O ;
} ------- Ссылку someOtherScaryclown можно использовать для
вызова метода ScareLittleChildrenQ, но вы не сможете
вызвать этот метод ссылкой someFunnyClown.

304 глава 7
интерфейсы и абстрактные классы

Модификаторы доступа
В ы , * е з н а е т е , н а с к о л ь к о в а ж н ы м я .^ я е т с я к л ю ч е в о е с л о в о p r l v a t e , Л
как его нужно использовать и чем оно отличается от ключевого слова ^ членами (tnembers)
p u b l ic . В C# подобные ключевые слова называются модификаторами Людой член можем быть
доступа (access modifiers). Меняя модификатор свойства, поля, метода помечен модиф икам о-
или даже всего класса, вы меняете способ доступа других классов к ука- Р°М доступа public или
занным элементам. В этом разделе мы вспомним про уже известные вам priva е.
модификаторы и познакомимся с новыми: сущ ествует
доступ к объявленному классу.)
Q public означает свободный д оступ ^
Пометив класс или его члены модификатором p u b l i c , вы объявляете открытый
доступ для всех экземпляров всех классов. Это наименее ограничивающий из мо­
дификаторов. И вы уже видели, причиной каких проблем он может стать,
зуйте его только тогда, когда это действительно нужно. модификато -
ра доступа при
Q private означает доступ только для д р у ги х членов этого ж е класса объявлении члена
Пометив члены класса модификатором p r i v a t e , вы оставляете доступ к ним класса о зт ч м т ,
только для других членов этого же класса или экземпляров этого же класса, _
Сам класс можно пометить словом p r i v a t e , только если он находится внутри Private.
другого класса. После этого доступ к нему сохранится только у экземпляров э т ^
го внешнего класса.

Q protected означает о ткры тий только для производны х классов


Вы уже видели, что из производных классов не всегда имеется доступ к полям базовых, что не
всегда удобно. Но любой член класса с модификатором p r o t e c t e d доступен как в рамках его соб­
ственного класса, так и из методов производных классов.
—' Отсутствие модифика
О internai означает открытый для д р у ги х классов в сборке _ Х е н ы ? ^ й с с д или °ин"
Встроенные классы .NET Framework являются сборками (assemblies) — терфейса означает, что
библиотеками классов, на которые можно ссылаться из вашего проек­ будет использован вариант
internal. При этом класс
та. Их список можно увидеть, щелкнув правой кнопкой мыши на пункте становится доступен для
References в окне Solution Explorer и выбрав команду Add Reference... любого другого класса в со­
Если при построении сборки воспользоваться модификатором ставе сборки. Если сборка
всего одна, модификатор
i n t e r n a i , доступ к классам будет осуществляться только изнутри сбор­ internal является анало­
ки. Существует вариация этого модификатора p r o t e c t e d i n t e r n a i , гом модификатора public
воспользовавшись которой вы ограничите доступ текущей сборкой для классов и интерфейсов.
Откройте какой-нибудь^
и типами, которые являются производными от содержащего класса. старый проект, поменяйте
доступ некоторых классов
на internal и посмотрите,
что получится.
s e a le d означает, что о т д анно го класса нельзя наследовать
Существуют классы, наследование от которых невозможно. К ним относятся мно­
гие классы .NET Framework, попробуйте, к примеру, создать класс, наследующий Ключевое слово
от класса S t r i n g (метод этого класса IsE m ptyO rN ull ( ) вы использовали в пре­ Sealed не о т ­
дыдущей главе). Компилятор выдаст сообщение об ошибке «cannot derive from носится к м о ­
sealed type ‘string’». Чтобы запретить наследование от созданного вами класса, дификаторам
доступа.
достаточно добавить ключевое слово s e a l e d после модификатора доступа.
__________ ______
про видимость

изменение 6uguMocmu при помои^и


модификаторов доступа В н е с и т е эт и изменения
Посмотрим, как модификаторы доступа влияют на видим ость различ­ ^ ^ е и ^ е н к е . Зат ем верни-
ных членов класса. Пометим вспомогательное поле fu n n y T h in g lH a v e модификат ора до-
f ^ y n a значение private
словом p r o t e c t e d и отредактируем метод S c a r e L i t t l e C h i l d r e n {),
и посм от рит е, к каким
использующий поле fu n n y T h in g lH av e; ошибкам эт о приведет

Перед вами два интерфейса. IClow n для клоуна с набо­


ром смешных вещей и производный от него IS caryC low n.
Страшный клоун не только имеет все функции обычного Слово указывает, на
клоуна, но еще и пугает маленьких детей. какую именно переменную
вы ссылаетесь. Оно говорит
interface IClown { «Посмотри на текущий
экземпляр класса».
string FunnyThinglHave { get; }

}
void HonkO;
I
Ключевое слово tkis позволяет
о ж л и ч ы т ь вспомогательное поле
interface IScaryClown : IClown { ^^Р<^метра
string ScaryThinglHave { get; Имя f'^'^nyrkm ^H ave от носит ся
void ScareLittleChildren0; запись tkis.FunnyThnqlH ave
}
О Класс FunnyFunny реализует интерфейс IClown. Поле fu n n y T h in g lH a v e
помечено модификатором p r o t e c t e d , значит, доступ к нему имеют все эк­
земпляры производного класса.
class FunnyFunny : IClown {
piiblic FunnyFunny (string funnyThinglHave) {
_ t h ^ .funnyThinglHave = funnyThinglHave;
} П осмот рит е, как м оди­
ф икат ор p ro tected п о ­
protected string funnyThinglHave; влиял на м ет од ScaruScaru
public string FunnyThinglHave { ScareLittleChildrenQ .
get { return "Би-би! У меня есо?ь " + funnyThinglHave;
Употребив рядом со свой­
ством ключевое слово this, вы
запускаете метод чтения
piiblic void HonkO { или метод записи.
MessageBox.Show(this.FunnyThinglHave);
■Кл н?чебое слово tkis указывает,
что б данном случае имеется
б оиду вспомогательное поле
л не одноименный парам ет р.'

306 глава 7
интерфейсы и абстрактные классы

^ Класс S c a r y S c a r y реализует интерфейс I S c a r y C l o w n


и наследует от класса F u n n y F u n n y , реализующего интер- Моди^Рикадаоры ^
фейс I C l o w n . Посмотрите, как именно метод S c a r e L i t t l e ' J
C h i l d r e n О осуществляет доступ к вспомогательному полю Доступа под
f u n n y T h i n g l H a v e . Такое поведение связано с модификато- уВелиЧитпеЛьНьхМ
ром p r o t e c t e d . П ри модификаторе p r i v a t e компиляция
кода стала бы невозможной.
class ScaryScary : FunnyFunny, IScaryClown { закрым,
Diiblic ScaryScary (string funnyThinglHave, со вспомогательными
, _ __ . полями. СоомветственноJ он
int numberof ScaryThxngs) доступен только для экзем пля-
: base (funnyThinglHave) { ров класса ScaryScary.
this. numberOfScaryThings = numberOfScaryThings;^:
}
private int numberOfScaryThings;
public string ScaryThinglHave {
get { return «У меня " + numberOfScaryThings + " пауков"; }
J Ключевое слово protected
оставляет доступ к
public void ScareLittleChildren о {
MessageBox. Show ("Ты не можешь забрать " производного класса.
+ base.funnyThinglHave);
т, о N ^ Наличие при переменной funnyThing
S ^^^'^ево е слово base заставляет 1Н<яуе модификатора private п р и -
J использовать значение из базово- ^ сообщению об ошибке.
го класса. Но в данном случае можно Ключевое слово protected делает ее
ооспользоваться также ключевым видимой из классов, производных от
словом this. Вы понимаете, почему? класса FunnyFunny.
О Эта кнопка создает экземпляры F u n n y F u n n y и S c a r y S c a r y . Обратите внимание, как с помощью опе­
ратора a s осуществляется нисходящее приведение s o m e F u n n y C l o w n к ссылке I S c a r y C l o w n .
private void buttonlClick(object sender, EventArgs e) {
ScaryScary fingersTheClown = new ScaryScary("большие ботинки", 14);
FunnyFunny someFunnyClown = fingersTheClown;
IScaryClown someOtherScaryclown = someFunnyClown as ScaryScary;
someOtherScaryclown.HonkO ;
^ Программа искусственно удлинена, чтобы по -
} казать возможность восходящего приведения
ТйК как запускающийся щелчком на от ScaryScary к FunnyFunny, а зат ем — нисхо-
кнопке обработчик событий не является дящего приведения к IScaryClown. Вместо этих
частью классов FunnyFunny и ScaryScary, т рех строчек можно написать всего одну. По-
ц него нет доступа к п о м е ч е н н о - пробуйте сделать это самостоятельно,
мч ключевым словом protected полю

или ScaryScary.
дальше ► 307
ox уж этот дублирующийся код!

_ Часто
Зачем нужен интерфейс, если все ^аД аБ аеМ ы е
Получается, интерфейсные ссыл­
необходимые методы можно написать B oiiJJoC bi
ки ограничивают мои возможности.
непосредственно в теле класса? Зачем же мне их тогда использовать?
Зачем нужны свойства, если есть
; По мере усложнения программ
I ОЛЯ? S Интерфейсные ссылки позволяют
классов становится слишком много. работать с различными объектами,
Интерфейсы позволяют сгруппировать
эти классы в соответствии с выполняе­ Q ; Интерфейс определяет способ, которым
выполняющими одну функцию, их помо­с
щью вы можете создать массив, обме­
мыми задачами. Это гарантирует, что для класс выполняет определенную работу. При
нивающийся информацией с методами ин­
выполнения определенной работы классы этом он не является объекгом, поэтому вы
терфейса I C a r r y P a s 3 e n g e r , неважно,
будут использовать одни и те же методы. не можете создавать экземпляры и хранить
работаете вы с грузовиком, лошадью или
Благодаря интерфейсу вам не придется в них информацию. Добавив поле путем объ­
автомобилем. Способы, которыми эти
беспокоиться о том, как именно выполня­ явления переменной, вы ставите перед про-
объекты выполняют работу, различаются,
ется эта работа. фаммой задачу сохранить данные. Свойство
но благодаря интерфейсным ссылкам вы
же, с точки зрения остальных объектов, вы-
знаете, что все они имеют одни и те же
Представим, что у вас есть классы глядиг как поле, но по сути является методом
методы, одинаковые параметры и воз­
грузовиков и парусников, реализую­ и не нуждается в хранении данных.
вращают значения одинаковых типов. Это
щие интерфейс перевозки пассажиров дает вам возможность единообразно вы­
1 С а г г у Р а з з е п д е г . Он требует Чем обычная ссылка отличается зывать их и передавать им информацию.
у реализующих его классов наличия от интерфейсной?
метода С о п з и т е Е п е г д у ( ) . Про­
грамма может использовать оба класса Зачем мне может понадобиться
Q ; Как работают обычные ссыл­ ключевое слово protected?
для перевозки пассажиров, хотя метод ки, вы уже знаете. Еспи создать эк­
С о п з и т е Е п е г д у () для парусников земпляр S k a t e b o a r d С именем
использует силу ветра, а для грузовиков — ! Оно позволяет инкапсулировать
V e r t B o a r d , и ссылку на него с именем
дизельное топливо. класс. Иногда требуется доступ из про­
H a l f p i p e B o a r d , ОНИ будут указывать на
изводного класса к внутренней части
один объект. Но еспи S k a t e b o a r d реали­
Без интерфейса Х С а г г у Р а з з е п д е г базового. Но при построении классов поля
зует интерфейс i s t r e e t T r i c k s , а вы
программе сложно объяснить, какие оставляют открытыми только если без
создаете ссылку на s k a t e b o a r d с именем
транспортные средства могут перевозить этого совсем нельзя обойтись. Модифика­
s t r e e t B o a r d , она будет знать только
пассажиров, а какие — нет. Вам потре­ тор доступа protected позволяет открыть
методы класса s k a t e b o a r d , являющиеся
бовалось бы просматривать все классы, поля, доступ к которым нужен производ­
частью интерфейса i s t r e e t T r i c k 3 .
чтобы определить в них наличие методов, ному классу, оставив их закрытыми для
пригодных для перевозки пассажиров, остальных объектов.
Все три ссылки указывают на один объ­
а потом вызывать эти методы вручную. ект. Но если ссылки H a l f P i p e B o a r d
Без стандартного интерфейса они, скорее и V e r t B o a r d дают доступ ко всем
всего, назывались бы слишком разнород­ свойствам и методам объекта, ссылка
но, кроме того, могли оказаться скрытыми
в теле других методов. Запутаться в этом
S t r e e t B o a r d видит только те методы Интерфейсные
и свойства, которые указаны в интерфейсе.
очень легко.
ссылки «знают»
только о тех свой­
ствах и методах,
которые упомянуты
в интерфейсе.
308 глава 7
интерфейсы и абстрактные классы

Классы, для которых недопустимо создание


экземпляров
Помните иерархию классов, использованную для симулятора зоопарка? За­ ShQPBer...
TotalSpent
кончили вы ее множеством экземпляров бегемотов, собак и львов. Н о в ие­
Credilim it
рархию входят и классы C an in e и F e l in e , а также базовый класс A nim al. Ду­
маем, вы уже поняли, что создание экземпляров некоторых классов не имеет
ShopTillYouDropO
смысла. Рассмотрим дополнительный пример: BuyFavoriteStuffO
Начнем с базового класса, описывающего поведение студентов
в магазине.
c la ss S hopper {
p u b lic v o id S h o p T i l l Y o u D r o p ()
w h ile (T o ta lS p en t < C r ed itL im it) ArtStudent Engineering
Student
B u y F a v o r ite S tu ff 0 ;

}
p u b lic v ir tu a l v o id B u y F a v o r ite S tu ff 0 { BuyFavoriteStuffO BuyFavoriteStuffO

// Реализация зд е с ь неум естна - мы н е з н а е м ,


// ч т о любит п о к у п а т ь наш с т у д е н т !

}
Г /
} Классы A rtS tu d e n t
Класс A r t S t u d e n t производный от класса S h o p p e r : и EngineeringStudent
перекрывают м ем од
c la ss ArtStudent : S h o p p e r { BuyFavoriteStuffQ.
p u b lic o v e r r id e v o id B u y F a v o r ite S tu ff () { Они покупают р а з­
ные книги.
B u y A rtS u p p lie s 0 ;
B u y B la c k T u rtlen ec k s();
B u y D ep re ssin g M u sic ();

}
}
И класс E n g in e e r in g S tu d e n t наследует от класса Shopper:

c la ss EngineeringStudent : S h o p p e r {
p u b lic o v e r r id e v o id B u y F a v o r ite S tu ff О {
B u y P e n c ils {);
B u y G r a p h in g C a lc u la to r {);

B u yP ock etP rotector0 ;

}
}
Ч то п р о и з о й д е т с п о я в л е н и е м э к з е м п л я р а к л а с с а S h o p p e r? И м е е т л и с м ы с л е го с о з д а в а т ь ?

дальше > 309


не могу поверить, что это не интерфейс

Абстрактны й класс. Перепутье ме)кду


классом U интерфейсом
Предположим, вам требуется интерфейс, чтобы заставить классы реа­
лизовывать определенные методы и свойства. Но при этом вы хотите
включить в этот интерфейс некий код, чтобы определенные методы не
приходилось реализовывать во всех наследующих классах. На помощь Метод, не имеющий тела,
называется абстракп^иы^
вам придет абстрактны й класс (abstract class). Этот элемент имеет (abstract Method) Н асл !^
свойства интерфейса, но позволяет записать внутри себя код. дующие классы должны
реализовывать все аб­
страктные методы, как
О Абстрактны й класс нап о м и н ает обычный
и в случае наследования от
интерфейса.
Как уже знакомый обычный класс, абстрактный класс имеет
поля и методы, и даже позволяет осуществлять наследова­
ние. Фактически вы уже знаете, что умеет делать абстракт­ V
ный класс! Абстрактные методы
м огут находиться т о ль­
ко бнутри абстрактных
классов. Если помест ит ь
Ому^рь класса абстрактный
мет од и не пом ет ит ь сам
класс словом abstract, про-
Абстрактны й класс нап о м и н ает интерцж йс
Создавая класс, реализующий интерфейс, вы соглашаетесь
реализовывать все определенные в рамках интерфейса
свойства и методы. Абстрактный класс работает аналогич­
ным способом —все объявленные в нем свойства и методы
должны регшизовываться в производных классах.

Создавать экземпляры абстрактного класса нельзя


Самым большим отличием абстрактного класса является не­
Оилибка связана с на­
возможность создать его экземпляр при помощи оператора личием абстрактных
new. Попытавшись это сделать, вы получите сообщение об методов, не содержащих
ошибке. ' ^^омпилятор не п о ­
^ ----- зволяет создать экзем ­
пляр класса, который не
содержит кода, точно
Cannot c r e a t e an i n s t a n c e o f th e
a b s t r a c t c l a s s o r i n t e r f a c e 'M y C la ss'
•позволял
экземпляры
интерфейсов.

310 глава 7
интерфейсы и абстрактные классы

Что? Класс, от которого я не могу получить


экземпляры? Зачем он вообще нужен?

И ногда исто чни ком части кода явл яю тся п рои зв од ны е классы .
Бывает так, что создание ненужных объектов имеет плохие последствия.
Поля самого верхнего класса иерархии обычно задаются в производных
классах. В классе Animal могут находится вычисления, зависящие от зна­
чения логической переменной HasTail или Vertebrate, но в нем невозмож­
но задать эту переменную.
К ласс PlanetMission клуб
аст роф изиков и спользу­ Вот еще один пример... Один полет совершаемся на
ет для от правки ракет — Венеру, д р у го й -н а Марс.
к различны м планет ам .

c la ss Plan 0 tMission {
\
Присваивать этим
полям значения в c la ss
\
Venus : P l a n e t M i s s i o n {
p u b lic l o n g R o c k e t F u e l P e r M i l e ; базовом классе бес­ p u b lic V enus О {
смысленно, потому M ile sT o P la n et = 40000000;
p u b lic l o n g R ocketSp eedM P H ;
что мы не знаем,
p u b lic in t M ile sT o P la n e t; J какая ракета куда R o c k e tF u elP er M ile = 100000;
полетит. R ocketSpeedM PH = 2 5 0 0 0 ;
p u b lic l o n g U n i t s O f F u e l N e e d e d () {
r e tu r n M ile sT o P la n et * R o c k e tF u elP er M ile; }
} }
c la s s Mars : P l a n e t M i s s i o n {
p u b lic in t T im eN eed ed O {
p u b lic M ars 0 {
r e tu r n M ile sT o P la n et / (in t) R ocketSp eedM P H ;
M ile sT o P la n et = 75000000;
} R o c k e tF u e lP e r M ile = 100000;
p u b lic str in g F u e lN e e d e d 0 ( R ocketSpeedM PH = 2 5 0 0 0 ;
retu rn " Y o u 'll need "
}
+ M ile sT o P la n et * R o c k e tF u e lP e r M ile
} Конструкторы производных классов^Мап
+ " u n its of fu el to get th ere. I t'll tak e
и Venus задают значения трех полей, уна­
+ T i m e N e e d e d () + " h o u r s ." ; следованных от класса Planet. Но поля не
} могут получить значения от экземпляра
Planet. Что произойдет, если их ‘допыта­
} ется использовать метод FuelNeeaed{).
p riv a te v o id b u tto n l_ C lic k (o b je c t s. E ven tA rgs e) {
M ars m ars = new M a r s ( ) ;
M essa geB ox. Sh ow (m ars. F u e lN e e d e d ())

}
p riv a te v o id b u tto n 2 _ C lick (o b je ct s, E ven tA rgs e) {
V enus v e n u s = new V e n u s ( ) ;
M e s s a g e B o x . Show ( v e n u s . F u e lN e e d e d ( ) ) ;

}
p riv a te v o id b u tto n 3 _ C lick (o b je ct s, E ven tA rgs e) {
P la n e tM issio n p la n e t = new P l a n e t M i s s i o n ( ) ; П ер ед тем как п ер е в е р н уть с тр а ­
M e s s a g e B o x . Show ( p l a n e t . F u e lN e e d e d ( ) ) ; — — ницу, п о д у м ай те, что п р о и зо й д е т
} п о с л е щ е л ч к а н а т р е т ь е й к н о п к е ...

дальше ► 311
абстрактные классы избегают порядка

Как у)ке было сказано, недопустимо создавать экземпляры


некоторых классоб
в программе с предыдущей страницы проблемы начинаются с появлением экземпляра P l a n e t -
M is s io n . Метод F u elN eed ed () этого класса ожидает, что значения его полей будут заданы в произ­
водном классе. Если этого не происходит, им по умолчанию присваивается нулевое значение. А при
попытке поделить на ноль...

p r iv a te v o id b u tto n 3 _ C lick (o b je ct s, E ven tA rgs e) {


P la n e tM issio n p la n e t = n e w P l a n e t M i s s i o n () ;
- I f
M e s sa g e B o x .S h o w (p la n e t. F u e lN e e d e d ( ) ) ;
}

Б методе- FuelNeededO про­


\
Attemfrted to dw<fe by m o .
исходим деление Нй
рамет р RocketSpeedMPH. Troubieshooting tips;
I ^ Mdtre sure the of the denom instor» npt_2_ero before pe ffo rm m g T S jsio n
который равен н у л ю . j Get genera! help for thts ©tceptton.

..... ...r.J
Search for more Help Onlftie..,

Actions
View DetaiL.,
Copy ocep tion detaiHo the cffpboarci

Решение: абстрактный класс Модификатор abstract указы­


вает, что класс может играть
Для классов, помеченных словом a b s t r a c t , C# не позволяет роль базового класса.
создавать экземпляры. Как и в случае интерфейса возможно
только наследование. ___________

Теперь ком пи- C ^ b s t r a c ^ class PlanetMlss хоп


ляция програм - ри1Я1с ’’long -------_ _i_ - ~
RocketFuelPerMile;
мы невозмож­ public long RocketSpeedMPH;
на, пока ты не
удерем ст роч­ public int MilesToPlanet;
ку, создаю­
щую экземпляр public long UnitsOfFuelNeededO {
PlanetMission. ^ return MilesToPlanet * RocketFuelPerMile;

// здесь определяется осФальная часть класса

Ш ТУРМ
Вернитесь к программе расчета стоимости мероприятий на с. 270-272
и подумайте над проблемой инкапсуляции. Придумайте ее решение при
помощи абстрактных классов.

312 глава 7
интерфейсы и абстрактные классы

Абстрактный метод не имеет тела


Как вы знаете, в интерфейсе объявляются методы и свойства, но отсутству­ И нтерф ейс сод ерж ит
ет их код. Это потому, что каждый метод в составе интерфейса является аб­ только аб страктны е м е­
страктным (abstract method). Давгште его реализуем! Сообщение об ошиб­ то д ы , п о это м у кл ю чево е
ке должно исчезнуть. Продолжая абстрактный класс, нужно гарантировать с л о в о a b s tr a c t п р и м е н я ­
перекрытие всех его абстрактных методов. К счастью, достаточно напеча­ е т с я т о л ь к о к о гд а р е ч ь
тать «public override», и как только вы нажмете пробел, появится список всех идет об аб страктны х
доступных для перекрытия методов. Выберите вариант S e tM is s io n I n f о ()
кл ассах. Тол ько таки е
и напишите:
кл а с с ы м о гу т и м е ть
в своем состав е аб ­
abstract class PlanetMission {
с тр а ктн ы е м етоды .

pi^lic(^abstrac^void SetMissionlnfo (
int milesToPlanet, int rocketFuelPerMile,
long rocketSpeedMPH);

// остальной код класса... Жизнь абстрактного Л


Г метода ужасна. Ведь ч
это жизнь без тела. )
П о бмЭу э^v^0M длет

1лрогрйММй нб ь-а

Попытавшись построить программу, вы получите сообщение об отсут­


ствии реализации унаследованного абстрактного члена:

'V e n u s M is s io n ' d o e s n o t im p lem en t i n h e r i t e d ab stract


^ m e m b er ' P l a n e t M i s s i o n . S e t M i s s i o n l n f o ( l o n g , in t, in t)'

Так давайте реализуем его! Ошибка сразу исчезнет.

c la ss V enus : P la n e tM issio n {
Наследуя от
абстрактно­
p u b lic V enus О { го класса, нужно
перекрыть осе
S e t M is s in ln f o (40000000, 100000, 25000) его абстрактные
} spr методы.
p u b lic o v e r r id e S e tM is sio n ln fo (in t m ile sT o P la n e t, lo n g r o c k e tF u e lP e r M ile ,
in t rocketSpeedM P H ) {
th is.M ile sT o P la n e t = m ile sT o P la n e t;
th is-R o c k e tF u e lP e r M ile = r o c k e tF u e lP e r M ile ;
th is.R o ck etS p eed M P H = rock etS peed M P H ;

}
дальше > 313
стоит тысячи слов

Возьми в руку карандаш


Продемонстрируйте свое искуссство. Слева вы видите набор классов и объявлений интер­
фейсов. Нарисуйте справа соответствующие диаграммы классов, как показано в примере
номер один. Не забудьте, что пунктирная линия показывает наследование от интерфейса,
в то время как сплошная — наследование от класса.

Дано: Диаграмма
( in t e r f a c e )
1) in terfa ce Foo { } 1) F oo
c la ss Bar : Foo { }

2) in terfa ce V in n { } 2)
ab stract c la ss Vout : V in n { }

3) a b stract c la ss M u ffie : W h u ffie { }


3)
c la ss F lu ffie : M u ffie { }

in te rfa c e W h u ffie { }

4) 4)
c la ss Zoop { }

c la ss Boop : Zoop { )

c la ss G oop : Boop { }

5) 5)
c la ss Gamma : D e lta , E p silo n { }

in te rfa c e E p silo n { }

in te rfa c e B eta { }

c la ss A lp h a : G a m m a ,B e ta { }

c la ss D e lta { }

314 глава 7
интерфейсы и абстрактные классы

А в этом задании слева приведены диаграммы классов, вам же нужно превратить их в объявления,
как показано в первом примере.

Дано: Объявление

1) public class Click { }


public class Clack : Click { }

О бозначения
наследует

; реализует
I

C la c k ^ i класс
Clack i интерф ейс

C la c k абстрактный
класс

дальше > 315


словесная битва

Беседа у камина К т о важнее: а б с т р а к т н ы й класс


или интерф ейс?

А б с т р а к т н ы й класс И нтерф ейс


я думаю, абсурдна сама постановка вопроса: кто
из нас важнее. Без меня программист не выпол­
нит свою работу. Посмотрим правде к глаза, ты
и близко ко мне не можешь подойти. Прекрасно. Другого я от тебя и не ожидал!

Как ты вообще мог подумать, что можешь быть


важнее меня? Ты даже не в состоянии наследовать,
как полагается, тебя можно только реализовать. Только полный невежда может гордиться тем, что
интерфейсы не используют механизм наследова­
ния, а реализуются. Реализация ничем не хуже на­
Превосходит? Да ты сошел с ума. Я намного более следования, а в чем-то даже превосходит его!
гибок. Я могу иметь как обычные, так и абстракт­
ные методы. И даже виртуальные методы, если
захочу. Да, я не создаю экземпляров, но этого не
можешь и ты. Зато моя функциональность ничем Да? А если класс захочет наследовать у тебя
не хуже, чем у обычного класса. и у твоего товарища? Наследовать у двух клас­
сов нельзя. Нужно выбрать кого-то одного. А вот
число реализуемых интерфейсов может быть лю­
бым, так что не нужно говорить мне о гибкости!
С моей помощью программист заставит классы
делать что угодно.
Возьми в руку карандаш
'ешение
2) (in terfaced
Vinn 1
3) (in terfa ce)]
W huffie
4) Zoop 5) D e lta
(in terfa ce^
E psilon

\
~7К
Л- (in terfa ce '!
Boop
Vout M u ffie B eta

/s /Ч

Goop
Fluffie A lp h a
Готовые диаграммы

316 глава 7
интерфейсы и абстрактные классы

А бстрахтн ьхй класс И нтерф ейс


Кажется, ты несколько переоцениваешь свою
роль. Думаешь, раз ты содержишь код, лучше тебя ни­
кого нет? Но ты не поспоришь с фактом, что на­
следовать можно только от одного класса за раз.
Так что ты ограничен. Да, я не содержу кода, но
роль кода сильно преувеличена.

Такую чепуху можно услышать только от интер­


фейса. Нет ничего важнее кода! Именно он за­
ставляет программу работать. Девять из десяти, что программисту нужны опре­
деленные свойства и методы, но его при этом не
волнует, как именно они реализуются.

Да ну? По моим наблюдениям программистов


очень даже волнует содержимое свойств и мето­ Да, конечно. Только вспомни, как часто програм­
дов. мисты пишут методы с объектами в качестве па­
раметров, которые просто должны включать в
себя определенные методы. П ри этом никого
не волнует, как именно эти методы построены.
Главное, чтобы они были. В этой ситуации доста­
точно написать интерфейс и проблема решена!
Да, конечно... расскажи кодеру, что он не может
писать код.
Не имеет значения!

2) abstract class Top { } 3) abstract class Fee { }


class Tip : Top { } abstract class Fi : Fee { }

4) interface Foo { } 5) interface Zeta { }


class Bar : Foo { } class Alpha : Zeta { ,
class Baz : Bar { } interface Beta { }
class Delta : Alpha, Beta { }
Корректные объявления

дальше * 317
проблемы множественного наследования

Досадно, что я не могу наследовать больше, чем от одного


класса, и поэтому должна пользоваться интерфейсами. Это
большой недостаток С # , не так л и ?

Это не недостаток, а защита.


Разрешить существование нескольких базовых классов —все равно что
открыть банку с червями. Существуют языки программирования, до­
пускающие м нож ественное наследование (m ultiple inheritance). Но
предоставив вместо этой функции интерфейсы, C# спасает вас от боль­
шой путаницы, которую мы хотели бы назвать...

MoviePlayer ✓
int ScreenWidth

ShowAMovieO
И Television (Телеви -
дение) и MovieTkeater
(К и н о т е а т р ) на­
следуют от класса
MoviePlayer (По- — ^
каз кино), и ода эти Television MovieTheater M o v ie T h e jx te r ^
класса перекрывают
метод ShowAMovieO каждого ^ произойдет,
(Показ дрильма). ShowAMovieO ShowAMovieO значение. Ч ^ gYi^eater (
и л и класс ^o m e i
К-роме того, они на­
следуют свойство рамный значения
ScreenW idth (Ширина
экрана).

фил&мм?

■егр
избегайте неопределенности!
в языках, допускающих смертельный ромб, возможны крайне неприятные си­
туации, так как для работы с подобными неопределенностями требуются специ­
альные правила... А это означает дополнительные усилия при написании про­
граммы! C# позволяет этого избежать. Сделайте T e l e v i s i o n и M o v ie T h e a te r
интерфейсами, и вам хватит одного метода ShowAMovie (). Главное, чтобы этот
метод присутствовал в указанном вами месте.

318 глава 7
интерфейсы и абстрактные классы

ебус Б б ассей н е

Возьмите фрагменты кода из бассейна и поместите их на пустые строч­


ки. Каждый фрагмент можно использовать несколько раз. В бассейне
есть и лишние фрагменты. Вам нужно получить набор классов, кото­
рые будут компилироваться, запускаться и давать показанный ниже
результат.

N ose { c la ss : .............................{
p u b lic A cts О : b a se(" A cts" ) { }
str in g Face { get; } p u b lic o v e rr id e {
} retu rn 5;

a b stra ct c la ss ...................... : ......................{ I Точка входа на­


ходимся здесь.
p u b lic v ir tu a l in t EarO
{ c la ss ............. : ............. {
retu rn 7; p u b lic o v e r r id e strin g Face {
} J get { retu rn "O f76"; }
p u b lic P ic a sso (str in g fa ce) p u b lic sta tic v o i d M a i n ( s t r i n g [] args) {
{ str in g r esu lt = "" ;
= face; N ose[] i = new N o s e [ 3 ] ;
} ................. i [0 ] = new A c t s ( ) ;
p u b lic v irtu a l str in g Face { i [1 ] = new C lo w n s 0 ;
......... { ............................... ; } i [2 ] = new O f76 0 ;
} for (in t X = 0; X < 3; X++) {
strin g fa ce; r e su lt += ( ............................... + " "
} + ) + " \n " ;

c la ss : {
p u b lic C low n s 0 : b a s e ( " C lo w n s" ) { }
}

К аж д ы й ф р а г­
м е н т ко д а м о ж но
и с п о л ь зо в а ть
н е с ко л ь ко раз.

QniBem на с. 3 3 7 -
в форме... ведра с орлами

данные
Думаю, что теперь ^ код в классы на мо~
я хорошо умею появления
оыла революционной,
управлять объектами! но теперь это вполне
обычная практика про­
граммирования.

В ы о б ъ е к т н о -о р и е н т и р о в а н н ы й п р о г р а м м и с т .
То, чем вы занимаетесь, называется о б ъ е к т н о - о р и е н т и -
р о в а н н ы м п р о г р а м м и р о в а н и е м (ООР). До появления
таких языков, как С#, объекты и методы при написании
кода не использовались. Применялись функции (кото­
рые в тех языках назывались методами), сосредоточен­
ные в одном месте. Можно сказать, что каждая программа
имела один статический класс, наполненный статически­
ми методами. Писать программы было намного сложнее.
К счастью, вам не придется писать программы без ООР,
так как это ключевая часть языка С#.

Четыре принципа объектно-ориентироВанного программирования


Объектно-ориентированное программирование опирается на четыре
принципа, которые вам уже знакомы. Вы пользовались ими при написа­
нии программ: н а с л е д о в а н и е , а б с т р а к ц и я и и н к а п с у л я ц и я . Последний
принцип называется немного странно —п о л и м о р ф и з м , но и с ним вы
на самом деле уже сталкивались.
ІТ Ґ ‘' "
Клдссм и интерфейсы
м огут наследовать данных ^ °
видеть. нужно
свойства друг друга.

Инкапсуляция
Это слово буквально
означает «множе­
ство форм». Мо­
ж е т е ли вы пред­
Абстракция ставить ситуацию,
Создание модели, начинающейся когда объект прини­
с более общих - абст ракт ­ м ает разные ф ор­
ні,,^ __ классов, от которых
наследуют более подродн ы е^^^^/
Полиморфизм мы?

классы.

320 глава 7
интерфейсы и абстрактные классы

Различные формы объекта


Помните, как вы использовали Пересмешника вместо Живот­
ного и выдержанный Вермонт вместо Сыра? Именно такое пове­
О полиморфизме
дение называется п о л и м о р ф и з м о м . И именно его вы исполь­
зуете при восходящем и нисходящем приведении. Другими
можно говорить, ког­
словами, вы вызываете методы и свойства объекта независимо
от их реализации.
да вы берете экзем­
пляр класса и исполь­
Пример полиморфизма зуете его в операторе
Вам предстоит выполнить очень большое (как никогда ранее)
упражнение, в котором вам придется то и дело использовать
или методе, которые
полиморфизм, так что будьте внимательны. Ниже показаны
четыре типичных способа применения этого явления. Пытай­
ожидают значение
тесь их отслеживать по мере выполнения упражнения: другого типа, напри­
□ Взять ссылочную переменную одного класса и присво­
ить ей экземпляр другого класса.
мер, из родительского
N e cta r S tin g e r b erth a = new N e c ta r S tin g e r О ;
класса или реализуе­
IN e c ta r C o lle c to r gath erer = b erth a; мого интерфейса.

□ Восходящее приведение путем использования произ­


водного класса в операторе или методе, ожидающих
значение из базового класса. .
sp ot = new D og();

z o o K e e p e r . F e e d A n A n im a l(sp o t); н а сл ед ует о т класса А п ! т ^ ^' ко т о р ы й

□ Создание ссылочной переменной интерфейсного типа


и нацеливание ее на объект, реализующий интерфейс.
^
Э т о т ож е воскодящ ее
приведение!
istin g P a tr o l d efen d er = new S tin g P a tr o lО ;
М е т о д M aintainTheHmO в к а
ч е с т в е п а р а м е т р а u ^ 0 A t> 3 yem
Нисходящее приведение при помощи оператора as. и н т е р ф е й с (W orker. О п е р а т о р
as п о з в о л я е т
v o id M a in ta in T h e H iv e (IW o r k e r w orker) { ку H iv e M a in ta in e r на о о ь е к т

if (w orker is H iv e M a in ta in e r ) { w orker.

H iv e M a in ta in e r m a in ta in e r = w orker as H iv e M a in ta in e r ;

дальше * 321
приступим

Длинные
упражнения
Давайте построим дом! В модели дома классы будут представлять комнаты
и прочие помещения, а интерфейс пусть соответствует двери.
Класс Location
Н ачнем с м о д ел и классов ''•абстрактный.
Каждая комната и помещение должны быть представлены ' Именно поэт о­
отдельным объектом. Внутренние комнаты наследуют от м у на диаграм-
класса R o o m (Комната), а внешние от класса O u t s i d e (Сна­ iMe он серый.
ружи). Для этих классов в свою очередь имеется базовый
класс L o c a t i o n (Помещение) с двумя полями: N a m e для
названия помещения (Кухня) и E x i t s (Выходы) массив
объектов, связанный с отдельными помещениями. В итоге
запись d i n i n g R o o m . N a m e будет иметь значение D i n i n g Room Outside
R o o m (Столовая), а запись d i n i n g R o o m . E x i t s будет соот­
Decoration Hot
ветствовать массиву { L i v i n g R o o m , K i t c h e n }.

о Н ари суем план дом а


В доме три комнаты, лужайка, задний двор и сад.
Внешних дверей две; одна ведет из гостиной на лу­ Интерьер помещений опи -
сывается предназначенным
жайку, а вторая —из кухни на задний двор. только для чтения свой­
ством decoration.
Определить, жарко
ли снаружи, поможет
Со предназначенное т о ль­
ко для чтения буле&о
свойство H o t .

Переместиться
с лужайки на зад­
ний двор можно
через сад.
Это дверь, ведущая из
гостиной на лужайку. Все комнаты им ею т
Существует также двери, но только не­
дверь из кухни на зад­ которые двери ведут
ний двор. наружу.

О Д л я ком нат с внеш ней дверью создадим интерд)ейс IHasExteriorDoor


IH asБ xteгiorD oor
DoorDescription
Помещения с ведущими наружу дверями (лужайка, задний двор, гостиная
DoorLocation
и кухня) должны реализовывать интерфейс IH a s E x te rio rD o o r. Предна­
значенное только для чтения свойство D o o r D e s c r ip tio n содержит опи­
сание двери (передняя дверь «Дубовая с латунной ручкой», в то время как
сзади у нас «Калитка»). Свойство D o o rL o c a tio n содержит ссылку на по­
мещение, в которое ведет дверь (Кухня).

322 глава 7
интерфейсы и абстрактные классы

Класс Location
Вот код для абстрактного класса L o c a tio n :
поля nam e, которое является
a b stra ct c la ss L o c a tio n {
'предназначенным только для
p u b lic L o c a tio n (str in g nam e ) { чтения полем свойства Name.
М ет од t h i s , name = nam e;
Description вир- }
т уальны й,его p u b lic L o c a t i o n [] E x its;
Открытое поле Exits являет ­
нужно пере- ся массивом ссылок Location,
крыть. p r iv a te str in g name; который отслеживает, какие

\ p u b l i c s t r i n g Name {

}
g e t { r e tu r n nam e;
помеш,ения связаны с т ем , в
котором находитесь вы.

^ -------- ^ p u b lic v irtu a l str in g D e sc rip tio n { - Класс Room


перекрыва -
Свойство Description ^ ет и расш и­
s t r i n g d e s c r i p t i o n = "Вы н а х о д и т е с ь в " + nam e
возвращает строку ряет метод
с описанием комнаты + " . Вы в и д и т е д в е р и , в е д у щ и е в : " ;
f o r ( i n t i = 0; i < E x i t s .L e n g t h ; i+ + ) {
Description,
и всех прилегающих добавляя к
к ней помещений (их d e s c r i p t i o n += " " + E x i t s [ i ] . N a m e ;
нему инт е­
список содержится i f ( i != E x i t s . L e n g t h - 1)
рьер. К методу
в поле Exi'tsO). Так как d e s c r i p t i o n += Outside он до­
в производных клас - } бавит т е м п е ­
сах описание будет d e s c r i p t i o n += Помните, что от ратуру.
меняться, свойство retu rn d e sc r ip tio n ;
класса Location можно
требуется перекрыть, j
ссылочные перем ен­
} ные типа Location, но
}
Создание классов
Начнем с классов R o o m и O u t s i d e . Затем создадим еще два класса: O u t s i d e W i t h D o o r ,
наследующий от класса O u t s i d e и реализующий интерфейс I H a s E x t e r i o r D o o r ,
и R o o m W i t h D o o r , который является производным от класса R o o m и также реализует ин­
терфейс I H a s E x t e r i o r D o o r . л„
дополнит ельную информацию вы
Вот как выглядит описание этих классов: получите на следующей странице.

/
d a ss O u tsid eW ith D oo r : O u tsid e, IH a sE x te r io r D o o r
{ Это будет очень
/ / Тут б у д е т с в о й с т в о D o o r L o c a t i o n (П олож ение д в е р и ) большое упражне­
/ / А з д е с ь с в о й с т в о D o o r D e s c r i p t i o n (О писание дв ер и )
ние... но мы обеща­
ем, что вы получите
c la ss R oom W ithD oor : Room, IH a sE x te r io r D o o r удовольствие! и обя­
зательно запомните
// Т у т б у д е т с в о й с т в о D o o r L o c a t i o n (П о л о ж е н и е д в е р и ) новый материал.
// А з д е с ь с в о й с т в о D o o r D e s c r i p t i o n (О писани е д в е р и )

IJefeBefHuirie aßpaHulU) u чродолжим!

дальше У 323
понаблюдаем за поведением объектов

Длинные
упражнения
Пришло время создать объекты, представляющие различные части дома, и добавить
форму для работы с ними.
К ак работаю т объекты
Рассмотрим архитектуру объектов f r o n t Y a r d и d i n i n g R o o m . Из-за наличия дверей они долж­
ны быть экземплярами класса, реализующего I H a s E x t e r i o r D o o r . Свойство D o o r L o c a t i o n
хранит ссылку на помещение, расположенное по другую стороны двери.
F r o n tV a r d - эт о
объект класса LivinßRoom это экзем ­
f OutsideWithDoor, пляр класса RoomVJithDoor,
производного от производного от клас­
класса Outside са Room и реализующего
и реализую щ его IHasExteriorDoor.
IHasExteriorDoor.

DoorLocation

DoorLocation
ExitsO Ви начали создание интерфейса \ Exitsfl !
IHasExteriorDoor и добавили два реали J
зующих его класса. Один из них наследу- £)àts — массив ссылок на
ет от класса Room, другой—от класса прилежащие помещения.
Outside. О бьект LivingRoom имеет
О Закончим создание классов и создадим и х экземпляры р Т б н Т і,
Практически все готово для построения объектов. Вам осталось:
★ Убедиться, что конструктор класса O u t s i d e задает предназначенное только для чтения
свойство H o t и перекрывает свойство D e s c r i p t i o n , добавляя текст «Тут очень жарко»,
когда переменная H o t имеет значение t r u e . Ж арко должно быть на заднем дворе, но не на
лужайке и не в саду.
★ Конструктор класса R o o m должен задавать свойство D é c o r a t i o n и перекрывать свойство
D e s c r i p t i o n , добавляя «Здесь вы видите (интерьер)». В гостиной находится старинный ко­
вер, в столовой —хрустальная люстра, а на кухне —плита из нержавеющей стали и сетчатая
дверь, ведущая на задний двор.
★ Форма должна создавать объекты и хранить на них ссылки. Добавьте метод C r e a t e -
O b j e c t s О , который будет вызываться конструктором формы. к д
^ Создайте по экземпляру для шести помещений дома. Вот пример для гостиной: мещение будет
Exits (массив R oo m W ith D oo r l i v i n g R o o m = n e w R o o m W ith D o o r ( " Г о с т и н а я " , имет ь соб­
ссылок Location), ственное поле 6
"старинны й ковер" , " дубов ая д в ер ь с л а т у н н о й р у ч к о й " );
создает массив классе формы.
из двух строк. Метод C r e a t e O b j e c t s {) должен добавлять поле E x i t s [] к каждому объекту:

f ro n tY ard . E x it s = new L o c a tio n []^ ^ b a c k Y a r d , gardenQ ^


Здесь нужны фи-
^ ^ -гурные скобки.
324 глава 7
интерфейсы и абстрактные классы

о П о стр о ен и е qx>pMu
Создадим простую форму для экскурсии по дому. Потребуется большое текстовое поле
d e s c r i p t i o n , в котором будут появляться описания помещения. Элемент C o m b o B o x
с именем e x i t s содержит список выходов из комнаты. Кнопка д о Н е г е перемещает вас
в помещение, выбранное в C o m b o B o x , а кнопка g o T h r o u g h T h e D o o r появляется при на­
личии выхода наружу.
W Вфз» the Hause Г Г Й Ь в о .
задается зЭес&-

Элемент ComboBox со­


Это многосмрочное держит список выходов,
текстовое поле с поэт ому присвоим ему
І^нопка доНеге именем description, имя exits. Выберите для
позволяем отображающее описание свойства DropPownStyle
перейти в другое текущего помещения. вариант PropPownList.
помещение.
V
Gohere. 11 Элемент ComboBox Кнопка goThroughTheDoor
появляется, если вы нахо-
Go the door ■дитесь в комнате с дверью
на улицу. Для изменения
видимости присвойте
свойству Visible значение
О Заставим ф орму работать!
Осталось соединить друг с другом отдельные части.
true или false.

Форме потребуется поле с именем c u r r e n tL o c a tio n , определяющее текущее по­


ложение. ^
Добавьте метод M o v e T o A N e w L o c a t i o n () с параметром L o c a t i o n , присваива­
ющий свойству c u r r e n t L o c a t i o n новое значение. Затем он будет очищать рас­
крывающийся список при помощи метода I t e m s . C l e a r () и добавлять новые
имена из массива E x i t s [ ] при помощи метода I t e m s . A d d (). Наконец останет­
ся отобразить первый пункт раскрывающегося списка, присвоив его свойству
S e l e c t e d l n d e x значение ноль.

★ В текстовом поле должно появиться описание текущего помещения.


★ О ператор i s проверяет наличие дверей в помещении. При их обнаружении нуж­
но отобразить кнопку Со through the door, воспользовавшись свойством Visible.
Щ елчок на кнопке Go here должен перемещать в помещение, выбранное в рас­
крывающемся списке.
Щ елчок на кнопке Go through the door должен приводить к перемещению через
дверь.
UoAe формы currentLocation — это ссылка
Л

дает
Индекс элемента, выбран-
списке, совпа-
t
класса Location. Хотя она указывает на объект
реализующий интерфейс IHasExteriorDoor на­
писать «currentLocation.PoorLocation» нельзя
Оает с индексом соответствиюшЛп так как PoorLocation не является полем класса
•помещения в массиве ExitsU
Location. Вам потребуется нисходящее приве-

дальше > 325


решение упражнения

"ргшение
длинных
ПраЖНеНИЙ Вот код для модели дома. Комнаты и другие помещения представлены с помо­
щью классов, в то время как интерфейс соответствует дверям.

in te rfa c e IH a sE x te r io r D o o r {
strin g D o o r D e sc rip tio n { get; } Это интерфе.йс
iHasExteriorPoor.
L o c a tio n D o o r L o c a tio n { get; set; }
}

c la ss Room : L o c a tio n { ^л а сс Room наследует от


p riv a te str in g d eco ra tio n ; вспом добавляет
вспомогательное поле для
p u b lic R o o m ( s t r in g nam e, str in g d eco ra tio n )
у&дназначенного только для
чтения свойства Decoration.
: b ase(n am e) {
th is.d e c o r a tio n = d eco ra tio n ; конст^^и-
конструктор класса.
}

p u b lic o v e r r id e str in g D escrip tio n {


get {
retu rn b a se .D e sc r ip tio n + " Вы в и д и т е " + d e c o ra tio n +
}
}
}

c la ss R oom W ithD oor : Room, IH a sE x te r io r D o o r {


p u b lic R o o m W ith D o o r (s tr in g nam e, str in g d e c o ra tio n , str in g d o o r D e sc rip tio n )
: base(n am e, d eco ra tio n )
{
t h i s .d o o r D e sc rip tio n = d o o rD escrip tio n ;
}

p r iv a te str in g d o o r D esc rip tio n ; Класс RoomWithDoor наследует от


p u b lic str in g D o o r D e sc rip tio n { класса Room и реализует инт ер­
get { retu rn d o o rD e scrip tio n ; фейс iHasExteriorPoor. ß дополнение
к функциям класса Room в кон­
} ст рукт ор добавляется описание
внеилней двери. Появляется ссылка
p r iv a te L o c a tio n d o o r L o c a tio n ; PoorLocation, указывающая, куда
p u b lic L o c a tio n D o o rL o ca tio n { именно ведет дверь. Интерфейс
get { retu rn d o o r L o c a tio n ; } IHasExteriorPoor требует методов
set { d o o r L o c a tio n = v a lu e ; }
PoorPescription и PoorLocation.

326 глава 7
интерфейсы и абстрактные классы

c la ss O u tsid e ; L o c a tio n
p r iv a te b o o l h ot;
p u b lic b o o l Hot { get { retu rn h o t; } } Класс Outside во многом
подобен классу Room. Он
p u b lic O u tsid e (str in g nam e, bool h ot) тоже является производ­
: base(n am e) ным от класса Location
и добавляет вспомога­
{ тельное поле для свой­
th is.h o t = h ot; ства Hot. Это свойство
используется в методе
DescriptionQ.
p u b lic o v e r r id e strin g D e sc rip tio n
get {
str in g N e w D e sc r ip tio n = b a s e .D e sc rip tio n ;
if (h ot)
N e w D e sc r ip tio n += " Очень ж а р к о ." ;
r e tu r n N e w D e sc r ip tio n ;

c la ss O u tsid eW ith D o o r : O u tsid e, IH a sE x te r io r D o o r {


p u b lic O u t s i d e W i t h D o o r ( s t r i n g nam e, bool h ot, strin g d o o r D e sc rip tio n )
: base(n am e, h ot)

t h i s .d o o r D e sc rip tio n = d o o r D esc rip tio n ; Класс OutsideWithDoor


т следует от класса
Outside и реализует и н­
терфейс IHasExteriorDoor.
p r iv a te str in g d o o rD e scrip tio n ; По биду он напоминает
p u b lic str in g D o o r D e sc rip tio n { класс RoomWithDoor.
get { r etu rn d o o r D esc rip tio n ; }
}
Свойство Description базового
p r iv a te L o c a tio n d o o r L o c a tio n ; класса получает значение в за ­
p u b lic L o c a tio n D o o rL o ca tio n { висимости от того, будет ли
get { r e tu r n d o o r L o c a tio n ; } жарко в рассматриваемом по-
set { d o o r L o c a tio n = v a lu e; }
меш,ении. Оно зависит от исход­
ного свойства Description класса
} Location и поэт ому включает
\ / информацию о помещении и вы­
p u b lic o v e r r id e str in g D e sc rip tio n ходах.
get {
r etu rn b a s e . D e sc r ip t io n + Вы в и д и т е " + d o o rD escrip tio n +
}
}
IJepeBej^Hurae стр ан и ц у и г^ододжим!

дальше ► 327
решение упражнения

решение
длинных
ПраЖНеНИЙ Это код формы, расположенный в файле Form1.cs, внутри объявления Forml

p u b lic p a r tia l c la ss Form l : F orm форма отслеживает, б какой


{ ' комнате вы находитесь
L o c a tio n c u r r e n tL o c a tio n ; 1 в данный момент.

R oom W ithD oor l i v i n g R o o m ;


Room d i n i n g R o o m ;
R oom W ithD oor k i t c h e n ;
При помощи эт их ссылочных
переменных форма следит за
каждым помещением в доме
O u tsid eW ith D o o r fr o n tY a r d ;
O u tsid eW ith D o o r backY ard;
O u tsid e garden;
Конст рукт ор ф ор-
p u b lic F o rm lО { мы создает объект
^ и использует метод
In itia liz e C o m p o n e n t О ;
^ MoveToANewLocation
C r e a te O b je c ts(); для перехода в другое Создание объектов на­
M o v e T o A N ew L o ca tio n (liv in g R o o m ) J помещение. чинается с создания
экземпляров и передачи
}
конструкт орам эт их
экземпляров нужной ин­
p r iv a te v o id C reateO b jectsО { формации.
liv in g R o o m = n e w R o o m W i t h D o o r {"Гостиная", "старинный ковер",
"дубовая дверь с латунной ручкой");
d i n i n g R o o m = n e w R o o m ( "Столовая", "хрустальная люстра");
k i t c h e n = n e w R o o m W i t h D o o r ("Кухня", "плита из нержавеющей стали", "сетчатая дверь")

fron tY ard = n e w O u t s i d e W i t h D o o r ("лужайка", f a l s e , "дубовая дверь с латунной ручкой");


backY ard = n e w O u t s i d e W i t h D o o r ("Задний двор", t r u e , "сетчатая дверь");
g a r d e n = n e w O u t s i d e ( "Сад", f a l s e ) ;
Здесь мы переда- ^
ем описание дверей
d in in g R o o m .E x its = n e w L o c a t i o n [] { liv in g R o o m , k itc h e n }; конструкторам
liv in g R o o m .E x its = n e w L o c a t i o n [] { d in in g R o o m }; Outside\лJitШoor.
k itc h e n .E x its = new L o c a t i o n [ ] { d in in g R o o m };
Здесь заполняется м ас­
fr o n tY a r d .E x its = n e w L o c a t i o n [] { backY ard, garden }
сив Ех іі £[] для каждого из
b a c k Y a r d .E x its = n e w L o c a t i o n [] { fron tY ard , garden } экземпляров. Данная про­
g a r d e n .E x its = n e w L o c a t i o n [] { backY ard, fro n tY a rd } цедура возможна только
после создания всех экзем ­
liv in g R o o m .D o o r L o c a tio n = fron tY ard ; пляров!
f r o n t Y ard. D o o r L o c a tio n = liv in g R o o m ; / Аля объектов
> IH asExteriorD oor нцж-
k itc h e n .D o o r L o c a tio n = backY ard; У‘^ ^ ^ а т ь положение
b a c k Y a r d .D o o r L o c a tio n = k i t c h e n ; J дверей.

328 глава 7
интерфейсы и абстрактные классы

p riv a te v o i d M oveT o A N ew L o ca tio n (L o ca tio n n e w L o c a tio n )


c u r r e n tL o c a tio n = n ew L o ca tio n ;
MemodMoveToANewLocationO
показывает в форме новое
e x i t s . I t e m s . C l e a r () ; >ломещение.
for (in t 1 = 0 ; i < c u r r e n tL o c a tio n .E x its.L e n g th ; i+ + ) P
e x i t s . I t e m s . A d d ( c u r r e n t L o c a t i o n . E x i t s [ i ] .N a m e) ; f ^^УЖно о ч и с т и т ь
e x i t s .se le c te d ln d e x = 0; ^^^^М егоТ ож ^
заново заполни ^
d e sc r ip t io n . Text = c u r r e n tL o c a tio n .D e s c r ip tio n ; ^ M u > ^ 0 М е щ е н и ^ п ' ^ ^ ^ а ‘^^‘^~
\ ‘^ ^ р е м е н н ^ S e k c tld T i''^ ''
i f ( c u r r e n t L o c a t io n i s IH a sE x te r io r D o o r ) J значения
------ --------------- ^ ннолТ.
оль I
g o T h r o u g h T h e D o o r .V is ib le = t r u e ;
•^^рвый п у нкт Т^^
будьте Z T rT n We з а -
e ls e
^'^opO ow nStuleT ^
'^Г gornrougniiiejjuux
goT hroughTheD oo. r vi.bj.xj±t3
. V i s i b l e = i.ctxöc;
fa lse ; ^'^^pOownList
' V Д е л а е т кнопку Go through, the door невидимой, если зователи не L n
текуия,ее помещение не реализует IHasExteriorPoor.
private void goHere_Click(object sen d e r , EventArgs e) { ^
M o v e T o A N e w L o c a t i o n ( c u r r e n t L o c a t i o n . E x i t s [ e x i t s .S e l e c t e d l n d e x ] );
Щелчок на кнопке
} Qo here: перем е­
щает в выдранное
p r i v a t e v o id g o T h r o u g h T h e D o o r _ C lick (o b jec t sen d er, E ventA rgs e) { помещение.
IH a sE x te r io r D o o r hasD oor = c u r r e n t L o c a t io n a s IH a sE x te r io r D o o r ;
M o v eT oA N ew L ocation (h asD oor. D o o r L o c a t io n ) ;

}
Оператор as осуществляет нис-
)содящее приведение currentLocation
к IHasExteriorDoor, что дает нам
доступ к полю DoorLocation.

Работа еще не закончена!


Нашу замечательную модель дома можно превратить в игру! Хотите поиграть
с компьютером в прятки? Нам потребуется класс O pponent (Соперник) и воз­
можность прятать его в комнатах. Ну и, конечно, большой дом, в котором
удобно прятаться! Мы добавим новый интерфейс, описывающий укромные
местечки. И обновим форму, чтобы получить возможность проверять наличие
укромных мест и отслеживать, сколько ходов вы сделали, пытаясь найти со­
перника!
И ш а к , начнем!

дальше > 329


создай себе соперника

Время сыграть в прятки! Создадим дополнительные комнаты, укромные местечки


.нение и соперника в игре. Создайте новый проект и воспользуйтесь
_ командой A dd Existing Item , чтобы добавить
О Интерф ейс IH idin gP lace
Вам не потребуется ничего особенного. Любой класс, производный от L o c a t i o n и реализую­
щий I H i d i n g P l a c e , имеет место, где может спрятаться соперник. Потребуется только строка,
хранящая информацию о том, где он прячется («в шкафу», «под кроватью»...)
★ Добавьте только метод чтения, так как при наличии в комнате укромного места, вам уже
не потребуется ничего менять.

е Классы , реали зую щ и е IH id in g P lace


Потребуются два класса: O u t s i d e W i t h H i d i n g P l a c e (наследующий от класса O u t s i d e )
и R o o m W i t h H i d i n g P l a c e (наследующий от класса R o o m ) . Укромные места должны быть
в любой комнате с наружной дверью, поэтому наследование будет осуществляться от класса
R o o m W i t h H i d i n g P l a c e , а не от класса R o o m .

О Класс, описы ваю щ ий соперника


Объект O pponent будет прятаться, а вы его искать.
★ Ему потребуется закрытое поле L o c a tio n (myLocat io n ) для отслеживания его положе­
ния и закрытое поле Random (random ) для поиска случайного укромного места.
★ Конструктор присваивает начальное положение переменной m yLocat io n , а перемен­
ную random - новому экземпляру Random. Игра начинается на переднем дворе, а затем
случайным образом выбирается место для укрытия. Соперник делает 10 ходов. Оказыва­
ясь перед внешней дверью, он подбрасывает монету, чтобы определить, нужно ли через
нее проходить.
★ Метод Move () перемещает соперника. Если ran d o m . N ext (2) имеет значение 1, сопер­
ник проходит в дверь. Затем он случайным образом выбирает один из выходов из ново­
го помещения и проходит сквозь него. Если в помещении негде спрятаться, соперник
снова случайным образом выбирает дверь и уходит в другое место.
★ Метод Check () с параметром location возвращает значение true, когда соперник пря­
чется.

О Д ополнительны е колонаты
Обновите метод C re a te O b j e c t s (), чтобы создать больше комнат:
★ Добавьте лестницу с деревянными перилами, соединяющую гостиную с к о р и д о р о м вто­
р о г о э т а ж а , где висит картина с собакой и стоит шкаф.
★ Верхний коридор ведет в три комнаты: г л а в н у ю с п а л ь н ю с большой кроватью, в т о р у ю
спальню с маленькой кроватью и в а н н у ю с раковиной и туалетом. Прятаться можно как
под кроватями, так и в душе.
★ Передний и задний дворы соединены п р оезд ом с гаражом, пригодным для укрытия.
Прятаться можно и в сарае.

330 глава 7
интерфейсы и абстрактные классы

О О бновляем qx>pмy
Создадим несколько новых кнопок. Они будут появляться и исчезать, в зависимости от
того, на какой стадии находится игра.
Гюедтя кнопка называется
В е р х н и е две кноп ^ А
ки и
с п и с о к бцдут видимы
только 0 игре- ^

°^о5раж ается
^^олько кнопка Hide (Прячься!).
После щелчка на ней в т ек-
стовом поле идет отсчет
г
м десять сваивается
раз вызывается метод Movef) г г ^ о р ^ м ГлеЭуе^ " 1 ? Г х о Э и -
места. Например, появится
S u l ? e
З аставим кнопки работать триМ под кроватью).
Появились две новые кнопки.
В главе 2 t
уже вст ре­ Центральная кнопка становится видимой только когда вы находитесь в комнате
чали м ет о ­ с местом для укрытия. Она ищет соперника при помощи метода C h e c k { ) . Если
ды DoEventsO вы его находите, игра перезапускается.
и SleepO, ко­
торые будут Нижняя кнопка запускает игру. В текстовом поле с задержкой 200 миллисекунд
использоваться появляются цифры от 1 до 10. После каждой из них соперник перемещается
в данном случае. при помощи метода M o v e (). Затем на полсекунды появляется надпись «Я иду ис­
кать!», и игра начинается.

О М ето д , перерисовы ваю щ ий дю рм у, и м ето д , перезап ускаю щ ий и гр у


Метод R e d r a w F o r m () отвечает за появление нужного текста в текстовом поле, види­
мость кнопок и ярлык на средней кнопке. Метод R e s e t G a m e () запускается после об­
наружения соперника. Он возвращает соперника на передний двор и позволяет начать
игру заново после щелчка на кнопке «Hide!» Результатом его работы является пустая
форма с текстовым полем и кнопкой «Hide!» В текстовом поле должна быть информа­
ция, где и за сколько ходов вы обнаружили соперника.

О О тслеж ивание чи сла ходов


В текстовом поле должно отображаться количество прове­
ренных укромных мест и переходов из одного помещения в
другое. После обнаружения соперника должно появляться со­
общение «Ты нашел меня за X ходов!»

О Вид яэормы в начале работы проградлмы


Изначально форма должна содержать пустое текстовое поле
и кнопку «Hide!», щелчок на которой начинает игру!

дальше ► 331
решение упражнения

іа ж н е н и е Вот как выглядит программа после создания новых комнат, укромных


мест и соперника, с которым вы будете играть в прятки.
І'^еш ение
Новый интерфейс IHidingPlace содержим
всего одно поле м ипа string с м е м о д о !Г
чтения, возвращающим название мест а
in te rfa c e IH id in g P la ce { где можно спрятаться. ме.ста,
strin g H id in g P la c eN a m e { get; }
}

c la ss R o o m W ith H id in g P la c e : Room , IH id in g P la ce {
p u b lic R o o m W ith H id in g P la c e (s tr in g nam e, strin g d e c o ra tio n , str in g h id in g P la ce N a m e )
: b ase(n am e, d e co ra tio n )
{
th is.h id in g P la c e N a m e = h id in g P la ce N a m e ;
К л а с с RoomW ithHidingPlace
} наследует от класса Room
p riv a te str in g h id in g P la ce N a m e ; и реа ли зует инт ерф ейс ^
p u b lic strin g H id in g P la ceN a m e {
IHidingPlace, добавляя свойство
HidingPlaceName. Д анное вспо­
get { r e t u r n h id in g P la ce N a m e ; } м огат ельное поле задается
} конст рукт ором .
p u b lic o v e r r id e str in g D e sc rip tio n {
get {
retu rn b a se .D e sc r ip tio n + " Спрятаться
}
) }
c la ss R oom W ithD oor : R o o m W ith H id in g P la c e , IH a sE x te r io r D o o r {
p u b lic R o o m W ith D o o r (s tr in g nam e, str in g d e c o ra tio n ,
s t r i n g h id in g P la ce N a m e , s t r i n g d o o r D escrip tio n )
: b ase(n am e, d e c o r a t i o n , h id in g P la ce N a m e )
{ укры мия было решено
t h is .d o o rD escrip tio n = d o o rD e scrip tio n ; в Комнаты с внеш-
} ними дверями, мы сделали нКласс
RoomWithDoor производным от
p riv a te str in g d o o r D esc rip tio n ; RoomWithHidingPlace. Теперь
p u b lic str in g D o o r D e sc rip tio n {
конструкт ор этого производ-
get { retu rn d o o rD e scrip tio n ; } Тк7ом ТпТ название
укромного мест а конст рикт о­
} ру R-oomWithHidingPlace

p r iv a te L o c a tio n d o o r L o c a tio n ;
p u b lic L o c a tio n D o o rL o ca tio n {
get { r e tu r n d o o r L o c a tio n ; }
set { d o o r L o c a tio n = v a lu e ; }
}

332 глава 7
интерфейсы и абстрактные классы

c la ss O u tsid e W ith H id in g P la c e : O u tsid e, IH id in g P la ce {


p u b lic O u t s i d e W i t h H i d i n g P l a c e ( s t r i n g nam e, bool h ot, strin g h id in g P la ce N a m e )
: base(n am e, h ot)
{ th is.h id in g P la ce N a m e = h id in g P la ce N a m e ; }

p r iv a te str in g h id in g P la c e N a m e ;
p u b lic str in g H id in g P la ceN a m e {
Класс OutsideWithHidingPlace наследует
get { r e t u r n h id in g P la ce N a m e ; } от класса Outside и реализует
} метод IHidingPlace аналогично классу
RoomWithHidingPlace.
p u b lic o v e rr id e str in g D escrip tio n {
get {
retu rn b a se .D e sc r ip tio n + Можно с п р я т а т ь с я " + h id in g P la ce N a m e +

} Конструктор класса Opponent 6 каче­


} } стве параметра берет начальное по­
мещение. Он создает экземпляр Random,
c la s s O pponent { при помощи которого случайным об­
p riv a te R andom r a n d o m ; разом осуществляется перемещение из
p r i v a t e L o c a t io n m y L o c a tio n ; одного мест а в другое.
p u b lic O p p o n en t(L o ca tio n s t a r tin g L o c a tio n ) i Метод MoveQ при помощи оператора
m y L o c a tio n = s t a r t i n g L o c a t i o n ; IS проверяет наличие внешней двери
random = new Random ( ) ; в комнате. При ее наличии с 50% -й
вероятностью он через нее проходит.
Таким способом он случайным образом
p u b l i c v o i d M oveO {
перемещается по дому, пока не нахо­
i f (m y L o ca tio n i s IH a sE x te r io r D o o r ) { дит мест о, где можно спрятаться.
IH a sE x te r io r D o o r L o ca tio n W ith D o o r
m y L o c a tio n a s IH a sE x te r io r D o o r ;
if ( r a n d o m .N e x t (2) == 1)
m y L o c a tio n = L o c a tio n W ith D o o r .D o o r L o c a tio n ;
} Этот цикл while продолжается, пока переменная
hidden не примет значение true. Это произойдет
b o o l h id d en = f a l s e ; при попадании в помещение, пригодное для
w h ile ( ! h id d en ) { укрытия.
i n t rand = r a n d o m .N e x t(m y L o c a tio n .E x its.L e n g th )
m y L o c a tio n = m y L o c a t io n .E x i t s [ r a n d ] ;
if (m y L o ca tio n i s IH id in g P la ce)
h id d en = tru e;

} Метод CheckQ сравнивает


} значение переменной location
p u b lic bool C h eck (L o ca tio n lo ca tio n T o C h e ck ) класса opponent со значени -
ем ссылки Location. Если обе
if (lo ca tio n T o C h eck != m y L o c a t i o n )
ссылки указывают на один
retu rn fa lse ; объект, значит, соперник
e ls e найден!
retu rn tru e;
}
Ц ере^ерни тпе ои ран и Щ ) U ироД олж им !

дальше ► 333
решение упражнения

Это код формы. Неизменными в нем in t M oves;


іажнение остались только методы доНеге_С11ск()
решение и доТЬгоидЬТ11еОоог_СНск(). L o c a tio n c u r r e n tL o c a tio n ;

Список полей класса F orm i. Именно с их R oom W ithD oor l i v i n g R o o m ;


помощью отслеживаются помещение,
R o o m W ith H id in g P la c e d in in g R o o m ;
соперник и количество перемещений, сде­
R oom W ithD oor k i t c h e n ;
ланных игроком.
Room s t a i r s ;
^ —.К онст рукт ор F orm l создает объекты R o o m W ith H id in g P la c e h a l l w a y ;
Ґ определяет положение соперника и пеюе- R o o m W ith H id in g P la c e b a th ro o m ;
R o o m W ith H id in g P la c e m a ste rB e d r o o m ;
\ ><^^^tPame() билев парам ет р отвечает за - R o o m W ith H id in g P la c e secondBedroom ;

p u b lic F o rm lО {
O u tsid eW ith D oo r fr o n tY a r d ;
In itia liz e C o m p o n e n tO ;
O u tsid eW ith D o o r backY ard;
C reateO b jects О ;
O u tsid e W ith H id in g P la c e g ard en ;
o p p o n e n t = new O p p o n e n t ( f r o n t Y a r d ) ;
O u tsid e W ith H id in g P la c e d riv ew a y ;
R ese tG a m e (fa lse );
}
p r i v a t e v o i d M oveT o A N ew L o ca tio n (L o ca tio n n e w L o c a tio n )
M oves++;
c u r r e n tL o c a tio n = n ew L o ca tio n ;
Redraw Form 0 ; _
I Hemod MoveToANevJLocationQ
задает новое положение и пере­
рисовывает форму-
p r iv a te v o id R edraw Form О { ^
e x i t s . I t e m s . C l e a r () ;
fo r (in t і = 0; і < c u r r e n tL o c a tio n .E x its.L e n g th ; i+ + )
e x its .Ite m s.A d d (c u r r e n tL o c a tio n .E x its[i].N a m e );
e x i t s . S e l e c t e d l n d e x = 0;

") d e sc r ip tio n .T e x t = c u r r e n tL o c a tio n .D e s c r ip tio n + " \ r \ n ( п е р е м е щ е н и е #" + M oves +


if (c u r r e n tL o c a tio n is IH id in g P la ce ) { ________ _
IH id in g P la ce h id in g P la c e = c u r r e n tL o c a tio n у і р о ^ г о м е їт Т іо в
c h e c k . T e x t = "C heck " + h i d i n g P l a c e . H i d i n g P l a c e N a m e ; ^^лычии имеется только
c h e c k .V isib le = tru e; CurrentLocation,
} не обладающий свой-
e ls e ством HidingPlaceName.
c h e c k .V isib le = fa lse ; ^°^<^ользуемся onepam o-
if (c u r r e n tL o c a tio n is IH a sE x te r io r D o o r ) ватьссьИ'ку н Л е З е н
g o T h r o u g h T h e D o o r .V is ib le = tru e; ную IHidingPlace.
61S0
g o T h r o u g h T h e D o o r .V is ib le = fa lse ; ^ п і^ о і ^адает ^т е"!^Р Л Т^^

Z cZ . ^т > ^ной двери или укрш нТ го

334 глава 7
интерфейсы и абстрактные классы

Аобавив всего пару строк, вы прист роит е к дому целое крыло!


Видите, как полезна инкапсуляция классов и оо-ьектов?

private void CreateObjectsО {


l i v i n g R o o m = n e w R o o m W i t h D o o r (" Г о с т и н а я " , " с т а р и н н ы й ков е р " ,
"в г а р д е р о б е " , "дубовая дверь с л а ту нной ручкой");
d i n i n g R o o m = n e w R o o m W i t h H i d i n g P l a c e (" С т о л о в а я " , " х р у с т а л ь н а я л ю с т р а " ,
"в в ы с о к о м ш к а ф у " );
k i t c h e n = n e w R o o m W i t h D o o r (" К у х н я " , " п р и б о р ы и з н е р ж а в е ю щ е й стали",
"в с у н д у к е " , "сетчатая дверь");
stairs = n e w R o o m (" Л е с т н и ц а " , " д е р е в я н н ы е перила");
h a l l w a y = n e w R o o m W i t h H i d i n g P l a c e (" В е р х н и й к о р и д о р " , "картина с собакой",
"в г а р д е р о б е " ) ;
b a t h r o o m = n e w R o o m W i t h H i d i n g P l a c e (" В а н н а я " , " р а к о в и н а и т у а л е т " ,
"в д у щ е " ) ;
m a s t e r B e d r o o m = n e w R o o m W i t h H i d i n g P l a c e (" Г л а в н а я спальня", "больщая кровать",
"п о д к р о в а т ь ю " );
s e c o n d B e d r o o m = n e w R o o m W i t h H i d i n g P l a c e (" В т о р а я спальня", "маленькая кровать",
" п о д к р о в а т ь ю " );

f r o n t Y a r d = n e w O u t s i d e W i t h D o o r (" л у ж а й к а " , false, "тяжелая дубовая дверь");


b a c k Y a r d = n e w O u t s i d e W i t h D o o r (" З а д н и й д в о р " , true, "сетчатая дверь");
g a r d e n = n e w O u t s i d e W i t h H i d i n g P l a c e (" С а д " , false, "в с а р а е " ) ;
d r i v e w a y = n e w O u t s i d e W i t h H i d i n g P l a c e (" П о д ъ е з д " , true, "в г а р а ж е " ) ;

diningRoom.Exits = n e w L o c a t i o n [] { livingRoom, kitchen };


livingRoom.Exits = n e w Location[] { diningRoom, stairs };
kitchen.Exits = n e w Location[] { diningRoom };
stairs.Exits = n e w Location[] { livingRoom, hallway };
hallway.Exits = n e w L o c a t i o n [] { sta i r s , bathroom, masterBedroom, secondBedroom
bathroom.Exits = n e w L o c a t i o n [] { hallway };
masterBedroom.Exits = n e w Location[] { hallway };
secondBedroom.Exits = n e w L o c a t i o n [] { hallway };
frontYard.Exits = n e w Location[] { backYard, garden, driveway };
backYard.Exits = n e w Location[] { frontYard, garden, driveway };
garden.Exits = n e w Location[] { backYard, frontYard };
driveway.Exits = n e w Location[] { backYard, frontYard };

livingRo om.Door Locatio n = frontYard; Новый метод CreateObjects() создает


frontYard.DoorLocation = livingRoom; объекты, из которых состоит доМ-
О т старого метода он отличается
большим количеством вариантов.
kitchen.DoorLocation = backYard;
b a c k Y a rd.Doo rLocati on = kitchen;

- ЦереБерните сшраниЦу и прододжим!

дальше > 335


решение упражнения

ненке Это остальной код формы. Обработчики событий


кнопок доНеге и goThroughTheDoor идентичны
)'^ешение своим собратьям из первой части упражнения,
f Вы найдете их, вернувшись на несколько страниц
назад.
p riv a te v o id R esetG am e(bool d isp la y M e ssa g e ) {
if (d isp la y M essa g e) {
M e s s a g e B o x . S h o w ("Меня наш ли з а " + M oves + " х о д о в ! " ) ;
IH id in g P la c e fo u n d L o c a tio n = c u r r e n tL o c a tio n ^ ^ I H i d i n g P l a c e ;
d e s c r i p t i o n . T e x t = "Соперник н а й д ен з а " + M oves
^ + " ходов! Он п р я т а л с я " + f o u n d L o c a t i o n . H id in g P la c e N a m e

M oves = 0;
h id e .V isib le = tru e;
g o H e r e .V isib le = fa lse ;
«Hide!», невидимыми кнопки
c h e c k .V isib le = fa lse ;
g o T h r o u g h T h e D o o r .V is ib le = fa lse ;
Нам нужно отобразить имя
e x its .V is ib le = fa lse ;
}

p r iv a te v o id c h e ck _ C lic k (o b je c t sender,
M oves++;
if (o p p o n en t. C h e c k (c u r r e n tL o c a tio n ))
R esetG am e(tru e);
e lse
Кнопка check проверяет, не
Redraw Form ( ) ; прячется ли соперник в комна­
} те, где вы находитесь. При его
обнаружении перезагружает
p r iv a te v o id h id e _ C lic k (o b je c t sen d er, E ven tA rgs e) {
игру. Не обнаружив его, перери­
h id e .V isib le = fa lse ;
совывает форму (чтобы обно­
вить число ходов).
fo r (in t i = 1; i <= 1 0 ; 1++) {
o p p o n e n t.M ove();
Помните обработчик событий
d e sc r ip tio n .T e x t = i + " ... ^oEventsO главы а.? Именно
A p p l i c a t i o n . D o E v e n t s () ; — ------- --------- он отвечает за обновление ин-
S y s t e m . T h r e a d i n g . T h r e a d . S l e e p (2 0 0 ) ; <рормации в текстовом поле.
}
Игра начинается с кнопки «Н/^е?» Сначала
d e sc r ip tio n .T e x t = "Я и д у и с к а т ь ! " ; кнопка становится невидимой. Зат ем о к л ю -
A p p lic a tio n .D o E v e n tsО ; чается счет до Ю и сопернику говорится,
S y s te m .T h r e a d in g .T h r e a d .S le e p (5 0 0 ); что нужно спрятаться. Напоследок, видимы­
ми становятся первая кнопка и раскрываю-
ш,ийся список, и игрок помеш,ается в гост и­
g o H e r e .V isib le = tru e;
ную. М етод МоуеТоАЫе\миосаИоп() вызывает
e x its .V is ib le = tru e; метод Кес1га\л/Рогт().
M o v eT o A N ew L o ca tio n (liv in g R o o m );
интерфейсы и абстрактные классы

І^еіИение Б бассейне со с. ^ 9
Вам требовалось взять фрагменты кода из бассейна
и поместить их на пустые строки таким образом,
чтобы получить показанный ниже результат.

К-ласс Acts вызывает конструктор ба-


зооого для него класса Picasso. Он пере­
дает конструкт ору переменную Acts,
которая сохраняется в свойстве face.

interface N ose { c la s s . Acts , : Picasso.....{


p u b lic A ctsO : b a s e ("A cts" ) { }
int ÉarO
str in g Face { get; } p u b lic o v e r r id e int EarQ {
retu rn 5;
}
}
a b stra ct c la ss Picasso : Моте }
p u b lic v ir tu a l in t E ar()
{ c la ss ,P f7 ^ ..:..
retu rn 7; p u b lic o v e r r id e strin g Face {
Свойст ва м о гу т ^ get { retu rn "O f76"; }
}
p u b lic P ic a sso (str in g face) п о я в и т с я в 1л р о ^^^ sta tic v o i d M a i n ( s t r i n g [] args) {
..ойллпиллл местей
извоАШом м ест е^
str in g r e su lt = "" ;
класса! К-оЗ про­
this.face = face;
ще чит ат ь, если N ose[] i = new N o s e [ 3 ] ;

r они
OHM сосредото- Л
i [0] = new A c t s ( ) ;
p u b lic v ir tu a l str in g Face { с бе рК Ц , HO о i [1 ] = new C low n s 0 ;
___ _ ! ___ ______ _________
get { ....retHrP.-f!“?.®
. 1 ^
даиноМ случае M t>i
АЛІ
i [ 2 ] = new Of 76 0 ;
} поместили сбои- f o r ( i n t X = 0; X < 3; x++) {
str in g face; ство face в ниж­
нюю часть класса r e su lt += ( ..iM-.Eqr.O........+ " "
} + i[xi.Face ) "\п"
Picasso.
}
c la ss Clowns : Picasso { M e s sa g e B o x .S h o w (r e s u lt);
p u b l i c C lo w n s О : b a s e ( " C lo w n s" ) { }
} Face -- это метод записи,
возвраш,аюш,ий значения свой­
ства face. И метод, и свой­
ство определены в классе
Picasso и наследуются произ­
водными классами.

дальше ► 337
8 перечисление и Коллекции

Большие объемы данных ^

П р и ш л а бед а — отворяй ворота.


в р еал ьном мире данны е, как правило, не хр ан я тся м аленьким и
кусочками. Д а н н ы е приходят в агонам и, ш таб ел ям и и кучами.
Д л я их систем ати зации нужны м о щ н ы е инструм енты, и ту т вам на
п о м о щ ь приходят коллекции. О н и п озвол яю т хр анить , со р ти р о ­
вать и ред акти р о в ать данны е, которы е о б р а б а ты в а е т програм ­
ма. В результате вы можете сосредоточиться на основной идее
програм м ирования, остав и в зад ач у отслеж ивания д а н н ы х коллек­
циям.
акула-нянюшка и плотник-муравей

Категории данных не бсегда мо)кно сохранять 6 переменных типа string


Предположим, у вас есть несколько рабочих пчел из класса
W orker. Как написать конструктор, который в качестве параме­
тра берет работу? Если названия работ помещать в переменные
типа string, получится примерно такой код;

Наше приложение для управления Код позволяет передать эти


пчелами оплслеживало радоту значения в конструкт ор, даже
каждой из них при помош,и строк если программа поддерживает
вида Stiny Patrol или Nectar только такие занятия как
Collector. Sting Patrol (Охранник), Nectar
Collector (Сдорищк нектара)
и другие, привычные для пчел
варианты работы.

Worker buzz = new Worker("Прокурор");


Worker clover = new Worker("Кинолог");
Worker gladys = new Worker("Диктор");

К конструктору W orker можно добавить код, проверяющий


корректность каждой строчки. Но тогда, чтобы расширить
список доступных для пчел занятий вам придется отредактиро­
вать данный код и перекомпилировать класс W orker. Так что
это недальновидное решение. А что делать при наличии других
классов, проверяющих типы работ, которые могут выполнять
рабочие пчелы? Вы получите дублирующийся код, затрудняю­
щий вашу работу.
Фактически нам нужно объявить: «Здесь могут использоваться
только строго определенные значения». И п еречислить эти
значения.

340 глава 8
перечисления и коллекции

Перечисления

Э т о имя перечисления.

enum Job { Каждая


После последнего NectarCollector , ^ .м о ж е т бьітТв^поАиГ^
^инкт а списка ^^чeлoй. И ви,
З А іл я т у ю cm « 6w m & SStingPatrol,
t - і n c r P a t r o l . -чі С — ^
< .^,^л а Л і в і л а . п О

ГлїХГе"««"« HiveMaintenance,”^ значения Jobs^^'^^~


r»PaKfw«eS" BabyBeeTutoring,
^ Е д д С а а г е ,;^ «.— ---------------------- Значения разделяются
-^HoneyManufacturing, запятыми, а 6 конце
' ^ ставится фигурная скобка.
} < ____ —----------------------------^

Ссылка на элемент перечисления выглядит следую­ Название %жное вам


перечисления. значение из
щий образом:
•^^Р^числения

Worker nanny = new Worker(Job.EggCare);

у^араттры типа
Вы не можетие взять и создать новое значение для Worker. Jobs.
перечисления! Программа не будет компилироваться.

p r iv a te v o id b u tto n l_ C lic k (o b je c t s e n d e r E v e n tA r g s e)

{
W orker b u z z = new W o r k e r (J o b s . A t t o r n e y G e n e r a l ) ;
Сообщение об

'J o b s' d o es n o t c o n ta in a d e fin itio n fo r


a 'A tto rn ey G e n e ra l'

дальше > 341


имена лучше цифр

Присвоим числам имена


Иногда удобнее работать с именованными числами - сопоставить их элементам перечисления и обра­
щаться к ним по именам. Это позволит избежать непонятных цифр, разбросанных по всему коду. Вот
перечисление для оценки качества выполнения собаками различных команд:

public enum TrickScore { П о ум о л чан и ю б азовы м


д л я элем ентов п ер ечи с­
Порядок следо­
^ S it = 7, л е н и я я в л я е т с я т и п in t.
вания не и м е ­ Beg = 2 5 ,
ет значения. Укажите зат ем . \
К ром е того, Rollover = 50, ^ ЗйИАеЛ1 число объявить и перечисление
одно и т о же о т в р т г^ ^ а ^ '^ ° со - ' т ипа, наприм ер hyte или
число можно Fetch = 10, ‘^ о е т с т в у е т . как эт о сделано 6 нижней
сопост авит ь ComeHere = 5, части страницы.
разны м име - \
Такая запись з а с т а в л я е т к о м ­
нам- Speak = 30, п илят ор использоват ь число,
кот ором у соо т вет ст вует
} э лем ен т перечисления. 8 дан­
ном случае ХО.
Вот фрагмент метода, использующего перечисле­
ние T r ic k S c o r e путем приведения к значению Этот оператор п р и ­
типа i n t и обратно: сваивает переменной
value значение ъо.
int value = (jint^rickScore.Fetch * 3; Численное значение Мо
ж е т быть преобразова­
MessageBox.Show(value.ToStringO); но обратно в элем ент
перечисления. Так как
TrickScore score = (TrickScore)value; ^ value = З Л score п р и -
сваиавается значение
MessageBox.Show(score.ToStringO); T ric k S c o re .fe tc K С о-
а о^ ...й
т б ег-и
т слтлбтбен
е н нноо метис,
м ет ой
с^лементы перечисления можно использовать как числа и производить с ними ^core.ToStringO возвра
разнообразные вычисления, а можно воспользоваться методом T o S tr in g O и^ает значение Fetch,
и рассматривать их как строки. При отсутствии явного присвоения, элементы
перечисления получают значения по умолчанию. Первому элементу присваи­
вается О, второму —1 и т. д.
А что делать, если нужно присвоить элементу перечисления очень большое значение? По умолчанию
все элементы принадлежат к типу i n t , поэтому нужно поменять тип перечисления при помощи опера-
тора
public enum TrickScore ; long {
' заставляет
Sit = 7, компилят ор относить эле-
Beg = 2500000000025
} Если принадлеж ность элем ент ов перечисления к т и п у long явно
не указана, при ком пиляции появит ся сообщение:
Cannot implicitly convert type 'long' to 'int'.

342 глава 8
перечисления и коллекции

Воспользуемся полученными сведениями для построения класса, описывающего играль­


ные карты
•п^аЖ ениг
Создайте новый проект и добавьте класс Card
Вам н уж н ы два о т к р ы ты х поля; S u i t (S p ad es, C lu b s , D ia m o n d s,
H e a r t s ) и V a l u e (A c e , Two, T h r e e ...T e n , J a c k , Q u e e n , K in g ) , a так ж е
п р е д н а з н а ч е н н ы е т о л ь к о д л я ч т е н и я п о л я N a m e ( А с е o f S p a d e s (т у з
пик), F i v e o f D ia m o n d s (п я т ер к а б у б е й )).

Задайте Аласти и карты с помощ ью двух перечислений


Воспользуйтесь уже знакомой вам командой A d d » Class, заменяя слово c l a s s на e n u m .
Убедитесь, что ( i n t ) S u i t s . S p a d e s = О, C l u b s = 1, D i a m o n d s = 2, a H e a r t s = 3. Bo вто­
ром перечислении: ( i n t ) V a l u e s . А с е должно быть = 1, T w o = 2, T h r e e = 3 и т. д. J a c k
(валет) = 11, Q u e e n (дама) = 12, а K i n g (король) = 13.

О Добавим картам свойство


С в о й ст в о Nam e д о с т у п н о то л ь к о для ч тен и я . М е т о д ч т е н и я д о л ж е н в озв р ащ ать стр ок у
с н а з в а н и е м к ар ты . Э т о т к о д в ы зы в а ет с в о й с т в о N am e и о т о б р а ж а е т результат:

C ard c a r d = new C a r d ( S u i t s . S p a d e s , V a l u e s . A c e ) ;
str in g cardN am e = c a r d .N a m e ; 4mO$fc>i э т о заработало.
в класс C ard нужно п о ­
П е р е м е н н а я c a r d N a m e д о л ж н а и м е т ь з н а ч е н и е “А с е o f S p a d e s ”. м е т и т ь конст рукт ор
с Эбумя п а р а м е т р а м и .
К н о п ка , отображаю щ оя название случайной карты
Будем выбирать карту, случайным образом присваивая число от О до 3 переменной
S u i t s , а числа от 1 до 13 - переменной V a l u e s . Воспользуемся встроенным классом
R a n d o m , позволяющим вызвать метод N e x t () тремя способами:

Возм ож ност ь R andom r a n d o m = n e w R a n d o m ( ) ;


в ы з ы в а т ь один
M tm o d разны м и
способами н а
i
i n t nu m berB etw een 0and 3 = r a n d o m .N e x t( 4 ) ;
i___
з ы
в а е т с я п ер егр уз-^
n t __________________________
n u m b erB etw een la n d l3 = random . N e x t (1,
a n y R a n d o m ln teg e r = random .N e x t О ;
14)
Г*”
^
ком (overloading). З д е с ь с л у ч а й н ы м о б р а з о м в ы б и р а е т с я ч и с л о о т г до
О ней м ы п о г о в о ­
р и м п о з ж е ...
Часто
Задаваем ы е
Боцр>ос:ь1
При наборе метода R a n d o m . N e x t ( ) появилось ОКНО от записи «3 of 3» находятся стрелки, которые позволяют вы­
IntelliSense с надписью «3 of 3». Что это значит? бирать варианты. Это полезно в случаях, когда метод имеет
множество определений. Вам же важно выбрать правильный
Q; Вы увидели метод, который был перегружен. Возможность вариант метода N e x t () I Более подробно об этом мы погово­
вызывать один метод несколькими способами называется пере­ рим чуть позже.
грузкой. При работе с классом, в составе которого присутствуют
randoffi.Mextd _____________ _____ ___________________ __
подобные методы, ИСР показывает вам все возможные вариан­
Rando«,H€Xt(int siinValue, int maxValue)
ты. В рассматриваемом случае класс R andom имеет три метода
Returns 3 random number within з specified range.
N e x t ( ) . Когда вы печатаете random.Next(), появляется окно
IntelliSense с параметрами всех вариантов. Слева и справа
minValue; Themdusimlowerboundcfthemnilannartierr^umed.

дальше ► 343
массивы... ному они нуж ны?

^ажнение Колода карт позволяет проиллюстрировать важность ограничения значений. Было бы


странно обнаружить джокера крестей или 13 червей. Вот как выглядит код класса Card.
ешение
en u m S u i t s {

D iam on d s, \
к единще, т рет ий к двойке и т. д.
H earts
}

В данном случае значения по


умолчанию переопределяются
и \/<я(ие5.Асе равно 1.
Счет начинается с тузов.

c la ss Card
p u b li lu it { get; set; }
p u b lic V alu e { get; set; }

Метод чтения свойства


p u b lic C a r d (S u its su it, V a lu es v a lu e ) { Name использует строку,
th is.S u it = su it; полученную из имени при
th is.V a lu e = v a lu e; помощи метода ToStnngQ.
}
p u b lic str in g Name {
get { r e tu r n V a lu e .T o S tr in g O + of " + S u it.T o S tr in g O ; }
генерирует слу^ай-
^ Код кнопки, щелчок на которой вызывает Mh, которое
} окно с названием случайной карты. мы присваиваем
У ‘^^рсчислению.
Ra n dom r a n d o m = n e w R a n d o m ( ) ;
p r iv a te v o id b u tto n l_ C lic k (o b je c t sen d er, E ventA rgs e) {
Card c a r d = new C a r d ( ( S u i t s ) r a n d o m . N e x t ( 4 ) , (V a lu e s )r a n d o m .N e x t(1, 1 4 ));
M e ssa g e B o x .S h o w (c a r d .N a m e );
}

344 глава 8
перечисления и коллекции

Создать колоду карт мо)кно было при помои^и массиба..


Как создать класс, представляющий собой колоду карт? Нужно
отслеживать каждую карту в колоде и их порядок. Данная задача
решается при помощи массива Card: первой карте присваивает­
ся значение О, следующей — 1 и т. д. Отправной точкой сделаем Объявление массива
класс Deck, описывающий полную колоду (52 карты). сокращено для
экономии места на
^^Ранице. Но здесь
^^ечи слен ь, все
cl a s s D e c k { 5^2 карты в колоде.
p r i v a t e Card[] cards = {
n e w C a r d ( S u i t s - S p a d e s , Values.Ace),
n e w C a r d (Suits. Spades, V a l u e s .Two) ,
n e w C a r d ( S u i t s .Spades, V a l u e s .T h r e e ) ,
// ...
new Card(Suits.Diamonds, V a l u e s .Q u e e n ) ,
new Card(Suits.Diamonds, V a l u e s .K i n g ) ,

public void P r i n t C a r d s O {
for (int 1 = 0; i < cards .Length; i-i--i-;
C o n s o l e . W r i t e L i n e ( c a r d s [ i ] . N a m e ()]
}

... но что если бам захочется большего?


Представьте все, что можно делать с карточной колодой. Напри­
мер, для моделирования игры нужно менять порядок карт, а так­
же добавлять их в колоду и убирать из нее. Эти задачи уже не ре­
шить при помощи массива.

ШТУРМ
Как бы вы д о б ав и л и м етод s h u f f l e О , м од елирую щ ий процесс т а ­
сован и я к о л о д ы ? Как см о д е л и р овать п р оце сс сдачи к а р т ? Каким о б ­
разом д о б а в и ть карты в к о л о д у ?

дальше > 345


предметы коллекционирования

Проблемы работы с массивами


Массивы прекрасно подходят для хранения фиксированного списка значений или ссылок. Но как толь­
ко возникает необходимость поменять элементы местами или добавить новые значения, ситуация ос-
ложняется.

Каждый массив имеет длину, которую вы должны знать. Для получения пустых
элементов можно воспользоваться ссылкой на неопределенное значение null:

П а р а м е т р L en g th э т о ­
г о м а с с и в а р а в е н 7 , но
в нем хранит ся и н ф о р ­
мация т олько о т рех
карт ах.

О Чтобы отследить количество имеющихся карт, создадим поле t o p C a r d типа


i n t для хранения информации об индексе последней карты. Параметр L e n g t h
нашего массива имеет значение 7, в то время как t o p C a r d равно 3.

^m L от слеж ивает количе-


c ^ H d S Z , элем ент ы
с с ы л а ю т с я н а з н а ч е н и е nu ll.

Это может сделать


встроенный метод
.NET A r r a y . R e s i z e Q .
о Можно добавить метод Р е е к {), возвращающий ссылку на верхнюю карту, сы­
митировав взятие верхней карты из колоды. А как добавить карту? Если пара­
метр t o p C a r d меньше длины массива, карта добавляется в массив, а значение
t o p C a r d увеличивается на 1. В противном случае нужно создать новый, боль-
ший массив и скопировать туда имеющиеся карты. Удаляя"кЕфтуГвы вычитаете'
1 из to p C a rd и возвращаете элементу массива значение null. А как удалить кар­
ту из середины? После удаления карты 4 нужно сместить вниз карту 5, затем
карту 6 и т. д.

346 глава 8
перечисления и коллекции

Коллекции
в .NET Framework существует множество классов коллекций (collection), позволяющих легко решить
вопросы с добавлением и удалением элементов массива. Чаще всего используется коллекция L ist< T > .
L ist< T > позволяет легко добавить, удалить, выбрать элемент и даже поменять порядок следования
элементов.

Н ачнем с создания нового экзем пляра L is t< T >


Коллекции, как и массивы, принадлежат к определенному типу. Тип объектов
или значений, которые должны там храниться, указывается в момент созда- Иногда для
ния коллекции в угловых скобках о с помощью оператора new. ста^(^упро -
L is t < C a r d > c a r d s = new L is t < C a r d > { ) щения код <Т>
будет о п у­
скаться-
Tun <Card> был указан
в м омент создания
коллекции, так что
теперь т ам хранятся
только ссылки на объ­ Символ <Т> указы вает на
екты типа Card. обобщенную коллекцию .
Символ Т заменяется типом,
L i s t < i n t > означает L i s t значе­
ний типа i n t .

О Д обавление в ко л л е кц и ю L is t< T >


В коллекцию L ist< T > можно добавить произвольное количество элемен­
тов (главное, чтобы они были полиморфны с типом указанным при создании
^ ^ ^ L is t< T > ) .
Они м огут c a r d s.A d d (n e w C a r d (S u its .D ia m o n d s , V a l u e s . K i n g ) ;
быть C ard ( S u i t s , c l u b s . V a lu e s .T h r ee ) ;
интерфейсам,
абстрактным c a r d s . A d d ( n e w C a r d ( S u i t s . H e a r t s , V a l u e s . A c e ) ; в коллекции, как
классам, базо­ и в массиве, элементы
вым классам к хранятся в определен­
и т. п. / ном порядке. Сначала
В коллекцию List можно король бубей, зат ем
добавить произвольное тройка крестей и п о ­
число карт, достаточно следним — т у з червей.
воспользоваться методом
AddQ. Если количество
объектов превыилает ко­
личество свободных мест ,
размер коллекции увеличи­
вается.

^ент
дальше > 347
какой прогресс!

Коллекции List
lOiacc L i s t встроен в .NET Framework и позволяет делать вещи,
о которых вы не могли даже мечтать, имея в арсенале только ста­
рые добрые массивы. Перечислим новые возможности:

L ist 6 ^ g объект е
О М ож но создать ко л л е кц и ю
L ist< E g g > m yC arton = new L ist< E g g > ();

о Добавить в нее объект


E gg X = new E gg (); © Теперь в к о л л е к ц и и
m y C a rto n .A d d (x );
имеется объект В 3 3 -

О Добавить ещ е один объект


E g g у = new E g g {);

m y C a rto n . A dd(у );
©
увеличивается
принят ь ^
0<^орой обьект Е дд.

Узнать количество объектов в ко л л екц и и


in t th e S iz e = m y C a r to n .C o u n t; У

Узнать, содержатся л и в ко л л екц и и объекты


о пределенно го ти п а T ' ’“ ” “'’
bool Is in = m y C a r to n .C o n ta in s( х ) ;

О О предел ить и х полож ение Элемент X имеет индекс О, в то


время как элемент у — индекс 1.
in t id x = m y C a r to n .In d e x O f( у ) ;

/
Q Удалить оттуда элем ент
m y C a r to n .R em o v e( у ) ;
^ 1 )а J —

р м е р « о « '“ ““ “

348 глава 8
перечисления и коллекции

Возьми в руку карандаш Заполните таблицу, сверяясь с расположенным слева кодом. Вам нужно
показать, как выглядел бы код, если бы вместо коллекции использовался
массив. Мы не ожидаем от вас 1 0 0 % правильных результатов, просто по­
пытайтесь догадаться.

Предположим, ч т о эти
операторы выполняются Мы начали выполнять
по порядку. упражнение...
\ Коллекция Обычный массив
N/
L is t < S t r in g > m y L ist = S t r in g и m y L is t = n e w S tn n g [ Z ] ;
new L i s t < S t r i n g > ( ) ;

S tr in g a = "Y ay!"; S tr i n g a = " У д у /” ;


m y L ist.A d d (a );

S t r i n g b = " B u m m er"; S tr i n g b = ''B u m m e r ”;


m y L ist.A d d (b );

in t th e S iz e = m y L ist.C o u n t;

G uy о = m y L i s t [ 1 ] ;

bool isIn = m y L ist.C o n ta in s (b );

Подсказка: П от ре­
бует ся более одной
ст роки кода.

дальше > 349


один размер для всего

щЫьт в руку карандаш Итак, вот как выглядит правильный вариант кода с использова­
нием вместо коллекций обычных массивов.
Решение
Коллекция Обычный массив
L is t < S t r in g > m yL ist = String [] myList = new String [2];
new L i s t < S t r i n g > { ) ;

S tr in g a = " Y a y !" String a = "Yay!";


m y L ist.A d d (a );
m y L i s t [ 0 ] = aj

S tr in g b = "B u m m er"; string b = "Bummer" ;


m y L ist.A d d (b );
m y L i s t [ l ] = b;

in t th e S iz e = m y L is t. C ou n t;
in t th e S ize - m y L iSt. L e n g t h ;

G uy o = m y L i s t [ 1 ] ;
Guy 0 - m y L ist[l];

bool isin = m y L ist. C o n ta in s(b );


b o o l is in = false;
f o r ( i n t i ■= O ; i < m y L i s t .
L e n g t h ; (V+) {
if (b == m y L is t[ i] ) {
isin = t r u e ;

Коллекции используют методы, как и уже зна­ Массивы довольно сильно вас ограничивают.
комые вам классы. Чтобы увидеть список до­ В момент создания массива требуется указать
ступных методов, введите . после имени L i s t . его размер, а код для нужных процедур при­
Параметры методам передаются так же, как ходится писать вручную.
это делалось для созданных вами классов.

в .NET Fm m ework сущ ест вуем


класс Array, упрощающий некоторые
операции, но коллекции все равно долее
просты в использовании, поэт ому мы
сконцентрируемся в основном на них.

350 глава 8
перечисления и коллекции

Динамическое изменение размеров .у п р а ж н е н и е ! ^

в момент создания коллекции вы не указываете информацию о ее длине.


Она растет или сжимается в соответствии с количеством объектов внутри. объявляем коллек-
Рассмотрим это на примере. С о з д а й т е к о н с о л ь н о е п р и л о ж е н и е и добавьте Shoe
код в метод M ain (). Мы будем и с п о л ь з о в а т ь о т л а д ч и к для пошагового про­
смотра кода и отслеживания происходяш;его.
(ОдувнойШ каф).
L ist< S h o e> sh o e C lo se t = new L is t< S h o e > {
Оператор new можно
использовать внутри
s h o e C l o s e t .A dd (new S h o e O метода List-AddQ.
{ S ty le = S t y l e . Sneakers, C o lo r = Черный" }) ; ^ J
s h o e C l o s e t . A d d (n e w S h o e O f o r e a c h — это спец и аль ны й
{ S ty le = S ty le .C lo g s , C o lo r = " К о р и ч н ев ы й ' } ) ; ц икл, р аб отаю щ и й с ко л л ек­
s h o e C l o s e t .A dd (new S h o e O ц иям и. Он и спол ь зует о п ер а­
{ S ty le = S ty le .W in g tip s , C o l o r = "Черный" }); торы д л я каж д ого эл ем ента
s h o e C l o s e t .A d d (new S h o e O колл екции. В д ан н о м случае
{ S ty le = S ty le .L o a fe r s , C o l o r = "Белый" }); со зд ается и денти ф и като р
s h o e C l o s e t .A d d ( n e w S h o e O
с им енем sho e, котором у
п р и св аи в аю тся элем енты
{ S ty le = S ty le .L o a fe r s , C o lo r = "К расный" });
колл екции по очереди.
s h o e C l o s e t .A d d ( n e w S h o e O
{ S ty le = S t y l e . Sneakers, C o lo r = "Зеленый" }); 7
Цикл foreach работает
Возвращает число и с массивами тоже.
in t nu m berO fSh oes = s h o e C l o s e t . C o u n t;
^ объектов Shoe
перечислении.
(fo r e a c h (Shoe s h o e in sh o eC lo se'O ' {
Это класс Shoe
sh o e .S ty le = S ty le .F lip flo p s ; и перечисление Style.
s h o e . C o lo r = "Оранжевый"
Цикл foreach
} просматривает c la ss Shoe^
всю обувь в шкафу. p u b lic S ty le S ty le ;
Метод RemoveQ удаляет
объект по ссылке, мет од — ^ p u b lic str in g C o lo r;
RemoveAtQ — по индексу. ^ Метод ClearQ
удаляет из }
перечисления все
s h o e C l o s e t . R e m o v e A t (4) объекты. enum S t y l e {
Д о удаления веек Sneakers,
Shoe th ir d S h o e = sh o e C lo se t [3]; элементов перечис­ L oafers,
Shoe secondShoe = sh o e C lo se t [2]; ления мы сохранили
две ссылки на обувь. S a n d a ls,
s h o e C lo s e t. C le a r ();
Одна из них была воз- F lip flo p s,
оращена, а вторая ~ W in g tip s,
нет.
s h o e C lo se t.A d d (th ir d S h o e );
C lo g s,
if (sh o eC lo set.C o n ta in s(se co n d S h o e ))
}
' C o n s o l e . W r i t e L i n e ( " У д и в и т е л ь н о !"

Эта строчка не будет запускаться, пот ому что мет од ContalnsQ


возвращает значение false. В пустое перечисление мы добавили дальше > 351
только элемент thirdShoe, но не элемент fifthShoe.
привилегии членства

Обобщенные коллекции
Вы уже видели, что коллекция может состоять как из
строк, так и из ботинок. Можно поместить туда целые
числа или произвольные созданные вами объекты.
Именно поэтому класс L i s t является о б о б щ е н н о й
коллекцией. В момент его создания нужно указать
КЛЮЧЕВЫЕ
тип значений, которые могут храниться внутри.
МОМЕНТЫ
Эта запись не означает, что вы добавляете букву Т. L i s t — ЭТО класс .NET Framework.
Она всего лииль показывает, что класс или инт ер­
фейс может работать со значениями любого типа, Класс L i s t динамически меняет свой
(достаточно указат ь эт от т ип в скобках). З а ­ размер.
пись List<Shoe> означает, что коллекция содержит
только элементы класса Shoe. _ Для добавления элементов в класс L i s t
используйте метод A d d ( ) . Удалить
List<T> name = new List<T>() элементы можно с помощью метода
R e m o ve ().
Класс List очень
KUM (^озволяю и^м лю дш m Метод R em oveA t () позволяет удалять
объекты, начиная с заданного индекса.
^ Т м о Т у т ^ а с с и в ы (плюс eu,e кое-что).
Тип значений, которые могут храниться в
коллекции, указывается в угловых скобках.
В .NET Framework существуют обобщенные интер)- Запись L i s t < F r o g > означает, что в
фейсы, позволяющие созданным вами коллекциям коллекции L i s t могут храниться только
работать с любыми типами. Класс L i s t реализует объекгы класса F r o g .
эти интерфейсы, именно поэтому работа с коллек­
цией целых чисел практически не отличается от ра­ Метод In d e x O f () определяет индекс
боты с коллекцией объектов класса Shoe. для заданного объекта.

Убедитесь самостоятельно. Наберите L i s t в ИСР, Узнать количество элементов класса L i s t


щелкните правой кнопкой мыши и выберите ко­ можно при помощи свойства C ou nt.
манду Go То Définition. Вы увидите объявление Метод C o n t a i n s () проверяет
класса L i s t . Он реализует несколько интерфей­ коллекцию на наличие объектов.
сов:
Отсюда берутся f o r e a c h — это цикл, перебирающий
методы RemoveAtÇj, элементы коллекции и выполняющий
IndexOfO w InsertQ. для каждого указанный код. Его
синтаксис: f o r e a c h ( s t r i n g s
c la s s L ist< '^ : fL is t< T ^ ^ i n S t r i n g L i s t ) . Вам не нужно
^ C o l 1 e с t io n < T > b ÊEnum eraEXë<T >> IL ist, заботиться об инкрементном увеличении
Г IC o lle c tio n , lE n u m era b le ^ на единицу, перебор элементов коллекции
0<тсюда берутся методы Этот выполняется автоматически.
Add(), ClearQ, СорцТо() позволяет использ
и Remove(). (Это верно ш кл foreact^.
для всех обобщенных
коллекций).

352 глава 8
перечисления и коллекции

|^ а Г н и т ь 1 С К оД »М
В о с п о л ь з у й т е с ь ф р а гм е н та м и кода
д л я р еко нстр укц и и ф о р м ы с кнопкой,
н аж ати е которой б уд ет в ы в о д и ть п о ­
к а за н н о е ниж е окно с с о о б щ е н и е м .
a . R e m o v e A t (2 );

List<string> ^ ^ ^ i e ^ L i s t < s t r i n g >

p u b lic v o id p r in t L ( L is t < s t r in q > a ) ( 1


if (a.Contains("two")) {
private void buttonl_Click(object
a.Add(twopointtwo) ;
sender, EventArgs e ) {

a .A d d (fir s t);
a .A d d (se c o n d );
a.Add(third)_;^______
..- ...........ге зи 1 Г Т ~ ^ Г Г Г ~ |
if (a.Contains ("three") ){ Iswu^l
-1 _T / w ^ ____ \ -
a.Add("four");
}

in a)
{
re s u lt

MessageBox.Show(result:

(a.indexOf("four") ГГ"
^ a.Add(fourth);

li^ 5 5
[p ri^ tL T ^
string zilch = "zero";
string first = "one";
zero
one string second = "two";
three
four string third = "three";
42 string fourth = "4.2";
string twopointtwo = "2.2";
I OK 1

дальше ► 353
решение упражнения
П ом ните, в главе 3 го ворилось об и н туи ­
ти в н о п онятны х им енах? С ей час мы их с о ­
l^e^eHue 5аДаЧи зн ател ь н о изб егаем , та к как это слиш ком
у п р о щ а е т реш ени е ребуса. Но в ж и зн и такие
с МаГнищаМи им ена, как рпп1Ь(), и спользовать не стоит!

private void buttonl_Click(object sender, EventArgs e)

{
List<string> а = new List<string>
string zilch = "zero";
string first = "one";
zero string second = "two";
one
three
string third = "three";
four string fourth = " 4 . 2 " ;
4.2
string twopointtwo = "2.2";
Sb/ понимаете,
OK
a .

if
W W 'fi'r" '" "
Add(second) ;
- ------
(a.Contains("three")){
I почему « z . z »
никогда не п о ­
падет в кол­
лекцию. несмо­
a . A d d ("four"); т ря на то что
эт от элемент
} оыл объявлен?

if (a.IndexOf("four") i= Метод printLQ ис­


4) { пользует цикл
Memo? RemoveAtQ foreach для про­
удаляет элемент, a . A d d (fourth); смотра коллекции
следующий за эле­ } строк, добавления
м ент ом То есть каждого элемента
т рет ий элемент общую строку ы
коллекции. отображения резуль­
тат а 8 окне диалога.

public void printL (List<string


Цикл foreach а) {
по очереди
отображает - a
все элементы in a)
коллекции.
result += "\n"
+ element;
)
MessageBox.Show(result);

354 глава 8
перечисления и коллекции
Ч аст»
ЧаДаБаеМые
Б о и р о с :^ ,!
Разве перечисления и класс L i s t Возможна ли коллекция, не имею­
выполняют
ВЫ1 не одну и ту же задачу? щая типа?
ща5
Вы можете преобразовать коллек­

0^ I Основным и главным отличием


перечислений является то, что они от­
цию в массив при помощи метода
Т о А г г а у () ...обратное преобразова­ 0^ ! Нет. Любая обобщенная коллекция
должна принадлежать определенно­
носятся к типам, в то время как коллекции ние совершается при помощи одного из му типу. В C# имеются и коллекции
L i s t — это объекты. перегруженных конструкторов объекга A r r a y L i s t S , хранящие объекты
L ІSt<T > . произвольного типа. Чтобы воспользо­
Перечисление является удобным ваться ими, нужно включить в верхнюю
способом хранения списков констант, Почему коллекция называется часть кода строчку u s i n g S y s t e m .
дающим возможность обращаться к ним Обобщенной»? C o l l e c t i o n s ;. Но вряд ли это вам
по имени. Перечисления увеличивают чи­ когда-либо потребуется, так как коллекция
табельность кода и гарантируют ислоль- L i s t < o b j e c t > в большинстве случаев
; Обобщенная коллекция — это объект
зование корректных имен для доступа замечательно работает!
(или встроенный объект, позволяющий
к нужным вам значениям.
хранить множество других объектов), ко­
В коллекцию L i s t можно записать прак­ торый настраивается под хранение одного
тически все. Так как речь идет о списке типа данных.
объектов, каждый элемент может иметь
собственные методы и свойства. Пере­
числениям же можно назначать только
Вы объяснили, что такое «коллек­
ция». Но почему «обобщенная»?
Создавая объект
значимые типы (например, из перечис­
ленных в начале главы 4), хранить в них
; в супермаркеты товар доставляют
list, вы всегда
ссылочные переменные нельзя.

Перечисления не могут динамически ме­


в'однотипных коробках, на которых на­
писано название содержимого («Чипсы»,
указываете тип
нять размер. Они не реализуют интерфей­ «Кола», «Мыло» и т. п.). Важно, что внутри
сов, не имеют методов, а для сохранения коробки, а не то, как это выглядит. данных, которые
значения из перечисления в переменной
другого типа потребуется операция при­ Так же и с обобщенными типами данных. будут в нем хра­
ведения. Как видите, это разные способы Объект L i s t < T > , наполненный объ­
хранения данных. ектами S h o e , объектами C a r d , целыми
числами или даже другими коллекциями,
ниться. Сохранять
Зачем при таком мощном инстру­ служит таким же контейнером. Вы можете
добавлять, удалять, вставлять элементы
можно значимые
мен как L i s t , мне могут потребо­
менте,
ват
ваться массивы? вне зависимости от их типа. ff
тины (int, bool
0: ; Бывают случаи, когда работать при­
ходится с фиксированным количеством
Термин «обобщенный» yKaj или decimal) или
зывает, что хотя каждый
элементов или с последовательностью
значений фиксированной длины.
экземпляр List может сохра­
нять данные только одного
класс.
у ' Массивы занимают меньиле т ипа, класс List работает
/ мест а в памяти и быстрее с любыми т ипами данных.
{ обрабатываются процессором,
впрочем, выигрыил о произ­ Именно эт у функцию выполняет сиМ
водительности получается вол <Т>. Вместо него вы указываете, како­
незначительным- Если ваила му т ипу принадлежит рассматриваемый
программа работает слиш ­ обш кт List. Этим обобщенные коллекции
ком медленно, вряд ли пробле­ отличаются от всего, что вы использовали
м у м о ж н о реш ит ь переходом
от коллекций к массивам. раньиле.

дальше > 355


начнем отсюда

инициализаторы коллекций
C # п о з в о л я е т у м е н ь ш и т ь к о л и ч е с т в о в в о д и м о го т е к с т а п р и с о з д а н и и к о л л е к ц и и . В ы м о ж е те в о с п о л ь з о ­
в а ть ся и н и ц и а л и з а т о р о м к о л л е к ц и й ( c o l l e c t i o n in i t i a li z e r ) , к о т о р ы й д о б а в л я е т э л е м е н ты в к о л л е к ц и ю
н е п о с р е д с т в е н н о в м о м е н т ее со зд а н и я . ^ „
Этот код вы видели несколько
страниц назад. Он создает
объект List<Shoe> и заполняет
к/ его объектами Shoe.
Llst<Shoe> shoeCloset = new List<Shoe>();
shoeCloset.Add(new ShoeO { Style = Style.Sneakers, Color = "Черный" });
shoeCloset.Add(new ShoeO { Style = Style.Clogs, Color = "Коричневый" });
shoeCloset.Add(new ShoeO { Style = Style.Wingtips, Color = "Черный" });
shoeCloset.Add(new ShoeO { Style = Style.Loafers. Color = "Белый" });
shoeCloset.Add(new ShoeO { Style = Style.Loafers, Color = "Красный" });
shoeCloset.Add(new ShoeO { Style = Style.Sneakers, Color = "Зеленый" });

Инициализатор коллекции
Обратите внимание: может быть создан добав­
каждому объекту Shoe лением каждого полученного
присваивается началь­ при помощи метода AddQ
ное значение при помощи элемента к оператору,
инициализатора. формирующ ему коллекцию.

List<Shoe> shoeCloset = new List<Shoe>() {


За оператором y new ShoeO { Style = Style.Sneakers, Color = "Черный" },
создания кол­
лекции следуют new ShoeO { Style = Style.Clogs, Color = "Коричневый" },
фигурные скобки,
о которых на­ / new ShoeO { Style = Style.Wingtips, Color = "Черный" },
ходятся разде­ / new ShoeO { Style = Style.Loafers. Color = "Белый" },
ленные запятыми
операторы new. { new ShoeO { Style = Style.Loafers. Color = "Красный" },
\ n e w ShoeO { Style = Style.Sneakers, Color = "Зеленый" },
}:
Инициализатор
может содер­
жать не только
Инициализатор коллекций делает код ком­
набор опера­
торов newJ но пактнее, комбинируя создание коллекции
и переменные.
с добавлением начального набора элементов.
356 глава 8
перечисления и коллекции
Ф
У праж нение!
Коллекция уток
Р а с с м о т р и м к л а сс D u c k , с л е д я щ и й з а в а ш е й к о л л е к ц и е й у т о к . ( В ы же
к ол л ек ц и он и р уете уток, правда?) С озд ай те к о н со л ь н о е п р и л о ж ен и е
и д о б а в ь т е в н е г о класс D u c k и п е р е ч и с л е н и е K in d O f D u c k (В и д ы уток).

К аж дая у т к а и м е е т р а з м е р ,
эт а — 1 7 дю йм ов.

У т ки бы ваю т
д и к и е ... ( m a l l a r d s ) .
c la s s D uck {
p u b lic in t S iz e ;
p u b lic K in d O fD u ck K in d ;
И даж е д е р е в я н н ы е
м ан к и (d e c o y s) }
М ускусны е ут к и К л а с с и м е е т два о т ­
(M u sc o vy ).
I кры т ы х поля и м е т о ­
ды , к о т о р ы е з д е с ь не
показы ваю т ся.

't m i'
enum K in d O fD u ck {
M a lla r d ,
инициализатор коллекции уток M uscovy,
у нас ш есть уток, п о это м у мы со зд а д и м к ол л ек ц и ю L is t < D u c k > D ecoy,
с состоящ им из ш ести операторов инициализатором . К аж­
}
д ы й и з э т и х о п е р а т о р о в с о з д а е т н о в у ю утку, у к а з ы в а я з н а ч е н и я
п о л я s i z e и K i n d . Д о б а в ь т е э т о т к о д в м е т о д M a i n () в ф а й л е
П еречи слен и е
P r o g r a m .c s :
K in d O fD u c k х р а н и т и н ­
ф о р м а ц и ю о видах у т о к
L ist< D u c k > ducks = new L ist< D u c k > О { Ö коллекции.

new D uckO { K in d = K in d O fD u c k .M a lla r d , S iz e = 17 },

new D uckO { K in d = K in d O fD u c k .M u s c o v y , S iz e = 18 }, Д обавьт е к проект у


класс Р и ск и п ер еч и с­
new D uckO { K in d = K in d O f D u c k . D e c o y , S iz e = 14 }, л е н и е K in d O f D u c k .

new D uckO { K in d = K in d O fD u c k .M u sc o v y , S iz e = 11 },

new D uckO { K in d = K in d O f D u c k . M al l a r d . S iz e = 14 },
M a in Q к о д , в ы в о д я щ и й р е з у л ь -
new D uckO { K in d = K in d O f D u c k . D e c o y , S iz e = 13 }, °ст ав-
^ л я ет р е з у л ь т а т видим ы м
^ — п о к а вы не н а ж м е т е к л а ви ш у.

II Этот метод не позволяет результату исчезнуть, пока вы е г о не прочитали


C o n so le .R eadK ey();

дальше > 357


расставим уток по ранжиру
%
CopmupoBka элементов коллекции
С о р т и р о в к а ч и с е л и л и букв — в п о л н е о б ы ч н о е дело. Н о в о т к а к о т с о р т и р о ­
вать о б ъ е к т ы , о с о б е н н о п р и н а л и ч и и н е с к о л ь к и х п олей? М о ж н о р а с п о л о ­
ж и т ь о б ъ е к т ы п о им е н а м , в д р у г и х сл уч а я х и м е е т см ы сл с о р т и р о в а т ь п о д л и ­
не и л и п о дате р о ж д е н и я . С у щ е с тв у е т м н о го с п о с о б о в р а с п о л о ж и т ь о б ъ е к т ы
п о поря д ку, и к о л л е к ц и и п о д д е р ж и в а ю т все.

У то к мо)кно располо)кить по размеру... маленькой к самой большой

с о р т и р о в к а п о в и д у у т к и .^ _
...UAU по Виду...

Сортировка объектов
осуществляется с помощью
интерфейса 1Сотрагег<Т>,
о котором мы поговорим
чут ь позже...
Встроенные методы copmupoBku
К а ж д а я к о л л е к ц и я с н а б ж е н а м е то д о м S o r t ( ) , м е н я ю щ и м п о р я д о к эл е м е н ­
т о в . С у щ е с тв у ю т за д а н н ы е с п о с о б ы с о р т и р о в к и б о л ь ш и н с т в а в с т р о е н н ы х
т и п о в и кл а ссов, к р о м е т о г о , м о ж н о н а п и с а т ь с п о с о б с о р т и р о в к и в а ш е го
с о б с т в е н н о го класса.

SortO

Сортировка всего \
лиш ь меняет по -
рядок расположе­ ^ е /г г Ы '
ния элементов. 17
I дюймов

358 глава 8
перечисления и коллекции
*

и н т е р ф е й с IC o m p a r a b le < D ( ie k >

М е т о д L i s t . S o r t О ум е е т с о р т и р о в а т ь о б ъ е к т ы л ю б о го т и п а и кл а ссы ,
к о т о р ы е р е а л и з у ю т и н т е р ф е й с 1 С о т р а г а Ы е < Т > . В со ста в е э т о г о и н т е р ­
Любой класс
ф е йса н а х о д и т с я т о л ь к о м е то д С о т р а г е Т о { ) . S o r t ( ) , к о т о р ы й и с п о л ь з у ­
е т м е то д С о т р а г е Т о () о б ъ е к та для с р а в н е н и я с д р у г и м и о б ъ е к та м и . В о з ­
может рабо­
вращ аем ое з н а ч е н и е (т и п а in t ) п о ка зы в а е т, к т о д о л ж е н б ы т ь п е р в ы м .
тать со встро­
Д ля к о л л е к ц и й о б ъ е кто в , к о т о р ы е н е р е а л и з у ю т и н т е р ф е й с IC o m p a -
г а Ы е < Т > , в .N E T им еется д р у го й сп о со б . М е т о д S o r t () м о ж н о передать э к ­ енным методом
зем пляру класса, ре а л и зую щ е го IC o m p a r e r < T > . Э т о т и н т е р ф е й с т о ж е им еет
всего о д и н м етод и п о зв о л я е т задавать с п е ц и а л ьн ы е с п о с о б ы с о р т и р о в к и . Sort ( ) объекта
Метод СотрагеТоО сраВнибает два объекта List с помощью
М о ж н о о т р е д а к т и р о в а т ь класс D u c k т а к и м о б р а зо м , ч т о б ы о н н а ча л реа­ IComparable<T>
л и з о в ы в а т ь и н т е р ф е й с IC o m p a r a b le < D u c k > . Д л я э т о г о д о б а в и м м е то д
С о т р а г е Т о ( ) , и с п о л ь з у ю щ и й с с ы л к у н а о б ъ е к т D u c k в к а ч е с тв е пар а м е ­ и СотрагеТо().
тр а . Е сл и утка , взя та я для с р а в н е н и я , д о л ж н а о ка за ть ся п о с л е а н а л и з и р у е ­
м о й у т к и , м е то д С о т р а г е Т о {) в о з в р а щ а е т п о л о ж и т е л ь н о е ч и с л о .

О б н о в и т е класс D u c k п уте м р е а л и з а ц и и и н т е р ф е й с а IC o m p a r a b le < D u c k > ,


ч т о б ы о т с о р т и р о в а т ь у т о к п о разм еру:

Реализуя 1СомрагаЫе<Т> J
c la s s D uck : IC o m p a r a b le< D u ck > { вы указываете т ип
p u b lic in t S iz e ; парамет ра, по которому
осуществляется сравнение.
p u b lic K in d O fD u c k K in d ;

Так вы гл я д ^ ^ о л ь^ p u b lic in t С о т р а г е Т о (D u ck duckT oC om pare) {

Г л т » а г е Т ^ ( ) - i f ( th is .S iz e > duckT oC om pare.S iz e )


retu rn 1;
знамения п о л е й S ^ e
д в ц х y vn o K . Е с л и e ls e if (th is .S iz e < duckT oC om pare. S iz e )
m кот орую ссы лает ­
ся с л о в о this ^o^fc-tue. retu rn -1 ;
, Чтобы получит ь ряд от самой
-%■ маленькой до самой большой
A 6 случае совпадения retu rn О; ут ки, мет од CompareToQ должен
р азм еров - ноль. J возвращать положительное число
при сравнении с уткой меньшего
размера.
}
Д о б а в ь т е э т о т к о д в к о н е ц м етод а M a i n { ) д о в ы з о в а м е то д а C o n s o l e .
R e a d K e y О , и к о л л е к ц и я у т о к будет о т с о р т и р о в а н а п о размеру. П о м е с т и т е
т о ч к у о с т а н о в а в м е то д С о т р а г е Т о () и в о с п о л ь з у й т е с ь о тл а д ч и к о м , ч т о ­
б ы п о н я т ь , к а к все раб о та е т.

d u c k s.S o r t ();

дальше > 359


пример сортировки
%

Способы copmupoßku
Д л я к о л л е к ц и й сущ е ств уе т с п е ц и а л ь н ы й в с т р о е н н ы й в .N E T
F ra m e w o rk и н т е р ф е й с , п о з в о л я ю щ и й со зда ть о т д е л ь н ы й класс для
Способ сортировки
с о р т и р о в к и с о с т а в л я ю щ и х о б ъ е к та L i s t < T > . Р е а л и з у я и н т е р ф е й с
IC o m p a r e r < T > , вы объясняете ко л л е кц ии, каким способом н уж н о
зависит от способа
у п о р я д о ч и т ь ее с о д е р ж и м о е . З адача в ы п о л н я е т с я с р е д с тв а м и м е то ­
да C o m p a re ( ) , к о т о р ы й б е р е т п а р а м е т р ы д в у х о б ъ е к т о в х и у и воз­
реализации
в р а щ а е т ц ел ое ч и с л о . Е с л и х м е н ь ш е , чем у, в о зв р а щ а е тс я о т р и ц а ­
те л ь н о е ч и с л о . В случае и х р а в е н с тв а в о з в р а щ а е тс я н о л ь . Н у а е сли
интерфейса
X б ол ьш е , ч е м у , будет в о з в р а щ е н о п о л о ж и т е л ь н о е ч и с л о .
IComparer<T>.
В о т п р и м е р о б ъ я в л е н и я класса, с р а в н и в а ю щ е го о б ъ е к т ы D u c k п о
размеру. Д об авьте э т о т класс к сво е м у п р о е к ту , з

f r “ “
сортируемых объектов- Duck

\ Тип сравниваемых
class DuckComparerBySize : IComparer<Duck> значений всегда бу­
дет совпадать.
{ public int Compare (Виск'^хГвис^^^Т^
{ м ет од
целое имело

О т рицат ельное число означает ,


л чт о X должен ст оят ь перед у,
т а к как х «меньиле, ч ем » у.

^^сдоват ь зд долж ен
p означает совпадение
р«м еро6 У-

Добавьте метод PnntPucks


в класс Program, чтобы
Это метод вывода ут ок обеспечить вывод данных.
в виде коллекции List<Duck>.

p u b lic sta tic


Л
v o id P rin tD u c k s(L ist< D u ck > ducks)
\
Обновите метод MainQ
таким образом, чтобы
{ он вызывался до и после
fo rea ch (D uck d u c k i n du cks) сортировки.
C o n s o l e . W r i t e L i n e (duck. S i z e . T o S t r i n g () + "-дю йм ов " + d u c k . K i n d . T o S t r i n g O )
C o n s o l e . W r i t e L i n e ( "Утки к о н ч и л и с ь ! " ) ;

Ф
360 глава 8
перечисления и коллекции

Создадим экземпляр объекта-компаратора Здесь не написан код при -


сваивающий элементам
коллекции начальные значе­
Д ля с о р т и р о в к и с п о м о щ ь ю IC o m p a r e r < T > т р е б у е тс я н о в ы й э к з е м п л я р ния, вы найдете его не­
р е а л и з у ю щ е го э т о т и н т е р ф е й с класса. Э т о т о б ъ е к т п о м о га е т м е то д у сколькими страницами р а -
L i s t . S o r t () у п о р я д о ч и т ь м ассив. Н о к а к и в случае с д р у г и м и (н е с т а т и ­ 'нее! Если этого не сделать,
ссылки будут указывать на
ч е с к и м и ) классам и, вам н у ж н о у ка за ть н а ч а л ь н ы е з н а ч е н и я .
знйч сн и я ^

DuckComparerBySize sizeComparer = new DuckComparerBySize();


ducks.Sort(sizeComparer); Методу ЗогЬО передается ссылка на
новый объект РискСотрагегВу51ге
PrintDucks (ducks) ; как на парамет р метода.
Г О т самой маленькой к самой большой..
Добавьте эт от код в
мет од MainQ, чтобы
бидеть, как были о т ­
сортированы утки.

Мно)кественные реализации интерфейса IComparer как разные


способы copmupoBku
М о ж н о создать н а б о р кл а ссо в IC o m p a r e r < D u c k > , к а ж д ы й и з к о т о - ^^г^численця
р ы х будет с о р т и р о в а т ь у т о к по-своему. Вам будет д о с т а т о ч н о то л ь- по их ~
к о в ы б р а т ь н у ж н ы й к о м п а р а т о р . Д о б а в и м к п р о е к т у ещ е о д н у реа- ^
л и за ц и ю п р оц едуры сравнения:

class DuckComparerByKind : IComparer<Duck>


public int Compare(Duck x. Duck y) {
if (x.Kind < y.Kind) М ы сравн и ваем свойст во
-1; . иток Kind, поэт ому со
ртировка происходит по
> у.Kind) и н дексам перечи слени я
п р и м е р со-
KlndOfPuck.

м чи слени и
чем>>^и«метш1 «больш е, J
значение. Мы и с п о л ь з о в а ^ ^ ІТ
операт оры < и > Ъло . Т ^°^'^‘^ ^ские
} перечисления при cop T u p S Z Z ' k ^^ ^'

DuckComparerByKind kindComparer = new DuckComparerByKind();


ducks.Sort(kindComparer);
С о р т и р о в к а no виду
PrintDucks(ducks); ---- у т к и ...

Дополнительный код
для метода MainQ.

дальше ► 361
выберите карту, любую карту

Если метод 5ог1() для объекта


Сложные схемы срабнения 1Сотрагег<Т> не указан, бу­
дет использован заданный по
умолчанию метод сравнения
О тд ел ь н ы й класс для с о р т и р о в к и уток п о з в о л я е т п р и м е ­ значимых типов или ссылок.
нить более сложную логику сравнения, добавив члены ,
оп р ед ел я ю щ и е м етод сор ти р ов к и коллекции.
Класс, выполняющий сравнение ут ок,
enum s o r t c r i t e r i a { Перечисление указы вает объек- более сложен. Метод Сотраге()
s iz e T h e n K in d , т у , каким именно способом бцдит использует те же самые параметры,
K in d T h e n S iz e , сорт ироват ься ут ки. Первый но сначала проверяет значение
^ р а зм ер у, за т ем открытого поля ЗогЬВу, чтобы
— *^0 виду», вт орой —• наоборот.
определить способ сортировки.
c la ss DuckCom parer : IC om parer<D uck> {
p u b lic S o r tc r ite r ia S o r tB y = S o r t C r i t e r i a . S izeT h en K in d ; ^

p u b lic in t C o m p a r e (Duck x . Duck y ) {


if ( S o r t B y == S o r t c r i t e r i a . S i z e T h e n K i n d )
Оператор if проверяет поле
if (x .S iz e > y .S iz e )
л З о ^В у. Если оно имеет значение
r e t u r n 1;
\ 51геТкепК!пс(, ут ки сначала будут
e ls e i f (x .S iz e < y .S iz e ) ^ упорядочиваться по размеру, а
r e tu r n -1; зат ем в пределах каждого разм е­
e lse ра пройдет сортировка по виду.
i f (x .K in d > y .K in d )
r e t u r n 1; Раньиле, если ут ки имели один и т от
e l s e i f (x .K in d < y .K in d ) же размер, возвращалось нулевое
r e tu r n -1; значение, теперь же проверяется
e ls e
еще и видовая принадлежность уток.
r e t u r n 0:
В итоге О возвращается не только когда
e ls e
ут ки им ею т одинаковый размер, но
и относятся к одному и т ом у же виду.
if (x .K in d > y .K in d )
r e t u r n 1;
e l s e i f (x .K in d < y .K in d ) Если значение поля ЗогіВу от лич­
r etu rn -1; но от ВігеТкепКії'^сі, то сначала
e lse сортировка выполняется по виду
i f (x .S iz e > y .S iz e ) утки. Обнаружив двух ут ок одного
r e t u r n 1; вида, компаратор сравнивает их
e ls e i f (x .S iz e < y .S iz e ) размер.
retu rn -1;
e ls e
r e t u r n 0;
Сначала мы, как
экземпляр к о м п а р а т о р а З а т е м
присваиваем значение п о л ю .^о п т ^
DuckCom parer co m p a r er = new D u ck C om p arer( ) ;
7оТле чего можно 6bifam b метод
ducks SortQ. Теперь вы можете
менять метод сортировки ут ок,
com p arer. SortB y = S o r tc r ite r ia .K in d T h e n S iz e ,•
редактируя значение одного поля.
d u ck s.S o r t(co m p a re r) ;
P rin tD u ck s(d u ck s) ;
Добавьте эт от код в М ^^_
Тд MainQ, и вы получите дозмож
н о с тсортировать коллекцию
ь

c o m p a r e r .S o r tB y = S o r t c r i t e r i a . S izeT h en K in d ;
d u ck s. Sort(com p arer) ;
много раз!
P rin tD u ck s(d u ck s);

362 глава 8
перечисления и коллекции

С о з д а й т е п я ть с л у ч а й н ы х кар т и р асп о л о ж и те их в о п р е д е л е н н о м порядке.


Ч ^ а ж н ш е

Код, м о д ел и р ую щ и й набор перем еш анны х карт


С о зд а й те к о н с о л ь н о е п р и л о ж е н и е и до б а вьте в м е то д M a i n () к о д , с о з д а ю щ и й п я т ь
с л у ч а й н ы х о б ъ е к то в C a rd . И с п о л ь з у й т е в с т р о е н н ы й м е то д C o n s o l e . W r i t e L i n e ()
для з а п и с и и м е н и к а ж д о го о б ъ е к та в о к н е вы во да . Д о б а вьте в к о н е ц п р о гр а м м ы м е то д
C o n s o l e . R e a d K e y { ) , ч т о б ы э т о о к н о н е и сч е з а л о п о с л е о к о н ч а н и я р а б о т ы п р о гр а м м ы .

о Класс, которы й реализует сортирую щ ий карты интер(рейс IC o m p arer< C ard >


В о с п о л ь з у й те с ь ср е д с тв а м и И С Р , с о к р а щ а ю щ и м и тр у д о з а тр а ты . В в е д ите :

c la ss C ard C o m p a rer_ b y V a lu e : IC om parer<C ard >

Щ е л к н и т е н а I C o m p a r e r < C a r d > и з а д е р ж и те к у р с о р над с и м в о л о м I. П о я в и т с я ч е р т а ,


щ е л ч о к н а к о т о р о й о т к р о е т в о т та к о е о к н о :
ICDfl>Da"e-<Ca"d>
А льт ер н а т и вн ы м спосо­
бом вызова эт ого м е н ю J -
являет ся клавиат урная Implement interface '1Сотрзгег<Сзгй>'
комбинация СЬг1-точка.
Explicitly implement interface 'IComparer<C3rd>'

В ы б е р и т е в а р и а н т Im p le m e n t in te rfa c e IC o m p a re r< C a rd > , и И С Р вве де т за вас все м е то ­


д ы и св о й с тв а , н е о б х о д и м ы е для р е а л и з а ц и и д а н н о го и н т е р ф е й с а . Б удет создан п у с т о й
м етод C o m p a r e ( ) , с р а в н и в а ю щ и й к а р т ы х и у . П у с т ь м е то д в о з в р а щ а е т 1 (е с л и х б о л ь ­
ш е у ) , - 1 (е сл и м е н ь ш е ) и О (е с л и к а р т ы о д и н а к о в ы ). У б е д и те с ь , ч т о к о р о л ь следует п о ­
сле валета, к о т о р ы й следует за ч е т в е р к о й , к о т о р а я следует за тузом .

Конечны й результат
В о т к а к в ы гл я д и т о к н о вы вода:

///С ftAers/3n<lrew/documents/vtsu . 1 сз Объект IComparer


В ст роенны й м ет о д долж ен о с у и ^ е с т в л я т ь
F i w e FandoKi c a r d s :
С о п $ о 1 е .\л /г И е и п е ( ) Q u e en o f C l u b s соот ировку т аким
добавляет ст роки Two o f D i a m o n d s об р а зо м , чт обы п е р ­
Keyen o f S p a d e s вы м и в сп и ске о к а зы ­
в р езул ьт ат , в т о l e n o f D iam onds
врем я как м ет од Ih re e o f C lubs вались к а р т ы с н а и ­
С о п 5 о 1 е Я е а с 1 К е у () м ен ьш и м зн ач ен и ем .
f h o s e sa m e c a r d s , s
ж дет наж ат ия к л а ­ Two o f D i a m o n d s
виш и , чт обы з а в е р ­ I lire e o f C lu b s
ш ит ь п ро гр а м м у. Seuen o f Spades
i’e n o f D i a m o n d s
Cjiieen o f C l u b s

дальше ► 363
ищите!

ажнение В о т как в ы гл я д и т с о р т и р о в к а с л у ч а й н о го н а б о р а карт.

)‘^ешение
c la ss C ard C o m p a rer_ b y V a lu e : IC om parer<C ard> { ^ ^ ‘^ Р ^ е н н ы и
p u b lic in t C om p are(C ard x , Card y) {
(x .V a lu e < y .V a lu e ) {
Если X имеетл retu rn -1;
оольшее значение,
меилод возвра - (x .V a lu e > y .V a lu e ) {
щает 1 . Если retu rn 1;
значение меньше,
оозЬращается
-!■ Но оба опе­ (x .S u it < y .S u it) { Э ти операторы выполняются ^
ратора, возвра­ retu rn -1; только при совпадении значении
щающие значе­ х и ч , ведь первые два оператора,
ние, завершают возвращающие значение, в этом
(x .S u it > y .S u it) {
радоту метода. случае не выполняются.
retu rn 1;
}
retu rn 0;

Это обобщенная
sta tic v o id M a i n ( s t r i n g [] args) коллекция объектов
{ Card. Ее легко сор­
R andom r a n d o m = n e w R a n d o m ( ) ; тироват ь с пом о­
щью интерфейса
C o n s o l e . W r i t e L i n e ( "Пять с л у ч а й н ы х к а р т : " ) ;
IComparer.
L ist< C a rd > cards = n e w L i s t < C a r d > () ;
fo r (in t i = 0; i < 5; i+ + )
{
c a r d s.A d d (n e w C a r d ((S u its)r a n d o m .N e x t(4 ),
(V a lu e s )r a n d o m .N e x t(1 , 14)));
C o n so le .W r ite L in e (c a r d s[i].N a m e );
}

C o n so le . W r ite L in e ();
C o n s o l e . W r i t e L i n e ( "Те ж е к а р т ы , отсор ти рован н ы е:");
c a r d s . S o rt(n ew C a r d C o m p a r er _ b y V a lu e ( ) ) ;
foreach (C ard c a r d in cards)
{ Метод Console.ReadKeyO нужен, чтобы про­
C o n so le .W r ite L in e (c a r d .N a m e ) грамма не закрывалась после завершения. Для
} реальных приложений он не подходит. При за­
C o n so le.R e a d K ey 0 ; < .......... пуске программы по Ctrl-F5 она начинает рабо­
тать без отладки. После завершения появляется
строка Press any key to continue... и приложение
ждет нажатия клавиши. Но отладки не происхо­
дит, и точки останова с контрольными значени­
ями не работают.
364 глава 8
перечисления и коллекции

Перекрытие метода ToStringO


Все о б ъ е кты .N E T и м е ю т м е т о д T o S t r i n g ( ) , п р е о б р а з у ю щ и й о б ъ е к т в с т р о к у . П о у м о л ч а н и ю о н воз­
вращ ает и м я класса ( M y P r o j e c t . D u c k ) . Э т о т м е то д унаследован о т класса O b j e c t (к о т о р ы й являет­
ся базовы м для л ю б о го о б ъ е кта ). О п е р а т о р +, с о е д и н я ю щ и й с т р о к и а в т о м а т и ч е с к и , в ы з ы в а е т м е т о д
T o S t r i n g ( ) . М е т о д ы C o n s o l e . W r i t e L i n e () и л и S t r i n g . F o r m a t () та к ж е а в то м а ти ч е с к и в ы з ы в а ю т
е го п р и передаче и м объ е кто в.

В п р о гр а м м е с о р т и р о в к и у т о к п о м е с т и т е т о ч к у о с т а н о в а в м е то д M a in () п о с л е и н и ц и а л и з а ц и и ко л л е к- х
ц и и и з а п у с ти те отл а д ку п р о гр а м м ы . З атем н а в е д и т е у к а з а т е л ь м ы ш и н а л ю б у ю п е р е м е н н у ю d u c k s ,
ч т о б ы з н а ть ее з н а ч е н и е . У в и д е т ь п р и отл а дке с о д е р ж и м о е к о л л е к ц и и м о ж н о , щ е л к н у в н а к н о п к е со
зн а ко м слева о т и м е н и п е р е м е н н о й :
Вместо передачи зна­
□ Ф d u cks
Count = 6 чения методам Console.
И Ф ducfes'C ount = 6 )
ш ^ т {MyProject.Dudî,} WriteLineO, String.
ш V [11 flHvProject.Dudt} FormatQ и т. п., мож­
[2] {MyProject.Duck} но передать им обьект.
Метод ToStringO вызывается при от о­ Его мет од ToStringO
ш # И ^ y P ro jectD u ck }
бражении обьекта в окне Watch. Но будет вызван авт ом а­
ш ФЫ flHyProject.Duck>
метод ToStringOJ унаследованный клас­ тически. (Это работает
ш é т {MyProject.Dudc}
сом Риск от класса Object, возвращает и для значимых типов,
только имя класса. Мы можем сделать ш # Raw View таких как int и enums!)
эт от метод более информативным.
И т а к , в ы в и д и т е , ч т о к о л л е к ц и я с о д е р ж и т ш е с т ь о б ъ е к т о в D u c k (M y P ro je c t — э т о п р о с т р а н с т в о и м е н ,
в к о т о р о м м ы н а х о д и м с я ). Щ е л ч о к н а к н о п к е (сл ева о т н о м е р а у г к и ) п о к а з ы в а е т з н а ч е н и я п а р а м е тр о в
K i n d и S i z e . Н о н е льзя л и сделать т а к , ч т о б ы вся и н ф о р м а ц и я п о к а з ы в а л а с ь о д н о в р е м е н н о ?

T o S t r i n g О - э т о в и р т у а л ь н ы й м е то д класса O b j e c t , к о т о р ы й я в л я е тс я б а зо в ы м п о о т н о ш е н и ю к л ю ­
бом у объекту. Т а к ч т о вам о с та е тс я т о л ь к о п е р е к р ы т ь м е т о д T o S t r i n g O , и в ы у в и д и т е р е зул ь та ты
в о к н е w a tc h ! О т к р о й т е класс D u c k и до б а вьте н о в ы й м е то д с к л ю ч е в ы м с л о в о м o v e r r i d e . П о с л е на­
ж а т и я П р о б е л а п о я в и т с я с п и с о к д о с т у п н ы х для п е р е к р ы т и я м е то д о в :
override
= ♦ E q u a l s ( o b j e c t o b j)

G e tH a s h C o d e O

ToStringO I s trin g o b je c t J o S trln g O


I Returns a System .String that represents the current S^çtem .Objsct.

В ы б е р и т е в а р и а н т T o S t r i n g () и за м е н и те с о д е р ж и м о е м е то д а в о т э ти м :

p u b lic o v e r r id e str in g T o S tr in g O
{
retu rn "A " + S i z e + " in ch " + K in d .T o S tr in g 0 ;

З а п у с ти т е п р о гр а м м у и с н о в а п о с м о т р и т е н а к о л л е к ц и ю . Т е п е р ь в ы в и д и т е все с в е д е н и я о D u c k !

В # ducks C ount = 6
Чтобы показать обьект,
Ш t [0] {A 17m di Maliard} отладчик вызывает метод
Ш {A 18 iid i №jscovy> ToStringO-
Ш 9 m {A H in d i Decoy)
ffl Ф т {A 11 inch Misrnvy}
Ш Ф [4] {A 14indiMa8ard}
ffl #151 {A 13 indi Decoy} дальше > 365
Ш Ч) Raw ¥iew
цикл foreach

Обновим цикл foreach


В ы в и д е л и у ж е два п р и м е р а ц и к л и ч е с к о г о в ы з о в а м етод а C o n s o l e . W r i t e L i n e () для в ы в о д а и н ф о р м а ­
ц и и о б э л е м е н та х к о л л е к ц и и н а к о н с о л ь . В о т т а к в ы в о д и л и с ь с в е д е н и я о к о л л е к ц и и L i s t < C a r d > :

f o r e a c h (C a rd c a r d i n c a r d s )
^ У мм инание о методе
C o n s o le . W r i t e L i n e ( c a r d . N am e) ; -ToStringO в данном с л у -
} чае Можно опуст ит ь,
, оператор + вызовет его
М е т о д P r i n t D u c k s () в ы п о л н я л а н а л о г и ч н у ю ф у н к ц и ю для о б ъ е к то в D u c k : ‘^отоматически-
fo r e a c h (D uck d u c k i n ducks) /

^ C o n s o le . W r it e L in e ( d u c k . S iz e . T o S t r in g O + " - in c h " + d u c k .K in d .T o S tr in g O ) ;

Т е п е р ь, ко гд а о б ъ е к т D u c k п о л у ч и л с в о й м е то д T o S t r i n g ( ) , м е то д P r i n t D u c k s () д о л ж е н и с п о л ь з о ­
ва ть э т о п р е и м у щ е с тв о :

p u b l i c s t a t i c v o id P r in t D u c k s ( L is t < D u c k > ducks) {


f o r e a c h (D u c k d u c k i n d u c k s ) {
C o n s o le . W r i t e L i n e ( d u c k ) ; _ передаче методу Console.WriteLineO
} ссылки на од-ьект_, он автоматически вызо-
C o n s o le . W r i t e L i n e { " У т к и к о н ч и л и с ь ! " ) ; Memo ToStringO этого обьекта-
}
В в е д и те э т о т ко д в п р о гр а м м у D u c k s и з а п у с ти те ее. С п и с о к в ы в о д а н е и з м е н и тс я . Н о если в ы з а х о ти те
д о б а в и ть с в о й с т в о G e n d e r для уче та п о л а у гк и , д о с т а т а т о ч н о о б н о в и т ь м е то д T o S t r i n g ( ) , и все м е тод ы ,
к о т о р ы е е го и с п о л ь з у ю т (в к л ю ч а я м е то д P r i n t D u c k s ( ) ) , и з м е н я тс я с о о тв е тс т в у ю щ и м образом .

д ^ * т л • 1\ I << I /> • Вы до сих пор можете вызы-


Дооаоим Метоу ToStringO ^ объбкшу Card вать метод ToStringO таким
„ способом, но теперь вы знаете,
С в о й с т в о Name о б ъ е к та C a r d в о з в р а щ а е т и м я к а р т ы : ч т о в эт ом нет необходимо -
p u b lic s tr in g Name как оператор + вы-
I зывает его автоматически.
^ get { re tu rn V a lu e . T o S t r in g O + " of " + S u it .T o S tr in g O ; }

В о т ч т о д о л ж е н делать м етод T o S t r i n g ( ) . П о э т о м у добавьте е го к классу C a rd :


p u b lic o v e r r id e s tr in g T o S tr in g O

^ функции метода ToStringO не


re tu rn N am e; ограничиваются идентификацией
} ваших объектов в ИСР. В следую-
ш,их главах мы рассмот рим пре-
Т е п е р ь в а ш и п р о гр а м м ы , и с п о л ь з у ю щ и е о б ъ е к т ы C a rd , имуш,ества, которые дает п р е-
м о ж н о л е гк о о тл а д и ть. образование объектов в строки.

366 глава 8
перечисления и коллекции

Д и к Л ГоГеаЛ П о д
интерфейс IEnumerable<T> уБеЛиЧитпеЛьНыМ свдеКЛоМ

Н а й д и т е п е р е м е н н у ю L is t < D u c k > и в о с п о л ь з у й т е с ь ф у н к ц и е й In te lliS e n s e Инициализаторы


A n flp a c c M O T p e H H H M e T O fla G e tE n u m e ra to r { ) . Н а п е ч а т а й т е « .G e tE n u m e ra to r» коллекций работ аю т
и п о см о тр и те на откры вш ееся меню : с ЛЮБЫМ объектом
>ЕпитетЬ1е<Т>!
ducks.GetEnumerator
6etEftume»tor List<D uck> .Ermmeratof List< Duck> .GetEnumeratorO
Returns an enumerator that iterates through the System.CoHectrar»s.Gcneric.L»st<T>.

С о зд а й те н о в ы й м а сси в о б ъ е к т о в D u c k :

D u ck [] duckA rray = new D u ck [6 ];

Н а п е ч а т а й т е d u c k A r r a y . G e t E n u m e r a t o r , ведь у м ассива и м е е тс я д а н н ы й м етод . Э т о с в я за н о с тем ,


ч то о б ъ е кты L i s t , как и м ассивы , реализую т и н тер ф е йс 1 Е п ш п ег а Ы е< Т > , содерж ащ ий е д и н стве нн ы й
м е то д G e t E n u m e r a t o r { ) , в о з в р а щ а ю щ и й э л е м е н т п е р е ч и с л е н и я .

Э т о о б ъ е к т E n u m e r a t o r , о б е с п е ч и в а ю щ и й м е х а н и з м п е р е б о р а к о л л е к ц и и . Р а с с м о тр и м ц и к л f o r e a c h ,
просм атриваю щ ий коллекцию L i s t < D u c k > с перем енной d u c k :

fo rea ch (D u ck duck in ducks) { Реализуя интерфейс


c o „ .o ie .w r it.L i» M d u = k ), IEnumerable<T>,
К о д , с к р ы в а ю щ и й с я за э т и м ц и к л о м :
коллекция предо­
IE n u m erator< D u ck >
w h ile
en u m era to r
(e n u m e r a to r .M o v e N e x t0 )
= d u c k s .G etE n u m era to r{);
{
ставляет цикл, пе-
D uck duck = en u m era to r. C u rr en t;
C o n s o le .W r it e L in e ( d u c k ) ;
ребираюпщй ее эле­
}
ID is p o s a b le d isp o sa b le = en u m era to r as ID is p o s a b le ;
менты по очереди.
if (d isp o sa b le != n u ll) d is p o s a b le .D is p o s e ();

(Н е б е с п о к о й т е с ь , ч т о в ы н е п о н и м а е т е п о с л е д н и х д вух с т р о к , с интерф ейсом ^^ ^


I D i s p o s a b l e в ы п о з н а к о м и т е с ь в главе 9.) Здесь написан
не весь код, но
Э т и два кода в ы в о д я т о д и н и т о т ж е с п и с о к у т о к . М о ж е т е п р о в е р и т ь с а м о с то я те л ь н о , в данном случае
главное, чтобы
П р и ц и к л и ч е с к о м п р о с м о т р е к о л л е к ц и и и л и м а сси ва м е то д M o v e N e x t () возвращ а- поняли
е т з н а ч е н и е t r u e п р и н а л и ч и и с л е д у ю щ е го э л е м е н та и з н а ч е н и е f a l s e , е с л и д о с т и г н у т основную идеЮ-
п о с л е д н и й элем ент. С в о й с т в о C u r r e n t всегда в о з в р а щ а е т с с ы л к у н а т е к у щ и й эле­
м ент. А ц и к л f o r e a c h о б ъ е д и н я е т у к а з а н н ы е ф у н к ц и и !

Поэкспетментиюийте, заставив метод ToStringO объекта Риск увеличивать свойство Size на единтУ-
Запустите отладку и наведите указатель мыши на имя Риск. Проделайте это несколько раз. Помни-
mCj что кажЭь>!й рдз будет вi:?(3 t>iвamtfcя метод ToSt\rmg().
Как вы думаете, что будет происходить при выполнении цикла foreach, если метод ToStringO
меняет одно из полей объекта?

дальше > 367


никого кроме уток!

Восходящее приведение с помои^ью lEnumerable


Bird
П о м н и т е о в о з м о ж н о с т и в о с х о д я щ е го п р и в е д е н и я о б ъ е к т о в к б а з о в о м у Name
классу? П р и р а б о т е с о б ъ е к т а м и L i s t э т у о п е р а ц и ю м о ж н о п р о д е л а т ь для
в с е й к о л л е к ц и и . Э т о н а з ы в а е т с я к о в а р и а ц и е й ( c o v a r i a n c e ) , и вам п о т р е б у ­
FlyO
е тс я т о л ь к о с с ы л к а н а и н т е р ф е й с I E n u m e r a b l e < T > .

С оздайте ко н с о л ь н о е п р и л о ж е н и е и в нем б а зо в ы й класс B i r d (е го п р о д о л ж е ­ _


н и е м будет класс D u c k ) и п р о и з в о д н ы й класс P e n g u i n . В оспользуем ся м етодом
T o S t r i n g ( ) , ч т о б ы п о н я т ь , где к а к о й класс.

c la ss B ird { Duck Penguin


Size
p u b lic s t r i n g Name { get; set; }
Kind
p u b lic v o id F ly O {
C o n s o le .W r ite L in e ( "П олетели!" );
}
p u b lic o v e r r id e s t r in g T o S trin g O {
r e t u r n "Имя п т и ц ы " + N am e;
}
Это класс Bird и наследующий от него
}
класс Penguin. Добавьте ик в новый
проект типа Console Application, за ­
c la ss P en g u in : B ird
т ем скопируйте туда же сущ ест ву­
{ ющий класс Риск, поменяв соот вет ­
p u b lic v o id F ly O { ствующ им образом его объявление.
C o n s o l e . W r i t e L i n e ("П и н гв и н ы н е л е т а ю т ! '
}
p u b lic o v e r r id e s t r in g T o S trin g O {
Л
r e t u r n "Имя п и н г в и н а " + b a s e . N a m e ; c l a s s D uck ; B ir d , IC o m p a r a b le< D u ck > {
} / / О стальной к од б е з изм ен ен ий
}

В о т п е р в ы е с т р о к и м е то д а M a i n { ) , и н и ц и а л и з и р у ю щ и е к о л л е к ц и ю и о с у щ е с т в л я ю щ и е е е в о с х о д я ­
щ ее п р и веден и е. ^ ------- ^
, . , ^ , , , , ^ с к о п и р у й т е и н и ц и а л и за т о р для к о ллекц и и ц т о к
L ist< D u c k > d u c k s = new L is t< D u c k > () { / * и н и ц и а л и зи р у й т е о б ъ е к т , как обычно * / }
IE n u m e r a b le < B ir d > u p c a s tD u c k s = d u c k s ;

П о с м о т р и т е н а п о с л е д н ю ю с тр о ч к у . С с ы л к у н а к о л л е к ц и ю L i s t < D u c k > в ы п р и с в а и в а е т е и н т е р ф е й с ­
н о й п е р е м е н н о й l E n u m e r a b l e < B i r d > . З а п у с ти т е о тл а дку и у б е д и те сь, ч т о о б е с с ы л к и у к а з ы в а ю т на
о д и н и т о т ж е об ъ е кт.

Объединим птиц 6 единую коллекцию


К о в а р и а ц и я п о зв о л я е т д о б а в л и ть ч а с т н у ю к о л л е к ц и ю к более о б щ е й. С каж ем , в к о л л е к ц и ю о б ъ е к то в B i r d
м о ж н о д о б а в и ть к о л л е к ц и ю D u c k . Вам п р и г о д и т с я м етод L i s t . A d d R a n g e ' '
L ist< B ir d > b ir d s = new L i s t < B i r d > 0 ;
rt bird iiatned Feathers
b i r d s . A d d ( n e w B i r d O { Name = " П е р н ат ы е " } ) ; fi 1 7 inch Mallard
b ir d s.A d d R a n g e(u p c a stD u ck s); fi 18 inch K u s G o u y
b i r d s . A d d ( n e w P e n g u i n () { Name = "Джордж" } ) ; fi ,1 4 inch Decoy
H 11 inch Muscouy
fo r e a c h (B ird b ir d i n b ir d s ) (благодаря восходящему пю иве- fi 1 4 inch Mallai'd
f! 1 3 i n c h Decoy
C o n sool el e. W
. Wr ri ti te eLLi ni ne e((bb ii rr d ) ; дению y m o K К IEnumerahle<Bird fi penguin naned George
} UK можно добавить в коллекцию
объект ов Bird.
перечисления и коллекции

Создание перегру)кенньіх методоВ Вместо редактирования


■ / — >^мен лчс>;
vt/vic-rf 2ж но
В ы уж е и с п о л ь з о в а л и н е т о л ь к о п е р е г р у ж е н н ы е м е т о д ы , н о и п е р е гр у - спользоЬать оператор.
ж е н н ы й к о н с т р у к т о р , к о т о р ы й б ы л ч а с т ь ю в с т р о е н н о г о класса .N E T.
С л о в о м , в ы уж е зна е те , н а с к о л ь к о п о л е з н о й я в л я е тс я э та ф у н к ц и я . Х о ­
т е л и б ы в ы в с т р а и в а т ь п е р е гр у ж е н н ы е м е то д ы в в а ш и с о б с т в е н н ы е
классы ? Э т о л е гк о м о ж н о сделать! Д о с т а т о ч н о н а п и с а т ь два и более -У п р аж н ен и е!
о д н о и м е н н ы х м е то д о в с р а з л и ч н ы м и п а р а м е тр а м и .

Создайте новый проект и добавьте в н е го класс C ard .


Э т о л е г к о сделать, щ е л к н у в п р а в о й к н о п к о й м ы ш и н а и м е н и п р о е к т а в о к н е S o lu tio n E x p lo re r
и в ы б р а в в а р и а н т E x is tin g Ite m в м е н ю A d d . И С Р д о б а в и т в п р о е к т к о п и ю класса. Н о п р и
э т о м о н о с т а н е т с я в п р о с т р а н с т в е и м е н с т а р о г о п р о е к т а , п о э т о м у о т к р о й т е ф айл C a r d . e s
и о т р е д а к т и р у й т е с т р о ч к у n a m e s p a c e . А н а л о г и ч н у ю о п е р а ц и ю п р о д е л а й те для V a lu e s
и S u T ts ^ Без этой операции для доступа к классу Card потребует-
. ся указывать пространство имен в явном виде (например,
oldnamespace.Card).

О Т еперь добавим в класс C ard новые перегруж енны е м етоды .


С о зд а й те два с т а т и ч е с к и х м е то д а D o e s C a r d M a t c h ( ) . П е р в ы й будет п р о в е р я т ь м а сть к а р т ы ,
а в т о р о й - с т а р ш и н с т в о . О б а м е то д а в о з в р а щ а ю т з н а ч е н и е tr u e п р и с о в п а д е н и и ка р т.

p u b lic sta tic b o o l D oesC ard M atch(C ard card T oC heck, S u its su it) {
if (c a r d T o C h e c k .S u it == s u i t ) {
retu rn tru e; Перегруженные методы не обязаны быть
} e lse { статическими, но мы решили, что
retu rn fa lse ;
Ьам будет полезно попрактиковаться
о написании статических методов.
}
]
p u b lic sta tic b o o l D oesC ard M atch(C ard card T oC heck, V a lu e s v a lu e ) {
if (c a r d T o C h e c k .V a lu e == v a l u e ) {
retu rn tru e; Кяк работает процедура перегрузки,
e lse {
(и в п р о г р а м і d f рсксчета ст оим о­
видели
сти вечеринок^из главы &, т ам
retu rn fa lse ; перегруженный мет од CalculateCostO о класс
PinnerParty.
}

О Добавьте к цю рме кно пку, и спол ь зую щ ую оба м етод а.


В от код это й кн о п ки:

C ard card T oC h eck = new C a r d ( S u i t s . C l u b s , V a l u e s . T h r e e ) ;


b o o l d o e sI tM a tc h = C a r d . D oesC ardM atch(cardT oC heck, S u i t s . H e a r t s ) ;
M e s s a g e B o x . S h o w ( d o e s I t M a t c h . T o S t r i n g ( ) A | j ^ ^ g T o S t n n a O нужен, так как параметр метода
^ ------------ MeisaqeBox.ShowQ должен принадлежать к типу s tn у.
К а к т о л ь к о будет н а п е ч а т а н о D o e s C a r d M a t c h ( ) , И С Р п о к а ж е т , ч т о в ы д е й с т в и т е л ь н о н а п и ­
сал и п е р е г р у ж е н н ы й м етод;

C a rd .D o e sC a rd M a tch (
, lo f 2 ▼ b o o l C a rd .D o e s C a r d H a tc h (C a r d c a rd T o C tie c k ^ S u i t s s u it) |

П о э к с п е р и м е н т и р у й т е с э т и м и м е то д а м и , ч т о б ы п р и в ы к н у т ь к р а б о те .
дальше * 369
колоду на стол

П о п р а к ти к уе м с я в п р и м е н е н и и о б ъ е к то в L i s t , с о зд а в к л а сс д л я хр а н е н и я
іа ж н ен и е
кол од ы карты и ф орм у, к о то р ая б уд е т его и с п о л ь зо в ать .

^ §о р м а. позволяю щ ая перемещ ать карты м еж д у колодам и


С оздадим класс D e c k (К о л о д а ) для х р а н е н и я п р о и з в о л ь н о г о к о л и ч е с т в а к а р т. В р е а л ь н о й к о л о ­
де 52 к а р т ы , н о в классе D e c k м о ж е т б ы т ь с к о л ь к о у го д н о к а р т (и л и н е б ы т ь н и о д н о й ).

Затем вам п о т р е б у е т с я ф о р м а , п о к а з ы в а ю щ а я с о д е р ж и м о е д в ух о б ъ е к то в D e c k . П р и п е р в о м
запуске п р о гр а м м ы в к о л о д е #1 м о ж е т б ы т ь д о 10 с л у ч а й н ы х ка р т, а в к о л о д е #2 — п о л н ы й н а ­
б о р (52 к а р т ы ). О б е к о л о д ы о т с о р т и р о в а н ы п о м а с ти и п о с т а р ш и н с т в у В э т о п о л о ж е н и е к о л о ­
ду м о ж н о в е р н у т ь в л ю б о й м о м е н т щ е л ч к о м н а к н о п к е Reset. Т а к ж е ф о р м а с н а б ж е н а к н о п к а м и
« и » , к о т о р ы е п е р е м е щ а ю т к а р т ы и з о д н о й к о л о д ы в д р у гу ю .
Кнопки моуеТоРескЯ (верхняя) и тоуеТоРеск! (нижняя)
першеьцаким. карты из одной колоды в другую.
Д л я показа обеих колод ис­
П ом ните, что с пом ощ ь ю пользуются два элемента
св ойств а N am e эл ем енту ustBox. При щелчке на кнопке
уп равл ени я м ож но при св ои ть ^ o v e r o P e c k l выбранная карта
имя, ул учш и в тем сам ы м ч и та ­ ^перемещается из колоды J z
б ел ь ность кода. П ри д в ойно м о колоду
щ елчке на кнопке об раб о тчи ку
соб ы тий б уд ет д а н о со о тв ет­
ствую щ ее имя.
Имена эт их кнопок shuffle! и
Кнопки r e se ti и resetz shuffieZ. Они вызывают п о ­
сначала вызывают ходящий метод P e c k .S K M rn 6 ()j
метод ResetDeckQ, чтобы перетасовать колоду,
а потом метод а зат ем перерисовывают ее.
R-edrawPeckQ.

Д о б а в и ть к ф орм е н у ж н о н е то л ь к о о б р а б о т ч и к и с о б ы т и й для ш е с т и к н о п о к , н о и два метода.


Н а ч н и т е с м етода R e s e t D e c k ( ) , во зв р а щ а ю щ е го ко ло д у в и с х о д н о е с о с то я н и е . В качестве пара­
м етра о н исп о л ьзуе т зн а ч е н и е т и п а in t: если ему п е редать 1, о н п р е в р а щ а е т п е р в ы й о б ъ е к т D e c k
в п устую колоду, а затем с л уч а й н ы м образом назн а ча е т е й 10 ка р т; передав з н а че н и е 2, в ы п р е вр а ­
т и т е в т о р о й о б ъ е к т D e c k в у п о р я д о ч е н н у ю ко ло д у из 52 карт:
p r i v a t e v o i d R ed raw D eck( i n t DeckNum ber) {
Метод RedrawPeck()
i f (D eckNum ber = = 1 ) { — Т к и 2 Т из нее случай-
скивает
Обратите l i s t B o x l . I te m s . C le a r {);
« обновляет
внимание, foreach (str in g cardNam e i n d e c k l.G e tC a r d N a m e s()) содержимое текстовых
как карты l ^ t B o x l . I t e m s .A d d ( c a r d N a m e ) ; соответствии
добавляются la b e ll.T e x t = "D eck #1 (" + d e c k l . C o u n t + " cards) обменом.
в коллекцию } e lse {
при помощи
цикла foreach. lis t B o x 2 . Ite m s. C le a r ();
foreach (str in g cardN am e i n d e c k 2 . G etC ard N am es())
l i s t B o x 2 . Ite m s.A d d (ca r d N a m e);
la b e l2 .T e x t = "D eck #2 (" + d e c k 2 . C o u n t + " cards)
}
перечисления и коллекции

«Основой» мы назвали объявление класса


С оздание кл а сса b e c k без его реализации.
В о т о с н о в а класса D e c k . М ы н а п и с а л и за вас н еск о л ь к о м е т о д о в . В ам о с т а л о с ь д о б а в и т ь м е­
т о д ы S h u f f l e {) и G e t C a r d N a m e s ( ) и з а с т а в и т ь р а б о т а т ь м е т о д S o r t ( ) . М ы д о б а в и л и д в а
п ер егр у ж ен н ы х к он структора: созд аю щ и й колоду и з 52 карт и загр уж аю щ ий в колоду со ­
д ер ж и м о е м ассива объек тов C ard.
^ Класс Реек хранит объекмы в коллекции List, при
ш
f эт ом^ t t л ^ шt ш,O^fy* ^1А
они являются закрытыми.
Л АЛ L^t л й ЛI й
c l a s s D eck {
p r i v a t e L i s t < C a r d >. b’ceaarr d s ;; _ „ ______ ______ д
p riv a te R andom r a n d o m = n e w R a n d o m () , создана без п е р е у ч и na~

p u b l i c D eck О { / рамет ров конструктору.


П р ш а д л е ж н о с!^ ^ c a r d s = new L i s t < C a r d > { ) ; ^
парамет ра К s u i t = 0 ; s u i t <= 3 ; s u i t + + )
т ипу l E n u m e - v a l u e = 1 ; v a l u e <= 1 3 ; v a l u e + + )
m h le < C a r d > c a r d s . A d d (n e w C a r d ( ( S u i t s ) s u i t , (V a lu e s)v a lu e ))
позволяет I Перегруженный конструктор в к а ­
передавать
rv c -ju ч«у^| ^
честве парамет ра берет массив
к о н с т р ук т о р у^ ^ ^ ^ ^ ^ P e c k ( IE n u m era b le< C a rd > in itia lC a r d s) { карт, загружая его в исходную
любую коллек- c a r d s = new L i s t < C a r d > ( i n i t i a l C a r d s ) ; колоду.
цию, a не moAix ___У Подсказка Свойство
ко List<T> или , , Selectedlndex элем е н -
массив. p u b l i c i n t C ount { g e t { r e t u r n c a r d s . C o u n t ; i J управлений LiStBoK
совпадает с индексом
p u b l i c v o i d A d d (C ard card T oA dd ) { Метод Peal (Раздача) ^^арты в к о л л е к ц и и . Его
c a r d s . A d d (cardT oA dd) Шдает одну карту J^oжнo передать м е т о -
} ------- колоды, он удаляет peai(). Если карта
4: объект Card и возвра- выбрана, значение
p u b l i c C ard D e a l( i n t in d e x ) { Щает ссылку на него.
.................... меньиле нуля и метод
Card C ardT oD eal = c a r d s [ i n d e x ] ;Переда6 О , вы сдади- moveToPeck не работа­
c a r d s . R em o v eA t(in d e x ) те верхнюю карту ет.
r e t u r n C a rd T o D ea l; колоды. Чтобы сдать
Хотя метод \ любую другую карт у,
detCardNamesO укажите ее индекс.
возвращ ает p u b l i c v o i d S h u f f l e () {
массив, мы I I м е т о д т а с у е т карты, меняя их п о р я д о к случайным о б р а з о м
даем дост уп j
к lE n u m e - " '^ _________ ^
r a b l e < S t r i n y > . I E n u m e r a b l e < s t r i n g > G e tC a r d N a m e s() {
/ / м е т о д в о зв р а щ а е т м а с си в тип а s t r i n g с им енами в с е х кар т
} Вам нужно написать м ет о -
’ V ды SkuffleO и GetCardNamesQ,
p u b l i c v o i d S o r tO { а также добавить класс, р е ­
c a r d s . S ort(n ew C ard C o m p arer_b yS u it()) ализующий IComparer, чтобы
} заставить работать метод
Sort(). Кроме того, пот ребу-^
ется добавить уже написанный
класс Card. Если вы будете
Еще одна подскока: Протестировать мет од SkuffleO мож- делать это командой A dd
помощью формы. Щ елкайте на кнопке "Reset Peck Existing Item, не забудьте о т ­
пока не получите колоду из т рех карт. В эт ом сличае легко редактировать пространство
убедиться, что мет од перетасовки работает. имен.

дальше * 371
решение упражнения

іажнение В о т код д л я класса, х р а н я щ е го и н ф о р м а ц и ю о кол од е карт, а такж е д л я и с­


п о л ь зу ю щ е й э то т к л а сс ф о р м ы
'ешение Э т о т конструктор создает колодц
из SZ карт. Он использует вложен­
c la s s D eck { ный цикл for. Внешний цикл оси-
p riv a te L ist< C a rd > c a r d s; ^ерсбор четырех мастей.
p r iv a te R andom r a n d o m = n e w R a n d o m ( ) ; / ^ ^ ‘^ ^ет ст венно, внутренний цикл,
j перебирающий 13 значений карт , за
p u b lic D eckO {
ест ь no
cards = n e w L i s t < C a j : d > () ; разу на каждую масть.
fo r ( i n t s u i t = 0; su it <= 3 ; su it+ + )
fo r (in t v a lu e = 1; v a lu e<= 1 3 ; v a l u e + + )
c a r d s.A d d (n e w C ard( ( S u i t s ) s u i t , (V a lu e s)v a lu e ));
}
Это второй конструктор.
p u b l i c D eck (lE n u m erab le< C ard > i n i t i a l C a r d s ) -------- Данный класс содержит
c a r d s = new L i s t < C a r d > ( i n i t i a l C a r d s ) ; два перегруженных кон­
} структора с различными
p u b lic in t Count { get { retu rn c a r d s.C o u n t;
J J параметрами.
p u b l i c v o i d A d d(C ard cardToAdd) {
c a r d s.A d d (c a r d T o A d d );
}
p u b l i c Card D e a l ( i n t in d e x ) {
Card C ardT oD eal = c a r d s [ i n d e x ] ^^oд A d d ^ л іе -
c a r d s . R e m o v eA t(in d e x );
r e t u r n C ard T oD eal;
}
p u b lic v o id S h u ffle 0 {
L is t < C a r d > N ewC ards = new L i s t < C a r d > ( ) ; -----
" с именем NewCards
w h ile (c a r d s.C o u n t > 0 ) {
i n t CardToM ove = r a n d o m . N e x t ( c a r d s . C o u n t) ИЗ п о л я Cards и помеш,ает в к м -
N e w C a r d s.A d d (c a r d s[C a r d T o M o v e ]) ;
c a r d s . R em oveA t(C ardT oM ove); СагГне^‘^"^
^aras не опустеет. После этого
}
н о в 'Л ^ Т '''^ ^ ня
cards = N ew C ards;
} на старый экземпляр в итоге не
остается, он удаляется.
p u b lic I E n u m e r a b l e < s t r i n g > G e t C a r d N a m e s () {
s t r i n g [] C a r d N a m e s = n e w s t r i n g [ c a r d s . C o u n t ] ;
( i n t i = 0; i < c a r d s .C o u n t; i+ + )
C a r d N a m e s [ i ] = c a r d s [ i ] .N a m e ;
r e t u r n C ardN am es; М е т о Э й О а С а гс1 Ы а т е $ {) нуж но с о з д а т ь
} Ъ ^ а т о ч н о больш ой м а сс и в , ч т обы в
p u b lic v o id S o rtO { н его п о м е с т и л и с ь и м е н а всех ^
Т т о гТ и с п о л ь зу е т с я цикл
c a r d s . S o r t(n e w C a rd C om p a rer_ b y S u it( ) ) ;
ч а р е ш а е м а и с п о М о ш р ю ц и к л а \о ге а с )л .

372 глава 8
перечисления и коллекции

c la ss C a rd C om p arer_b yS u it : IC om parer<C ard>


{
p u b lic in t C om pare(C ard x . Card y)
{ С орт ировка no м аст и н а т м и -
if (x .S u it > y .S u it) нает сорт и ровку по ст арш и н ­
r e t u r n 1; ст ву. Е ди нст венн ое от личие
if (x .S u it < y .S u it) в т ом , чт о м аст и сравн и ­
r etu rn -1; ва ю т ся п ер вы м и , а сравнен ие
if (x .V a lu e > y .V a lu e ) значении кдрт происходит
т о л ьк о при совпадении м а ст ей .
r e t u r n 1;
if (x .V a lu e < y .V a lu e )
retu rn -1;
retu rn 0; Вместо конструкции if/else if
мы использовали набор опера­
торов if. Это работает, так
как каждьш следуюший one
ратор if выполняется только
в случае невыполнения предыду­
D eck d e c k l; щего.
D eck d e c k 2 ;
R an do m r a n d o m = n e w R a n d o m ()

p u b lic F o rm lО {
In itia liz e C o m p o n e n t О ; К онст рукт ор формы сна
R esetD eck (1); чала возвращает колоды
R esetD eck (2); в исходное состояние п—о т о м
R edraw D eck(1 ); перерисовывает их.
R edraw D eck(2 );
}

p r iv a te v o i d R e s e t D e c k ( i n t deckNum ber) {
if (deckN u m ber = = 1 ) {
i n t nu m berO fC ards = ra n d o m .N e x t (1 , 1 1 ) ;
d e c k l = new D e ck (n e w C a r d [] { } ) ;
f o r ( i n t i = 0; i < nu m berO fC ards; i+ + )
d e c k l.A d d (n e w C a r d ((S u its )r a n d o m .N e x t(4 ) ,
(V a lu e s)r a n d o m .N e x t(1, 14)));
d e c k l. S o r t ();
} e ls e
deck2 = n e w D e c k () пользуется мет од random N extn и п . / ’

V
создается пистая после чего
}
For туда добавляются г ‘^омощи цикла
l4emod RedrawPeckQ следок остается провест Тгп^',^ карты. Напо-
вам уже встречался. становить колоди ^с>ртировку. Вос­
создать экземпляр D ecl^ '^Р°'Че. Достаточно

У ер е^ ер н и тп е с т р а н и ц у и 1 ^ о Д о Л ж и м !

дальше > 373


перегрузка информации

ажнение Присвоение элемент ам управления значимых


ешение имен облегчает чтение кода. Если бы эти
кнопки назывались ЬиИоп1_СИск, ЬиНоп2_СИск
и т. д., вы не смогли бы сразу определить их Это
назначение! остаилок
кода формы.

p riv a te v o id r e se tl_ C lic k (o b je c t send er, E v e n t A r g s e)


R esetD eck (1);
R edraw D eck(1 );

p r iv a te v o id r e s e t2 _ C lic k (o b je c t sen d er, E v e n tA r g s e) {


R esetD eck (2); Функция эт их
R edraw D eck(2 ); кнопок проста —
сначала восстано-
} оить или пере­
тасовать колоди^
p r iv a te v o id sh u ffle l_ C lic k (o b je c t sen d er, E ven tA rgs e) { а затем перери-
d e c k l.S h u ffle O ; СОбс{ІАЛ.Ь ее.
R edraw D eck(1 );
}

p riv a te v o id sh u ffle 2 _ C lic k (o b je c t send er, E v e n tA r g s e)


d e c k 2 .S h u ffle O ;
R edraw D eck(2 );
}

p r iv a te v o id m o v eT o D eck l_ C lick (o b ject sen d er, E v e n tA r g s e)


if (lis tB o x 2 .S e le c te d ln d e x >= 0)
if (d e c k 2 .C o u n t >0) {
d e c k l.A d d (d e c k 2 .D e a l(lis tB o x 2 . S e le c te d ln d e x ));
}
R edraw D eck(1 );
R edraw D eck(2 ); Свойство Selectedlndex
} элемента управления
ListBox позволяет п о ­
нять, какую именно
p riv a te v o i d m o v e T o D e c k 2 _ C lic k (o b je c t sen d er, E v e n tA r g s e) { карту выдрал пользо­
if (listB o x l.S e le c te d ln d e x >= 0) ватель. чтобы пере­
if (d e c k l.C o u n t > 0) м ест ит ь ее. (Если оно
d e c k 2 .A d d ( d e c k l.D e a l( lis t B o x l.S e le c t e d ln d e x ) ); имеет отрицательное
Redraw D eck(1 ); значение, значит, кар­
та не выбрана, и кнопка
R edraw D eck(2 );
ничего не делает.) После
перемещения карты
требуется перерисо­
вать обе колоды.

374 глава 8
перечисления и коллекции

Слобари
К о л л е к ц и и п о д о б н ы д л и н н ы м с т р а н и ц а м , з а п о л н е н н ы м и м е н а м и . А т е п е р ь п р е д с та в ь те , ч т о в ы х о т и т е
с о п о с т а в и т ь ка ж д о м у и м е н и адрес. И л и с н а б д и т ь к а ж д ы й а в то м о б и л ь в в а ш е й к о л л е к ц и и о п и с а н и е м .
Д ля э т о г о вам п о т р е б у е т с я с л о в а р ь ( d ic t io n a r y ) . Э та с т р у к т у р а п о з в о л я е т в з я ть н е к и й к л ю ч (k e y ) и свя­
зать е го со з н а ч е н и е м (v a lu e ). К а ж д ы й к л ю ч п р и э то м п о я в л я е т с я в с л о в а р е т о л ь к о о д и н р а з .

3 ^ 0 ключ. Именно сло-варь


т а к и щ ет ся
С писок сл о в в а л ф а в и тн о м порядке^
определение слова
о обычном словаре. с объяснением и х значений.

Это значение, то есть дан­


ные, связанные с определен­
ным ключом.
С л о в а р ь D i c t i o n a r y в C # о б ъ я в л я е тс я та к:

Dictionary <Ткеу, TValue> kv = new Dictionary <ТКеу, ТУа1ие> О


N --------- , -------------- Л
г у
Как м в случае с List<T>, символ <Т> Это снова типы. Первый
соот вет ст вует типу. К -л к видите, вь т ип в угловых скобках всегда
можете об-ьявить для ключа один т ип, соот вет ст вует ключу,
а для значений — другой. а второй — значению.

А в о т п р и м е р р а б о т ы со словарем :

p r i v a t e v o i d b u t t o n l _ C l i c k (o b j e c t sen d eri> E v e n tA r g s e )
{
D i c t i o n a r y o t r i n g , s t r in g > w o r d D e fin it io n = ‘Принадлежат 'T m u n ^T -^^-
new D i c t i o n a r y o t r i n g , s t r i n g > ( ) ; Моделируем обычный t

wordDefinitio^fГ^d^"C:лoвapь", "Книга, содержащая список слов "


КЛН5И1Л u ^--- -- "в алфавитном порядке и объяснянлцая их значения");
з н а ч е н и я do
« о га в е£ х п 1 Ь 1 о п .А а а (»ключ", " с р е д с т в о , дающее нам д о с т у п "
бавляю т ся Метод AddQ
+ "к пониманию чего бы то ни бьшо.");
в словарі> сначала берет
пом о­ wordDefinition.Add ("Значение", "Размер, количество или число.");
ключ, а потом
щи метода значение.
AddO- if (wordPefinitiok^ ContainsKey("Ключ'^ {
MessageBox.Show(wordDefinition["Ключ"]);
зволяет и кд зй н н о го

слова.

дальше ► 375
сопоставь что угодно чему угодно

Функциональность слобарей
С л о в а р и во м н о го м н а п о м и н а ю т к о л л е к ц и и . О н и г и б к и , п о з в о л я ю т вам р а б о та ть с д а н н ы м и
п р о и з в о л ь н о г о т и п а и и м е ю т м н о ж е с т в о в с т р о е н н ы х ф у н к ц и й . Р а с с м о тр и м о с н о в н ы е м е то ­
д ы класса D i c t i o n a r y :

^Добавление элементов.
Добавление осуществляется путем передачи ключа и значения методу Add о .

D ic tio n a r y < s tr in g , s t r i n g > m y D ic t io n a r y = n ew D i c t i o n a r y < s t r x n g , s t r in g > { ) ;


m y D i c t i 0n a r y .A d d (" K a K 0Ü -T 0 клю ч", " к а к о е-т о зн а ч е н и е " );

^Поиск значения по ключу.


Самым важным при работе со словарем является просмотр значений — собственно,
за этим вы их там и храните. Для словаря D i c t l o n a r y < s t r i n g , s t r i n g > ДО СТуП
К значению осуществляется по ключу типа string, возвращает он так же строку.

s tr in g lo o k u p V a lu e = m y D i c t i o n a r y [" к а к о й -т о к л ю ч " ];

^Удаление элементов.
Как и при работе с объектами L i s t , для удаления из словаря используется метод
Remove о. Достаточно передать ему ключ, как сам ключ и значение будут удалены.

m y D i c t i o n a r y . R em ove (" к а к о й -т о к л ю ч " ); ^оизвольноеТ оличес^^^ 'лвчм

♦получение списка ключей.


Свойство K ey s в комбинации с циклом fo r e a c h позволяет получить перечень ключей
словаря.

fo r e a c h ( s t r i n g k e y i n m y D ic t io n a r y .K e y s ) { . . . } ;
—^ К л ю ч и о т н о с я т с я к свойствам словаря, ß рассматри-
★ П о д сче т пар. ^ ваемом случае они принадлежат типу string, поэтому
_ э т о набор строк.
Свойство C ount возвращает число пар «ключ-значение», имеющихся в словаре:
int howMany = myDictionary.Covmt;

Ключ u значение м огут принадле)кать разным типам


С л о в а р и ф а к т и ч е с к и у н и в е р с а л ь н ы и м о гу т с о д е р ж а ть з н а ч е н и я л ю б ы х т и п о в ( о т с т р о к д о о б ъ е к т о в ).
В о т п р и м е р сл о в а р я , в к о т о р о м к л ю ч и о т н о с я т с я к т и п у in t , а з н а ч е н и я — к о б ъ е кта м D u c li.

D i= tio n a r y < in t, D uck> d u c k D i c t i o n a r y = n ew D i c t i o n a r y < i n t , D u c k X ),


u объекты, исполь- d u c k D i c t i o n a r y . A d d ( 3 7 6 , n ew D u c k O
зуются в случаях { Kind = KindOf Duck. Mallard, Size = 15 >) ;
присвоения объектам => s ' •
уникальных идент и­
фикаторов.
376 глава 8
перечисления и коллекции

практическое применение слоВаря


В о т н е б о л ь ш а я п р о гр а м м а , к о т о р а я п о н р а в и т с я ф анатам б е й с б о л а и з Н ь ю -
Й о р к а . К а к т о л ь к о и г р о к п е р е с та е т в ы с т у п а т ь за ком анду, ф у тб о л к а с е го н о м е ­ У праж нение!
р о м п е р е с та е т и сп о л ь з о в а т ь с я . С оздадим п р о гр а м м у , к о т о р а я п о к а зы в а е т, к а к и е
н о м е р а б ы л и у з н а м е н и т ы х и г р о к о в и к о гд а э т и и г р о к и у ш л и и з б о л ь ш о го с п о р та .

c l a s s J e rsey N u itib er {
p u b lic s t r i n g P la y e r { g e t; p r iv a t e s e t ; } Йоги £>ерра играл под * 2 за
p u b lic in t Y ea rR etired { g e t ; p r iv a t e s e t ; } одну команду, а Карл Рипкин-
младший... под т е м ж е ном е­
p u b lic J e rsey N u m b e r(strin g p la y e r . in t n u m b erR etired ) { ром за другую. Но в словаре
P la y e r = p la y e r ; одному значению может со­
Y e a r R e tir e d = n u m b erR etired ; ответ ст воват ь только один
ключ, поэт ому в программе на
}
данный м омент перечислены
} номера игроков одной команды.
В о т ф орм а; Попытайтесь придумать спо­
соб сохранять информацию об
! Retired Jersey Numbers игроках нескольких команд.

Nufflb«- 42 was worn Jadde Rotoson and retired m 1ЭЭЗ

А это код ф ормы :

p u b l i c p a r t i a l c l a s s F o r m l : F orm {
D i e t io n a r y < in t , JerseyN um ber> r etired N u m b ers n e w D i c t i o n a r y < i n t , J e r s e y N u m b e r > () {
{ 3 , new J e r s e y N u m b e r ( "Babe R u th " , 1 9 4 8 ) } ,
{ 4 , n e w J e r s e y N u m b e r { "Lou G e h r i g " , 1 9 3 9 ) } ,
{ 5 , new J e r s e y N u m b e r ( " Joe D iM a g g io " , 1 9 5 2 ) } , Объекты JerseyNumber
{ 7 , new J e r s e y N u m b e r ( " M ick ey M a n t le " , 1 9 6 9 ) } , передаются в словарь при
{ 8 , new J e r s e y N u m b e r ( "Y ogi B e r r a " , 1 9 7 2 ) } ,
помои^и инициализатора
{lO , new J e r s e y N u m b e r ( " P h il R i z z u t o " , 1 9 8 5 ) } ,
коллекции.
{ 2 3 , n e w J e r s e y N u m b e r { "Don M a t t i n g l y " , 1 9 9 7 ) } ,
{ 4 2 , new J e r s e y N u m b e r { " J a c k ie R o b in so n " , 1 9 9 3 ) } ,
{ 4 4 , new J e r s e y N u m b e r ( " R e g g ie J a c k s o n " , 1 9 9 3 ) } ,
};
p u b l i c F o r m l {) { К л ю ч ы из словаря
In itia liz e C o m p o n e n tO ; ^ добавляются в
коллекцию элементов
f o r e a c h ( i n t k e y i n r e t ir e d N u m b e r s . K eys) {
ComboBox.
num ber. I te m s.A d d (k e y );
}
}
p r i v a t e v o id n u m b e r _ S e le c te d I n d e x C h a n g e d (o b je c t s e n d e r , E ven tA rgs e) {
JerseyN um ber jerseyN u m b er = r e tir e d N u m b e r s [ (i n t ) n u m b e r .S e le c te d lte m ] a s JerseyN um ber;
nam eLabel .T ex t = je rse y N u m b e r . P la y e r ;
Свойство Selectedltem эле­
y e a r L a b e l.T e x t = je r se y N u m b e r .Y e a r R e tir e d .T o S tr in g O ;
мент а ComboBox является
объектом. Так как ключ
\ SelectedlndexCkanged элемента ComboBox принадлежит т ипу int, вам
(K одновляет две м ет ки на форме, выводя значение потребуется операция при -
объекта JerseyNumber, взятое из словаря. ведения к эт ому типу.
сыграем в карты!

Длинные
упражнения
Создадим симулятор карточной игры.

Э то упраж нени е сл е гка о тл и ч а е т с я ...


Д о п уска е м , ч т о в ы изуч а е те C # , ч т о б ы п о т о м н а й т и работу. П р о г р а м м и с т ы р а б о т а ю т в к о м а н ­
де, и н и к т о н е з а н и м а е тс я со зд а н и е м п р о гр а м м о т н а ча л а д о к о н ц а . К а ж д ы й в ы п о л н я е т кусок
зад а ни я. С л о в о м , м ы р е ш и л и п р е д о с т а в и т ь вам пазл, в к о т о р о й о тд е л ь н ы е ф р а гм е н т ы у ж е со ­
б р а н ы . К о д ф о р м ы п р и в е д е н в ш а ге #3. В ам о с та е т с я т о л ь к о в в е с т и е го в И С Р . Н о п р и э то м
все кл а ссы , к о т о р ы е в ы п и ш и т е , должны работать с этим кодом. А в о т э т о г о д о б и т ь с я н е та к-то
просто!

С пециф икация
Р а б о та над л ю б ы м п р о ф е с с и о н а л ь н ы м п р о гр а м м н ы м о б е с п е ч е н и е м н а ­ Если задачу не
ч и н а е т с я со с п е ц и ф и к а ц и и . Вам п р е д с т о и т п о с т р о и т ь с и м у л я т о р клас­
с и ч е с к о й к а р т о ч н о й и г р ы Go Fish! С у щ е с тв у ю т р а з л и ч н ы е в а р и а н т ы сформулиро­
п р а в и л , в о т те , к о т о р ы е будете и с п о л ь з о в а т ь вы :

★ И с п о л ь з у е тс я ко л о д а и з 52 ка р т. И г р о к а м р аздается п о п я т ь
вать заранее,
ка р т. К а р т ы , о с та в ш и е с я п о с л е р а зд а ч и , н а з ы в а ю тс я з а п а с о м .
И г р о к и п о о ч е р е д и с п р а ш и в а ю т п р о н а л и ч и е к а р т (« Е с ть л и
как узнать,
у к о г о се м е р ки ? » ). И г р о к , и м е ю ш ;и й н у ж н у ю карту, о тд а е т ее.
Е сл и т а к о й к а р т ы н и у к о г о нет, б е р е тс я о д н а к а р та и з запаса.
что работа
★ Ц е л ь ю и г р ы я в л я е тс я с б о р в з я то к . В з я т к о й с ч и т а е т с я н а б о р и з закончена?
ч е т ы р е х о д и н а к о в ы х ка р т. Д л я в ы и г р ы ш а н у ж н о с о б р а ть м а к с и ­
м ал ьн о е к о л и ч е с т в о в з я то к . С о б р а н н ы й н а б о р и з ч е т ы р е х к а р т Именно по­
в ы к л а д ы в а е тс я н а с то л .

★ Е сли в ы л о ж е н н а я взя тка о ста в л яе т и г р о к а без карт, о н б е р е т п я ть


этому разра­
к а р т и з запаса. Е сли в запасе о ста л о сь м еньш е п я т и к а р т, и г р о к
б е р е т и х все. И г р а за ка н чи в а е тся ка к т о л ь к о запас иссякает. П о ­
ботка профес­
б едитель о п р е д е л яе тся п о м а кси м а льн о м у к о л и ч е с тв у взя то к. сиональных
★ В н а ш е й в е р с и и ч е л о в е к в ы с т у п а е т п р о т и в д в ух к о м п ь ю т е р н ы х
и гр о к о в . Каждая п а р ти я н а чи нается с вы бора человеком ка р ты , приложений
о н а л и ч и и к о т о р о й о н будет с п р а ш и в а ть . З атем два к о м п ь ю т е р ­
н ы х и г р о к а с п р а ш и в а ю т п р о с в о и к а р т ы . Р езультаты к а ж д о й начинается со
п а р т и и о т о б р а ж а ю т с я в т е к с т о в о м п оле.

★ П р о ц е д у р ы сд а ч и к а р т и в з я т о к а в т о м а т и з и р о в а н ы . К а к т о л ь к о
спецификации,
к т о -т о п о б е ж д а е т, и г р а з а к а н ч и в а е тс я , и в ы в о д и т с я и м я п о б е д и ­
те л я (и л и п о б е д и т е л е й в случае н и ч ь е й ). Б о л ь ш е н и к а к и х д е й ­
которая сооб­
с т в и й в ы п о л н и т ь нельзя. И г р о к у о с та е т с я т о л ь к о п е р е з а гр у з и ть щает вам, что
п р о гр а м м у, ч т о б ы н а ч а т ь н о в у ю п а р т и ю .
должно полу­
читься в итоге.

378 глава 8
перечисления и коллекции

о П о стр о ен и е формы
Ф ор м е нуж ен эл ем ен т управления L i s t B o x для от о б р а ж ен и я карт игрока, два эл ем ен ­
та управления T e x tB o x для от обр аж ен и я п р оц есса игры и кнопка, с п ом ощ ью к отор ой
и г р о к б у д е т с п р а ш и в а т ь о н а л и ч и и карт.

П р и с в о й т е с в о й с т в у Name э т о ­
го э л е м е н т а T e x tB o x -зн ачени е
Свойства Namp
и ^ Ы а т е . Ш э т о м сниМ ке эк р а н а
о н о в к л ю ч е н о , но п ш з а п у с к е п р о
г р а м м ы долж но о т о б р а ж а т ь ся . ^;^^о игра уже началась

Edad«at anytmehasa Seven Карты на


Joehas2Sevw« руках у игрока
ВЫ5has0 Sev«is ofDanonds отображаются
Joea*s farflfwehasaTen KingrfOubs
Edhas0Tens в поле ListBox
&*ha»OTens с им е н е м
Jse must A m fromtt» stoA. listHand.
, &* a ^ f anyone has a Quew
ahasOQueens
Joe has 0 Queens
8(3b 1ШЙ*aw fromthe stodc.
a has 7 cards.
Эти элементы Joehas4ca*,
Bobhas lOcanJs.
TextBox называ­ The stodc has 4 cards Wt.
ются textProgress
и textBooks.

Books
a has a book rf fives
Joe has a ЬоЫс of Aces
a h a sa to rfto f Fot*B
BobhasabD(^!^№es

Asd<foracw<tl

Присвойте свойству ReadOniu Присвойте свойству Name этой кнопки


^ эт их элементов TextBox- значение buttonAsk, а свойству Enabled —
значение True, что сделает значение False. В результ ат е кнопку м о ж н о
их доступными только для будет нажать только после начала игры.
ч т е н и я . Так же присвойте зна­
чение true свойству Multiline.

jJej^eBeJ^’H u m e с г о р а н и п у и и р 'о Д о Л Ж и м !

дальше У 379
код формы

Длинные
упражнения
О Э то код дюрмы
В в е д и т е е го в И С Р .

p u b l i c p a r t i a l c l a s s Form l : F o rm {
p u b l i c F o r m l () {
In itia liz e C o m p o n e n t();
} Это единственный класс, с которым
взаимодействует фо^>ма. Именно он
p riv a te Game gam e; управляет всей игрои.

Свойство p riv a te v o i d buttonStart_Click(object s e n d e r , E ven tA rgs e) {


B n a b le d if (S tr in g .IsN u llO r E m p ty (te x tN a m e .T e x t)){
Включает и M e s sa g e B o x .S h o w (" P le a s e e n t e r y o u r nam e " C an 't start t h e game y e t " )
от клю чает retu rn ;
эл ем енты }
управления
game new G a m e (te x tN a m e .T e x t, new L i s t < s t r i n g > { "Joe", "Bob" }, tex tP ro g ress)
формьь
A u tto n S ta r t.E n a b le d = f a ls e ;
(te x tN a m e .E n a b le d = fa lse ;
При начале новой игры создается эк ­
b u tto n A sk .E n a b le d = tru e; земпляр класса Game, становится до­
U p dateF orm O ; ступной кнопка Ask, пропадает достцп
к кнопке S ta r t Game, после чего форма
перерисовывается.
p r iv a te v o id UpdateFormO {
Этот метод lis tH a n d . I te m s . C le a r (); Свойство SelectionStart
очищает т ек­ foreach (S trin g cardN am e i n g a m e . G e t P l a y e r C a r d N a m e s () и метод ScrollToCaretO
стовое поле позволяют прокручи -
lis tH a n d .I te m s .A d d (c a r d N a m e );
от предыду­ бать т екст , если он не
щего списка и te x tB o o k s.T ex t = g a m e .D e sc r ib e B o o k s0 ;
te x tP r o g r e s s .T e x t += g a m e . D e s c r i b e P l a y e r H a n d s ( ) ; умещается в текстовое
заполняет его ) поле.
набором карт t e x tP r o g r e s s .S e le c t io n S t a r t = te x tP r o g r e ss .T e x t.L e n g th ;
для новой игры. te x tP r o g r e s s .S c r o llT o C a r e t();
Свойство SelectionStart
определяет начало вы-
оеленного ф рагм ен­
p r i v a t e v o i d buttonAsk_Click(object s e n d e r , E v en tA rgs e) {
та, после этого метод
t e x t P r o g r e s s . T e x t = "" ;
ScrollToCaretQ прокручи­
if (listH a n d .S e le c te d ln d e x < 0) { вает содержимое т ек ­
M e s sa g e B o x .S h o w (" P le a s e se le c t a card " ); стового поля до места
retu rn ; нахождения курсора.
}
if (g a m e .P la y O n e R o u n d (listH a n d .S e le c te d ln d e x )) {
t e x t P r o g r e s s . T e x t += "T h e w i n n e r i s . . . + g a m e . G e t W i n n e r N a m e ()
te x tB o o k s .T e x t = g a m e .D e sc r ib e B o o k s0 ;
b u t t o n A s k . E n a b l e d = rf a li ss ee ;;
e lse A Игрок выбирает карт у и щелкает на кноп-
U p dateF orm O ; ке Ask, чтобы узнат ь, есть ли данная карта
У кого-нибудь из соперников. Класс Game игра­
ет с помощью метода PlayOneRoundQ.

380 глава 8
перечисления и коллекции

Этот код вам тоже понадобится


Вам п о т р еб у е т с я у ж е н а п и с а н н ы й к од для класса C a r d , п е р е ч и с л е н и й S u i t s и V a l u e s ,
класса D e c k и класса C a r d C o m p a r e r _ b y V a lu e . В классы н у ж н о будет д о б а в и т ь н еск ол ьк о
м е т о д о в ... суть к о т о р ы х вы д о л ж н ы п о н и м а т ь .

4 / ------- Метод РеекО позволяет взять


p u b lic C a r d Peek ( i n t cardNum ber) { карт у из колоды.
retu rn card s[card N u m b er];
}
К т о -т о перегрузил метод PealQ, сделав его
p u b l i c Card D e a lO { ^ g восприятия. Если не передавать
j,

r e t u r n D e a l (0) ; ^ Т м у п а раш т рь,. будет сдана верхняя карта


} в колоде.

p u b lic b o o l ContainsValue ( V a l u e s v a l u e ) {
Метод ContainsValueQ ищет
foreach (C ard c a r d i n cards) 6 колоде карты определенного
if (c a r d .V a lu e == v a l u e ) старшинства и, находя их,
retu rn tru e; возвращает значение true. Вы
retu rn fa lse ; догадываетесь, как он будет
}
использоваться в нашей игре?

p u b l i c D e c k PullOutValues ( V a l u e s v a l u e ) {
D e c k d e c k T o R e t u r n = n e w D e c k ( n e w C a r d [] { });
f o r ( i n t i = c a r d s . C o u n t - 1 ; i >= 0 ;
if (c a r d s [i] .V a lu e == v a l u e )
d e ck T o R etu rn .A d d (D ea l(i ) ) ;
retu rn deckT oR eturn;
и з в л е к а е т ад из
}
варіант t Z f d T e T
p u b l i c b o o l HasBook ( V a l u e s v a l u e )
включена и в з я т к ^ Р ^ ' ^
i n t N um berO fC ards = 0;
foreach (C ard c a r d i n cards) Г Метод HasBookQ, п о ­
if (c a r d .V a lu e == v a l u e )
лучив в качестве п а ­
N u m berofC ard s++; рамет ра карт у, на­
if ( N u m b e r O f C a r d s == 4) чинает искать взятки.
retu rn tru e; Обнаружив четыре
e ls e одинаковые карты, он
retu rn fa lse ;
возвращает значение
true.
}

p u b lic v o id SortByValue() { Метод SortByValuef)


c a r d s . S ort(n ew C a r d C o m p a r er _ b y V a lu e ( )) сорт ирует к о л о д У
}
с >ломощью класса
< ^ o m p a r e r ^ b y V a lu e .

П ереверн ите стр ан и ц у и лрододжим!

дальше > 381


покажи им, как надо играть!

Длинные
упражнешя
Э то С Л О Ж Н А Я часть: создание класса Player
Э к зем п л я р ы класса P l a y e r сущ ествую т для в сех и гроков. О н и с о з­
даю тся обработчиком собы тий кнопки b u t t o n S t a r t .
c la ss P la y e r
{
Внимательно читайте
p r i v a t e s t r i n g name; комментарии, т ам сказано,
p u b l i c s t r i n g Name { g e t { r e t u r n n a m e ; } } н Х действия долж­
ны выполнять методы для
p r i v a t e R andom random;
которых вы пишете код
p r i v a t e D e c k cards;
p r i v a t e T e x t B o x textBoxOnForm;
p u b lic Player ( S t r i n g n a m e , R andom r a n d o m , T e x tB o x tex tB oxO n F orm ) {
// Конструктор класса P l a y e r инициализирует четыре закрытых поля, а затем
// добавляет элементу управления T e x t B o x строку " J o e h a s j u s t
// j o i n e d t h e g a m e " , используя имя закрытого поля. Не забудьте поставить
// знак переноса в конец каждой строки, добавляемой в T e x t B o x .
}
p u b lic I E n u m e r a b l e < V a l u e s > PullOutBooks () { } // код на следующей странице
p u b lic V a l u e s GetRandomValue () {
// Этот метод получает случайное значение, но из числа карт колоды!

p u b lic D e c k DoYouHaveAny ( V a l u e s v a l u e ) {
// Соперник спрашивает о наличии у меня карты нужного достоинства
// Используйте метод D e c k . P u l l O u t V a l u e s о для взятия карт. Добавьте в T e x t B o x
// строку " J o e h a s 3 s i x e s " , используйте новый статический метод C a r d . P l u r a l ()

p u b l i c v o i d A s k F o r A C a r d (L ist< P la y er > p l a y e r s , i n t m y ln d ex . D eck s t o c k ) {


// Это перегруженная версия A s k F o r A C a r d () - выберите случайную карту с помощью
// метода G e t R a n d o m V a l u e () и спросите о ней методом A s k F o r A C a r d ()
}
p u b l i c v o i d AskForACard( L i s t < P l a y e r > p l a y e r s , i n t m yln d ex. D eck s t o c k . V a lu es v a lu e ) {
// Спросите карту у соперников. Добавьте в T e x t B o x текст: " J o e a s k s i f a n y o n e h a s
/ / a Q u e e n " . В качестве параметра вам будет передана коллекция игроков
// спросите (с помощью метода D o Y o u H a v e A n y ()), есть ли у них карты
// указанного достоинства. Переданные им карты добавьте в свой набор.
// Следите за тем, сколько карт было добавлено. Если ни одной, вам нужно
// взять карту из запаса (передается как параметр), в текстовое
// поле нужно добавить строку T e x t B o x : " J o e h a d t o d r a w f r o m t h e s t o c k "
}
// Перечень свойств и коротких методов, которые уже были написаны
p u b lic in t CardCoiint { g e t { retu rn c a r d s.C o u n t; } }
p u b l i c v o i d TakeCard(Card c a r d ) { c a r d s.A d d (c a r d ); }
p u b lic I E n u m e r a b l e < s t r i n g > GetCardNames () { retu rn c a r d s .G e tC a r d N a m e s ( ) ; }
p u b lic Card P e e k ( i n t cardNum ber) { retu rn c a r d s . P eek (card N u m b er); }
p u b lic v o id SortHandO { c a r d s . S o r tB y V a lu e (); }

382 глава 8
перечисления и коллекции

Метод PeekQj добавленный нами в класс


Реек, очень полезен. Он позволяет программе
посмот рет ь на карт у, индекс к о т ^ о и вы
указали, но, в отличие от метода PealQ, не
удаляет карт у из колоды.

p u b l i c I E n u m e r a b l e < V a l u e s > PullOutBooks() {


L i s t < V a l u e s > b o o k s = new L i s t < V a l u e s > ( ) ;
f o r ( i n t i = 1 ; i <= 1 3 ; i + + ) {
V a lu es v a lu e = (V a lu e s )i;
i n t how M any = 0 ;
f o r ( i n t c a r d = 0; c a r d < c a r d s .C o u n t; card ++ )
i f ( c a r d s . P e e k ( c a r d ) . V a l u e == v a l u e )
how M any++;
i f (how M any = = 4 ) {
b o o k s.A d d (v a lu e );
f o r ( i n t c a r d = c a r d s . C o u n t - 1 ; c a r d >= 0 ; ca rd --)
c a r d s.D e a l(c a r d );
}
}
retu rn b ook s; Вам потребуются Д В Е перегруженные
версии метода МкРогАСагс(0. Первая бу~
ucnoлt>зoв£XiAлt>cя вашими conepнuкaAЛUJ
она должна просмат риват ь их карты
и выбирать т у, о которой нужно спро­
сить. Вторая версия используется, когда
о карте спрашиваете вы. При эт ом обе
версии опрашивают ВСЕХ игроков (как
компьют ер, т ак и человека).

Ещ е один м етод для класса Card


Э т о т с т а т и ч е с к и й м е то д в о з в р а щ а е т н а з в а н и е к а р т ы (в о м н о ж е с т в е н н о м
ч и с л е ). Т а к к а к о н с т а т и ч е с к и й , для в ы зо в а н у ж н о ука за ть и м я к л а с с а — C a r d .
P l u r a l О — и о т н е го н е возм ож но п о л уч и ть экзем пляры .

p u b lic p a r tia l c la ss Card {


p u b lic sta tic strin g P lu r a l(V a lu e s v a lu e ) Д лядобавления этого с т а ­
if (v a lu e == V a l u e s . S i x ) тического метода мы ис­
пользовали разделяемый класс.
retu rn " S ix e s" ; В эт ом случае вам проще
e ls e понять, что именно происхо­
retu rn v a lu e .T o S tr in g O + "s" ; дит. Но необходимости ис­
пользовать разделяемый класс
} не было, так мет од можно
} добавить в класс Card.

(^К о р о З а к о н ч и м , иереБ ернш ю е а х р а н и ц у !

дальше > 383


запиши их

Длинные
упражнешя
Создание классо б а т е
Ф о р м а с о х р а н я е т э к з е м п л я р Game, у п р а в л я ю щ и й и г р о й . П о с м о т р и т е , ка к
о н и сп о л ьз у е тся .
8 классах Player и Game имеются ссылки на
c la ss Game {
элем ент формы TextBoxJ в котором появля­
p riv a te L i s t < P l a y e r > players;
ются сообщения о ходе игры. Убедитесь, что
p r iv a te D i c t i o n a r y < V a l u e s , P l a y e r > books; в верхней части файлов есть строка «using
p r iv a te D e c k stock; System.VJindows.Forms;».
p r iv a te T e x t B o x textBoxOnForm;
p u b l i c Game ( s t r i n g p l a y e r N a m e , I E n u m e r a b l e < s t r i n g > o p p o n e n t N a m e s , T e x tB o x textB oxO n F orm )
R an do m r a n d o m = n e w R a n d o m { ) ;
t h i s . textB oxO n F orm = textB oxO n F orm ;
p l a y e r s = new L i s t < P l a y e r > ( ) ;
Интерфейс IEnumerable<T>
делает классы более гибки­
p la y e r s .A d d ( n e w P la y e r (p la y e r N a m e , random , textB oxO n F orm ))
f o r e a c h ( s t r i n g p l a y e r i n opp onentN am es)
ми. Об этом следует помнить
p l a y e r s .A d d ( n e w P l a y e r ( p l a y e r , random, textB oxO n F orm ))
при дальнейшем редакти­
b o o k s = new D i c t i o n a r y < V a l u e s , P l a y e r > ( ) ;
ровании кода. В данный мо­
s t o c k = new D eck О ; _ -у.
мент для получения экзем­
D eal ();
Это полезно и для инкапсуляции. пляра класса Game можно
Если использовать 1ЕпитегаЫе<Т> написать stringQ.List<string>.
p l a y e r s [0] . S o r t H a n d O
вместо List<T>j вы не сможете
^ случайно отредактировать код.
p r iv a t e v o id D ea lO {
/ / И м ен н о з д е с ь н а ч и н а е т с я и г р а .
// Тасуется колода, р а з д а е т с я по пять кар т каждому и г р о к у , з а т е м с помощью
// цикла f o r e a c h вы зы вается м е т о д P u l lO u t B o o k s О для каж дого и гр ок а.

p u b lic bool PlayOneRound ( i n t selected P la y erC a rd ) {


// С ы г р а й т е о д и н р а з . П а р а м е т р о м я в л я е т с я в ы б р а н н а я и г р о к о м к а р т а и з им ею щ и х ся н а р у к а х
// В ы зовите м е т о д A sk F o rA C a r d О для каж дого и з и г р о к о в , начиная с ч е л о в е к а
// с нулевы м и н д е к с о м . З а т е м в ы з о в и т е м е т о д P u l l O u t B o o k s О -
// е с л и он в е р н е т з н а ч е н и е t r u e , з н а ч и т , у и г р о к а кончились
// кар ты . З а к о н ч и в с о в с ем и и г р о к а м и , о т с о р т и р у й т е карты
// ч е л о в е к а (ч тобы с п и с о к в форме в ы г л я д е л к р а с и в о ) . П р о в е р ь т е , н е з а к о н ч и л и с ь
// л и карты в з а п а с е . В с л у ч а е п о л о ж и т е л ь н о г о р е з у л ь т а т а о ч и с т и т е п о л е T e x tB o x
// и в ы в е д и т е ф р а з у "T h e s t o c k i s o u t o f c a r d s . Game o v e r ! " .
}
p u b lic bool PullOutBooks ( P l a y e r p l a y e r ) {
// И г р о к и в ы к лад ы ва ю т в з я т к и . М е т о д в о з в р а щ а е т з н а ч е н и е t r u e , е с л и к а р т ы у
// и г р о к а з а к о н ч и л и с ь . Каждая в з я т к а д о б а в л я е т с я в с л о в а р ь B o o k s .
}
p u b lic s t r i n g DescribeBooks О {
// Э тот м е т о д в о зв р а щ а е т длинную с т р о к у с оп и са н и е м в з я т о к к аж дого и г р о к а ,
// взяв з а осн ов у содерж ание словаря B ooks: "Joe h as а book o f s i x e s .
// ( п е р е н о с с т р о к и ) Ed h a s a b o o k o f A c e s . "
}

384 глава 8
перечисления и коллекции

А ля написания метода GetWinneirNameQ нужно создать новый словарь Dictionaru<strina int>


л я т ь ^ ' Х п ^ Т ' п о м е с т и т ь его н« самый верх. По имени игрока словарь будет предостав-
к о л и ч е с т в е сделанных им за игру взяток. Сначала с помощью цикла foreach
нужно будет записать в словарь все сделанные взятки. Зат ем второй цикл foreach должен
/илксм м дльное значение. Нужно помнит ь, что возможна и ничья - ситуация, когда
к о л м ч е с тб о взяток присут ст вует у более чем одного игрока! Так что вам по-
^ о д и н цикл foreach, ищущии всех игроков в словаре winners, которые имею т м а к-
Л вы и^л " ■ ^ информацией о Z m кто
public Л г 1 п д GetWinnerName () {
II Этот м е т о д вы зы вается в к он це игры. Он и с п о л ь з у е т собственны й словарь
// ( D i c t i o n a r y < s t r in g , in t > w in n e r s) для отслеж ивания к ол ич ества в зя т о к
// к а ж д о г о и г р о к а . С н а ч а л а ц и к л foreach (Values value in books.Keys)
// з а п о л н я е т с л о в а р ь winners и н ф о р м а ц и е й о в з я т к а х . З а т е м
/ / словарь п р осм атр и в ается на предм ет пои ск а максим ального
/ / к о л и ч е с т в а в з я т о к . Н а п о с л е д о к с л о в а р ь п р о с м а т р и в а е т с я ещ е о д и н р а з , ч т о б ы
/ / сформировать сп и с о к п о б е д и т е л е й в ви де стр о к и { "Joe and E d") . Если п о б е д и т е л ь
/ / о д и н , в о з в р а щ а е т с я с т р о к а "Ed w i t h З b o o k s " . В п р о т и в н о м
/ / с л у ч а е в о з в р а щ а е т с я с т р о к а "А t i e b e t w e e n J o e a n d B o b w i t h 2 b o o k s . "

// П а ра к о р о т к и х м е т о д о в , к о т о р ы е бы ли н а п и с а н ы р а н ь ш е : Введите 6 окно Watch (in ty \r'


чтобы присвоить символ \ r '
числу. В результ ат е вы поли-
p u b l i c I E n u m e r a b l e < s t r i n g > GetPlayerCardNames() { Чите г з . В то время как Л п’
r e t u r n p l a y e r s [ 0 ] .G e tC a r d N a m e s( ) ; превращается в Ю . Каждый
симоол превращается в си м -
оол гОникод, Аополнит ельицю
p u b l i c s t r i n g DescribePlayerHands() { информацию по этой теме вы
получите в следующей главе.
s t r i n g d e s c r i p t i o n = "" ;
f o r ( i n t i = 0; i < p l a y e r s .C o u n t ; i+ + ) {
d e s c r i p t i o n += p l a y e r s [ i ] . N a m e + " h a s " + p l a y e r s [ i ] . C a r d C o u n t ;
i f ( p l a y e r s [ i ] . C a r d C o u n t == 1)
d e s c r i p t i o n += " c a r d . " + E n v i r o n m e n t . N e w L i n e ;
e ls e
d e sc r ip tio n += " c a r d s . " + E n v iro n m en t.N ew L in e;
}
d e s c r i p t i o n += "T h e s t o c k has " + sto c k .C o u n t + " cards le ft." ;
retu rn d e sc r ip tio n ;

Она содержит символы \r\n . Если ^ o c £ ^ T fm T u T ^ '^ ‘^ ^'^wVonmentWewL/ne. t-


ванный в Windows, в конце каждой с^воки ^ и “Я ^*^форматиро~
гие операционные АРИ~

385
решение упражнения

решение
длинных
П р З Ж Н е Н И Й В о т ка к п о л н о с т ь ю в ы г л я д я т м е т о д ы д л я к л а с с а Game.

p r i v a t e v o i d Deal О { Метод PealQ вт ывает ся в начале


sto c k .S h u ffle О ; м асует колоду и раздает
fo r ( i n t 1 = 0 ; i < 5 ; i + + ) каждому игроку no пят ь карт.
fo r e a c h (P la y e r p la y e r in p la y e r s ) Зат ем он собирает взятки, если
p l a y e r . T a k e C a r d ( s t o c k . D e a l () ) ; маковые появляются.
fo r e a c h (P la y e r p la y e r in p la y e r s )
P u llO u tB o o k s(p la y e r );

p u b lic b o o l PlayOneRound ( i n t se lected P la y e rC a rd ) {


V a lu e s cardT oA skFor = p l a y e r s [ 0 ] . P e e k ( s e le c t e d P la y e r C a r d ) .V a lu e ,•
f o r ( i n t i = 0; i < p l a y e r s . C ou nt; i+ + ) {
i f ( i == 0)
p l a y e r s [ 0 ] .A sk F o rA C a rd (p la y ers, 0, stock , card T oA sk F or);
e ls e
p la y e r s[i].A sk F o r A C a r d (p la y e r s, i , s to c k );
if ( P u l lO u t B o o k s ( p la y e r s [ i] )) {
Если после того t e x t B o x O n F o r m . T e x t += p l a y e r s [ i ] . N a m e
как игрок спросил + " d rew a new hand" + E n v ir o n m e n t. N e w L in e ;
карт ц, образова­ i n t c a r d = 1;
К
лась взятка, она w h i l e ( c a r d <= 5 && s t o c k . C o u n t > 0) I После щелчка на кнопке A s k for
У ке-го забирается. p l a y e r s [i ] . T ak eC ard (s t o c k . D e a l ()] a card игра вызывает метод
Ь сли взяток нет card++; AskForACardQ с выбранной
он берет новые ' } картой в качестве параметра.
карт из за-} Зат ем мет од AskForACardQ
naca. вызывается для каждого из
p l a y e r s [0 ] . S o r t H a n d O ;
if (sto c k .C o u n t = = 0 ) { игроков.
textB oxO n F orm . T ex t =
"The s t o c k i s o u t of cards. Game o v e r ! E n v ir o n m e n t.N ew L in e ;
retu rn tru e;

}
РУ^<>тся!^ч^тТб1Т ипог>яд^‘^'^‘ copm u-
retu rn fa lse ;
Зат ем n p o 6 e S e m !^ ^ отображаемый
cnucoK.
лась ли игра b L u J Z закончи-
}

p u b lic bool PullOutBooks ( P l a y e r p l a y e r )

I E n u m e r a b l e < V a l u e s > b o o k s P u l l e d = p l a y e r . P u l l O u t B o o k s ()
f o r e a c h (V a lu es v a lu e i n b o o k sP u lle d )
b o o k s.A d d (v a lu e , p la y e r )
i f ( p l a y e r . C a r d C o u n t == 0) Метод PullOutBooksQ проверяет карты
retu rn t r u e ; игрока на наличие б з я т о к . Обнаруженная
retu rn f a ls e ; взятка добавляется в словарь. Если карт
н е осталось, возвращается значение true.

386 глава 8
перечисления и коллекции

Так как в форме должен отобра­


жаться список взяток, воспользу­
емся методом Ре5спЬеТкеВоок5()
чтобы преврат ит ь записи словаря
p u b l i c s t r i n g DescribeBooks() { о строки. ^
s t r i n g w h oH asW hich B ooks =
fo r e a c h (V a lu e s v a lu e i n b o o k s.K e y s)
w h o H a s W h i c h B o o k s += b o o k s [ v a l u e ] . N a m e + " h a s a b o o k o f
+ C a r d .P l u r a l ( v a lu e ) + E n v ir o n m e n t.N ew L in e ;
r e t u r n w h oH asW h ich B ook s;
)

p u b l i c s t r i n g GetWinnerName() {
D i c t i o n a r y < s t r i n g , i n t > w i n n e r s = n e w D i c t i o n a r y < s t r i n g , i n t > ()
fo r e a c h (V a lu es v a lu e i n b o o k s.K e y s) {
s t r i n g name = b o o k s [ v a l u e ] . N a m e ;
i f (w in n e r s.C o n ta in sK e y (n a m e )) После взятия последней карты
w i n n e r s [ n a m e ] ++; требуется определить победителя.
e ls e Именно эт им занимается метод
w in n er s.A d d (n a m e , 1 );
GetWinnerNameQ на основе инфор­
мации из словаря winners. К л ю ч о м
} является имя игрока, а значением -
i n t m ostB ook s = 0;
f o r e a c h ( s t r i n g nam e i n w i n n e r s . K e y s )
количество взяток.
i f (w in n ers[n a m e] > m ostB ook s)
m o s t B o o k s = w i n n e r s [na m e] ; <3
bool t ie = fa lse ; \ Зат ем определяется
s t r in g w in n er L ist = ;
максимальное количество
f o r e a c h ( s t r i n g nam e i n w i n n e r s . K e y s )
взяток. Оно помеи^ается
в переменную mostBooks.
i f ( w i n n e r s [na m e] == m o s t B o o k s )
{
if ( 'S tr in g .I s N u llO r E m p ty (w in n e r L is t)
{
w i n n e r L i s t += " a n d ";
t i e = tru e;
Теперь, когда мы знаем
}
w in n er L ist += n a m e ; игрока с максимальным
количеством взяток,
} можно вывести строку
w i n n e r L i s t += " w i t h " + m o s t B o o k s + " b o o k s " ;
с именем победителя
i f (tie)
(или победителей).
r e t u r n "A t i e b e t w e e n " + w i n n e r L i s t ;
e ls e
r etu rn w in n er L ist;

Ц ереБернш пе сш ранипу U п р о д о д ж ш !

дальше ► 387
решение _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
длинных
ПрЗЖ НеНИЙ Так в ы гл я д я т п о л н о с ть ю н а п и с а н н ы е м етод ы к л а сса P l a y e r .

p u b l i c P l a y e r ( S t r i n g n a m e . Random r a n d o m , TTeexxttBB oo x t e x t B o x O n F o r m ) {
t h i s . n a m e = name;
t h i s . r a n d o m = random ; Э т о к о н с т р у к т о р к л а с с а P la y e r . О н
t h i s . textB oxO n F orm = textB oxO n F orm ; ^ —з а д а е т з нa а ч е...................................
н и я п р и в а т н ы х п о л е й и до -
t h i s . c a r d s = new D e ck ( new C ard [] {} ) ; 5 в т екст овое поле ст року с ин-
t e x t B o x O n F o r m . T e x t += n a m e + ф о р м а ц и е й о п р и со ед и н и вш ем ся и гр о к е.
" has ju st jo in e d th e g am e " + E n v ir o n m e n t.N ew L in e ;
}

p u b l i c V a l u e s G e t R a n d o m V a l u e () {
C ard random C ard = c a r d s . P e e k (r a n d o m . N e x t (c a r d s . C o u n t)
r e t u r n random C ard. V a lu e ; М е т о д а ^ Я а п с 1 о т \ / а 1 и е ( ) с п о м о ш ,ьн >
} м е т о д а Р еекО вы б и р а е т с л уч а й н ую
к а р т у с р е д и и м е ю ш ,и х с я у и г р о к а .
p u b l i c D eck D oY ouH aveA ny(V alues v a l u e ) {
D eck c a r d sIH a v e = c a r d s . P u l l O u t V a l u e s ( v a l u e ) ;
t e x t B o x O n F o r m . T e x t += Name + " h a s " + c a r d s I H a v e . C o u n t +
+ C a r d .P lu r a l (v a lu e) + E n v ir o n m e n t.N e w L in e ; М е т о д P o Y o u H a v e A n u Q и с п о л ь з ц е т
retu rn cardsIH ave; М е т о д P u llO u tV a lu e s i) д л я и з в л е ч е -
‘ ^ ------' Н и я к а р т , к о т о р ы е с о о т в е т с т в у ­
ю т заданны м п а р а м е т р а м
p u b l i c v o i d A s k F o r A C a r d ( L i s t < P l a y e r > p l a y e r s , i n t m y ln d e x . D e ck stock)^ ( ^
V a l u e s r a n d o m V a lu e = G etR a n d o m V a lu e ( ) ;
A s k F o r A C a r d ( p l a y e r s , m y ln d e x , s t o c k , r a n d o m V a lu e) В програм м е два п е ­
регруж енных мет ода
As.kPorACard{). Э т от
Дополнительное мини-упражнение: Улучшите инкапсуляцию использует ся со пер­
класса Player, заменив в этих двух методах List<Player> на ни ка м и — выбирает
IEnumerable<Player>, не повлияв на работу программы. случайную карт у из
имеюш,ихся и вызы­
вает вт орой м ет од
AskForACardQ.
p u b l i c v o i d A s k F o r A C a r d (L ist< P la y er > p l a y e r s , i n t m y ln d ex .
D eck s t o c k . V a lu e s v a lu e ) {
t e x t B o x O n F o r m . T e x t += Name + " a s k s i f a n y o n e h a s a "
+ v a lu e + E n v ir o n m e n t.N ew L in e ;
i n t t o t a l C a r d s G i v e n = 0;
f o r ( i n t i = 0; i < p l a y e r s .C o u n t ; i+ + ) {
i f ( i != m y l n d e x ) { М е т о д A sk F o rA C a rd Q
P la y e r p la y e r = p l a y e r s [ i ] ; п р о в е р я е т всех и гр о к о в
D eck C a r d sG iv e n = p l a y e r . D o Y o u H a v e A n y (v a lu e ); (за и ск л ю ч ен и ем с п р а ш и ­
t o t a l C a r d s G i v e n += C a r d s G i v e n . C o u n t ; ^ в а ю щ е го ), вы зы в а ет их
w h i l e ( C a r d s G i v e n . C o u n t > 0) м ет о д P oY ouH aveA nyQ
c a r d s.A d d (C a r d sG iv e n .D e a l 0 ) ; и д о б а вл я ет найденны е
} подходящ ие карт ы .
}
if ( t o t a l C a r d s G i v e n == 0 ) {
t e x t B o x O n F o r m . T e x t += Name +
" m ust draw from t h e s t o c k . " + E n v ir o n m e n t. N ew L in e;
c a r d s . A d d ( s t o c k . D e a l () ) ;
П ри о т с у т с т в и и у с о п е р н и к о в п од ходя щ и х
к а р т и гр о к б ер е т к а р т у из зап аса п ри
п о м о щ и м е т о д а P e a lQ .
перечисления и коллекции

Дополнительные типы коллекций...


О бъ екты L i s t и D ic t i o n a r y относятся к в с т р о е н н ы м о б о б щ е н н ы м к ол ­
л е к ц и я м и я в л я ю т с я ч а с т ь ю .N E T F ra m e w o rk . О н и о ч е н ь г и б к и , д о с т у п к и х
д а н н ы м о су щ е ств л я е тс я в п р о и з в о л ь н о м п о р я д к е . Н о и н о гд а р а б о т у п р о гр а м ­
м ы с д а н н ы м и тр е б у е тс я о г р а н и ч и т ь , т а к к а к явление, к о т о р о е в ы м о д е л и р у е ­
те , д о л ж н о р а б о та ть , к а к в р е а л ь н о с т и . В с и т у а ц и я х и с п о л ь з у ю т с я о ч е р е д ь
(Q u e u e ), ил и с т е к ( S t a c k ) . О н и относя тся к обобщ енны м коллекциям , но
га р а н ти р ую т обработку д а н н ы х в определенном порядке.

И спол ьзуйте очередь, когда И спол ьзуйте стек, когда первы м вы


первы й сохраненны й о б ъ ект будет соб и раетесь об р аб аты в ать посл едний
о б рабаты в аться первы м . Как в случае: из со хр ан ен н ы х об ъ ектов . Как в случае:
★ автом обилей, д ви ж ущ и хся п о улице ★ м еб ели , з а гр у ж е н н о й в кузо в гр у з о ­
с о д н о сто ро н н и м движ ением ; в и ка ;

★ лю д ей, сто я щ и х в очереди; ★ с то п к и кн иг, из к о т о р ы х вы х о ти те


сн а ча л а п р о ч и т а т ь в е р х н ю ю ;
★ клиентов, ж д ущ их обслуж ивания по
те л е ф о н у; ★ л ю д е й , в ы х о д я щ и х и з сам олета;

★ д р у г и х с и т у а ц и й , к о гд а п е р в ы м о б слу­ ★ пирам ид ы из лю дей. П е р вы м с п р ы ­


ж ивается тот, к то первы м приш ел. ги в а т ь в н и з д о л ж е н то т , к т о н а ­
х о д и т с я н а сам ом верху. Т о л ь к о
п р е д с та вь те , ч т о п р о и з о й д е т , е сл и
Очередь р<х6омаеп\ по принципу начать ухо д и ть из п ир а м и д ы снизу!
«раньше вошел, раньше вышел».
То есм ь объект, первым пом е- Стек работает по диаметрально
щенный в очередь, первым и об­ противоположному принципу. То есть
рабатывается. чем позже объект попадает в стек,
т ем раньше он обрабатывается.

Обобщенные коллекции являю тся 6а)кной частью Очередь подобна


NET Framework
О н и насто л ько поле зн ы , ч т о И С Р а вто м а тиче ски добавляет
списку. Новые объ­
о п е р а т о р в в е р х н ю ю ч а с т ь к а ж д о го класса, к о т о р ы й созда­
е тся в р а м к а х п р о е к т а :
екты помещаются в
u s i n g System.Collections.Generic; его конец, а читается
О б о б щ е н н ы е к о л л е к ц и и в с т р е ч а ю т с я п о ч т и в о в се х п р о ­
е к та х , ведь вам н у ж н о гд е-то х р а н и т ь д а н н ы е . Г р у п п ы о д и ­
он сначала. Стек же
н а к о в ы х о б ъ е к т о в в р е а л ь н о м м и р е п р а к т и ч е с к и всегда
м о ж н о о б ъ еди нить в ка те го р и и , ко то р ы е в т о й и л и и н о й
дает доступ только к
с т е п е н и н а п о м и н а ю т к а к у ю -то и з к о л л е к ц и й .
последнему помещен­
Ц иклforeach позволяет осущ ест ­
влять перечисления в очереди и
стеке, так как они реализую т ин­
ному в пего объекту.
терфейс lEnumerable!
любите ли вы стоять в очереди?

Збенья следуют 6 порядке их поступления


О ч е р е д ь о тл и ч а е т с я о т с п и с к а те м , ч т о в ы н е м о ж е т е д о б а в л я ть и уда­
л я т ь э л е м е н ты с п р о и з в о л ь н ы м и н д е к с о м . В ы д о б а в л я е т е о б ъ е к т
в о ч е р е д ь ( e n q u e u e ) и у д а л я е т е и з н е е ( d e q u e u e ) . В п о с л е д н е м случае
о с та в ш и е с я о б ъ е к т ы с д в и га ю т с я н а о д и н элем ент.

Создаем Queue<string> myQueue = new Queue<string>();


очередь
строк. myQueue.Enqueue("first in line");
В очередь добавляются че­
myQueue.Enqueue("second in line"); тыре элемента.
myQueue.Enqueue("third in line");
myQueue.Enqueue("last in line");
string takeALook = myQueue.Peek() ;@ Первый мет од DequeueO
s t r in g g e t F ir s t = m y Q u e u e . D e q u e u e ()
Метод Реек()
позволяет вы­ string getNext = myQueue.Dequeue
делить п ер ­
вый элемент int howMany = myQueue.Count
........... .......................................................... его. уж е
в очереди, не myQueue.Clear();
удаляя его.
MessageBox.Show("Peek() returned: " + t a k e A L o o k + " \ n "
+ "The first Dequeue 0 returned: ‘ -I- g e t F i r s t + ' \ n ' ‘
+ "The second Dequeue 0 returned: " + g e t N e x t + ' \ n "
f^emod CleavQ
удаляет из + "Count before Clear 0 was " + h o w M a n y + " \ n "
очереди все + "Count after Clear 0 is now " + myQueue.Count);
объекты.

Свойство Count возвращает


число элементов в очереди.

В
Объекты РеекО returned; first in tine ( I )
ждут своей
очереди. The firs t DequeueO returned; firs t in lin e ( i)
The second DequeueO returned: second in tin e @
C o u n t before ClearO was 2 ®
C o u n t after ClearO is n ow 0 0

390 глава 8
перечисления и коллекции

Збенья следуют 6 порядке, обратном порядку их поступления


Стек и м еет всего одн о, н о зн ачи тельн ое отличие о т оч ер еди . Вы помещаете в него каждый элем ент
и м о ж е т е в л ю б о й м о м е н т взять п о с л е д н и й э л е м ен т стека. С тек н а п о м и н а ет бусы . С н ачал а вы сн и м а ет е
б у с и н у , к о т о р а я б ы л а н а н и з а н а п о с л е д н е й , п о т о м с л е д у ю щ у ю и т. д . Н е л ь з я в з я т ь ч е т в е р т у ю с к о н ц а
бусину, н е сн яв т р и п р ед ы д ущ и х.

Поме- С о з д а н и е с т е к а ничем не
о т с^о з д а н и я л ю д о и
м а е /л ы й отличается от
б стек д р уго й о6обиА,еннои коллекции.
элем ент
6 c e ^ ^ n p o * ^ w e S ta c k < str in g > m y S t a c k = new S t a c k < s t r i n g > () ;
э л е М ^ е н т Е ^ ^ ;;^ ^ ^ ^ " f ir s t in lin e ");

ницу бныз m y S t a c k . P u s h ( " s e c o n d in lin e ");

бается m y Sta ck . P u sh ( " t h ir d in lin e ");


Метод PopO удаля­
ИЙ сам ом lin e "); ет из стека п о ­
верху- следний добавленный
© s t r in g ta k e A L o o k = m y S t a c k . P e e k () ; туда элемент.
O s t r in g g e t F ir s t = m y S ta c k .P o p ();
Вместо символов \ п можно ^
0 s t r in g g e tN e x t = m y S ta c k .P o p (); воспользоваться константой
Environment.NewLinCj но мы
@ in t how M any = m y S ta c k .C o u n t; предпочли сократить код.
m y S t a c k •C l e a r 0 ;
. \
M e s s a g e B o x . S h o w ( " P e e k () re tu rn e d ; + takeALooJc +
ta k e A L o o k + "\п " /

+ "T h e f ir s t PopO re tu rn e d : + g e t F ir s t + "\n "

+ "T h e se co n d PopO re tu rn e d : " + g e tN e x t + "\n "

+ "C o u n t b e fo re C le a r O w as " + how M any + "\n "

+ "C o u n t a fte r C le a r O is now " + m y S ta c k .C o u n t);

П оследни й о б ъ е к т ,
пом ещ ен ны й в с т е к ,
PeekO returned fast: in ltne@ ст ан овит ся п ервы м
The first PopO returned: last in line @ объект ом , кот оры й
б уд ет о т т у д а взят .
The secorrd PopO returned; third in fine Q
Count before ClearO was 2 ©
Count after ClearO is now 0 ©

OK
дальше > 391
лепешки и лесорубы

Бессмыслица какая-то... А что


я могу сделать со стеком и очередью такого,
чего не могу сделать с объектами L ist? Они всего
лишь позволяют сделать код на пару строк короче.
П р и том, что я не им ею доступа к и х средним
элементам. Ну и зачем такие ограниченные
объекты могут мне понадобиться?

Не волнуйтесь, вам не придется ни в чем себя ограничивать.


С к о п и р о в а т ь о б ъ е к т Q u e u e в о б ъ е к т L i s t о ч е н ь л е гк о . Т а к ж е л е гк о , к а к с к о ­
п и р о в а т ь о б ъ е к т L i s t в о б ъ е к т Q u e u e , а о б ъ е к т Q u e u e в о б ъ е к т S ta c k . .. б о ­
лее т о г о , в ы м о ж е те со зд а ть о б ъ е к т ы L i s t , Q u e u e и S t a c k и з л ю б о г о д р у го го
о б ъ е к та , р е а л и з у ю щ и е и н т е р ф е й с lE n u m e r a b le . Д о с т а т о ч н о в о с п о л ь з о в а ть ­
ся п е р е гр у ж е н н ы м к о н с т р у к т о р о м , к о т о р ы й п о з в о л я е т п е р е д а в а ть к о п и р у е ­
м у ю к о л л е к ц и ю в к а ч е с т в е п а р а м е тр а . То е с ть в ы м о ж е т е л е гк о п р е д с т а в и ть
с в о и д а н н ы е в вид е к о л л е к ц и и , к о т о р а я м а к с и м а л ь н о с о о т в е т с т в у е т ва ш и м
нуж дам . ( Н о н е за б ы в а й те , ч т о п р и к о п и р о в а н и и в ы создаете н о в ы й о б ъ ект,
к о т о р ы й будет з а н и м а ть м е с то в п а м я ти .)

Заполним ст ек четырьмя
ст роками. ^

Stack<string> myStack = new Stack<string>();


myStack. Push ("first in line"); м о ж н о легко С т е к

myStack. Push ("second in line"); зат ем т р а н с ф о р м и р опйлть


ват ь
myStack. Push ("third in line"); в list, вернут ь g ,e о б ъ е к т

myStack.Push("last in line"), в с„т»яние стек». ^

Queue<string> myQueue = new Queue<string>(myStack);


List<string> myList = new List<string>(myQueue);
Stack<string> anotherStack = new Stack<string>(myList);
MessageBox.Show("myQueue has " + myQueue.Count + " items\n"
+ "myList has " + myList.Count + " items\n"
+ "anotherStack has " + anotherStack.Count + " items\n");

BM « « я р е
Sam cKonupo«» m y Q u e u e h a s 4 ite m s ...Д Л Я д о с т у п а ко в с е м
б новые колдекцим. m y L ist h a s 4 ite m s членам очеред и или стека
a n o th e r S ta c k h a s 4 ite m s
д остаточно воспол ьзоваться
циклом ^ ге а с И !

392 глава 8
перечисления и коллекции

_ Напишите программу, которая помогает хозяину кафе кормить лесорубов лепешками.


ажненке Начните с класса L u m b e r j a c k (Лесоруб). Сконструируйте форму и добавьте к кнопкам
обработчики событий.
en u m F l a p j а с к {
Л Д обавьте в класс Ь гш Ь е г j а с к м етод ч т е н и я для с в о й с тв а F l a p -
C risp y ,
j a c k C o u n t и м е то д ы T a k e F l a p j a c k s и E a t F l a p j a c k s . Soggy,
Browned,
c l a s s L u m b erjack { Banana
p r i v a t e s t r i n g name;
}
p u b l i c s t r i n g Name { g e t { r e t u r n
p r i v a t e S ta c k < F la p ja c k > m e a l;-
O u tp u t - Я X
p u b l i c Ь ш п Ь е гj a c k ( s t r i n g n a m e ) { • -ч-гчвв
t h i s . n a m e = name;
m ea l = new S t a c k < F l a p j a c k > { ); Ed’ s e a t i n g f l a p j a c k s
} Ed a t e a b ro w n ed f l a p j a c k
p u b lic in t FlapjackCount { g e t { // возвращает число } } Ed a t e a s o g g y f l a p j a c k
Ed a t e a s o g g y f l a p j a c k
p u b l i c v o i d T a k e F l a p j a c k s ( F l a p j a c k F o o d , i n t HowMany) { Ed a t e a so g g y f l a p j a c k
/ / Д обав л я ет в с т е к M eal у к а з а н н о е к о л и ч ес т в о лепеш ек Ed a t e a c r i s p y f l a p j a c k
} Ed a t e a s o g g y f l a p j a c k
p u b l i c v o i d EatFlapjacks{) { Ed a t e a b a n a n a f l a p j a c k
/ / В ы в е д и т е э т и с в е д е н и я н а к о н с о л ь ------------------------------------- Ed a t e a bro**ned f l a p j a c k

© Ф о р м а п о з в о л я е т в в е с ти и м я л е с о р у б а в т е к с т о в о е п о л е , ч т о б ы п о м е с т и т ь е го в о че р е д ь.
В ы д а в п е р в о м у в о ч е р е д и л е с о р у б у л е п е ш к и , в ы о тп р а в л я е т е е го е сть , щ е л к н у в н а к н о п ­
ке N e x t lu m b e rja c k . М ы н а п и с а л и о б р а б о т ч и к с о б ы т и й к н о п к и A d d fla p ja c k s . О т с л е ж и ­
в а й те с о с т о я н и е л е с о р у б о в п р и п о м о щ и о ч е р е д и b r e a k f a s t L i n e .

Щелчок на кнопке A dd LuMberjack do- Заметили, что перечисление


iS BreiiMartfofLumbejacte
^ ёавляет имя лесорцба из текстового Flapjack использует строчные
ІшЬазаскпагое поля пате в очередь breakf astLine. буквы (Soggy), но на выходе
вы получаете прописные
Перетаскивая элементы RadioBuUon (soggy)? Подсказываем, как
внутрь элемента droupBoXj вы авт о­ исправить ситуацию. Метод
матически соединяете их и оставляє-
1.Ed ,т е возможность выбрать только один ToStringO возвращает строку,
2.ВЙУ
3. .tones переключатель за один раз. Узнать и м е­ одним из открытых членов
4. Fred на переключателей можно в методе которой является метод
5.JdiSTsen
6. ВЫ)Ьу, Jr. addFfapJacks_Click. ToLowerO, возвращающий
Эж.(?ИА только прописные буквы.
элем ент ^ p r iv a t e v o id i^ lic k (...) {
IіS t box F l a p j a*
называете !ЄсіЬаз8ваРІВ£^ i f ( ^ i s ^ .C h e c k e 5 ~ ^ tru e)
line. обрат ит е внимание на
^ o d = F la p ja ck T cr isp y ; ./ гииіллаксис: else іГ.
e l s e i f ( ; о ; д у " с Ь е с к е Г : : t r u e ) ( ОСОБЫЙ с и н т а к с и с :
fo o d = F la p ja c k .S o g g y ; X
e lse i f ( b r o w n e d . C h e c k e d == t r u e ) !
fo o d = F la p j a c k . Browned; P e e k Q в о з в р а щ а е т ССЫЛКУ
Эта кнопка убирает из очереди e ls e первого лесоруба в очерет-
следуюш,его лесорцба, вызывает его fo o d = F la p ja c k .B a n a n a ; '
метод EatFlapjacks() и перерисовыбает
содержимое текстового поля.
L um b erjack c u r r e n tL u m b e r jа с к = b r e a k f a s t L i n e . P e e k {);
Метод RedrawListQ выводит в т е к ­ c u r re n tL u m b e rj a c k . T a k e F la p j a c k s ( f o o d ,
стовое поле содержимое очереди. Его (in t)h o w M a n y .V a lu e );
вызывают все три кнопки. Подсказка: ^ e d r a w L istO Элемент управления NumericUpDown
он использует цикл foreach. i
называется howMany, a label называется
'nextlnLine.
решение упражнения

неше p r iv a te Q ueue<L um berjack> b r e a k f a s t L i n e = new Q u e u e < L u m b e rja c k > ( ) ;


p r i v a t e v o id a d d L u tn b e r ja c k _ C lic k (o b je c t se n d e r , E ven tA rgs e) {
ешение b r e a k f a s t L in e .E n q u e u e ( n e w L u m b e r ja c k (n a m e .T e x t) ) ;
n a m e . T e x t = "" ; Э п \о
R e d r a w L i s t {) ; /,• ^ ^ ^ м е н т L k tR n ^

p r iv a t e v o id R ed r a w L ist0
i n t num ber = 1;
l i n e . I te m s . C lea r
М ет од f o r e a c h (L um b erjack lu m b e r ja c k in b re a k fa stL in e ) {
Reйf^'йu/L/st(') п р и l i n e . I t e m s . A d d ( n u m b e r + ". " + lu m b e r ja ck .N a m e );
<°2мощи цикла num ber++;
гогеаск убира­ Этот оператор обновляет
ет лесорубов из Е ( b r e a k f a s t L i n e . Count = = 0 ) {
мет ку 6 соответствии
°^ереди и поллеи^а - g r o u p B o x l.E n a b le d = f a l s e ;
с информацией о следующем
ет сведения о ник n e x tln L in e .T e x t =
лесорубе в очереди.
о список. e lse {
g r o u p B o x l.E n a b le d = tr u e ;
L um b erjack c u r r e n tL u m b e r ja c k = b r e a k f a s t L i n e . P e e k ( ) ;
n e x t l n L i n e . T e x t = c u r r e n t L u m b e r j a c k . Name + " h a s "
^ + cu rren tL u m b erja ck .F la p ja ck C o u n t + " fla p ja c k s "

p r i v a t e v o id n e x tL u m b e r ja c k _ C lic k (o b je c t se n d e r , E ventA rgs e ) {


L u m b erjack n e x tL u m b e r ja c k = b r e a k f a s t L i n e . D e q u e u e () ;
nextL um b erj a c k . E a tF la p j a c k s ();
n e x t I n L i n e . T e x t = "";
R ed r a w L ist();
}

c l a s s L um b erjack {
p r i v a t e s t r i n g nam e;
p u b l i c s t r i n g Name { g e t { r e t u r n n a m e ; } }
p r i v a t e S t a c k < F la p ja c k > m ea l;

p u b l i c L u m b e r j a c k ( s t r i n g nam e) {
t h i s . n a m e = nam e;
m ea l = new S t a c k < F l a p j a c k > ( ) ;
Метод
TakeFlapjacks
обновляет со­ p u b lic in t F la p ja c k C o u n t { get { retu rn m e a l.C o u n t; } }
держимое с т е ­
ка Меа! (Прием p u b lic v o id T a k e F la p ja c k s(F la p ja c k fo o d , in t how M any) {
пищи). f o r ( i n t i = 0 ; i < how M a n y; i + + ) {
m e a l . Push ( f o o d ) ; Именно здесь перечисление
} } Flapjack впервые пишется про­
писными буквами. Постарайтесь
P iib lic v o id E a tF la p ja c k s О { понять, как это происходит.
^кЦ е ^ C o n s o l e . W r i t e L i n e (n a m e + " ' s e a t i n g f l a p j a c k s "
Ф орм аии ^ ( m e a l . C o u n t > 0) {
^ ^ Ж д о го ^ ‘^ P '^ n e s e C o n s o l e . W r i t e L i n e (nam e + ate a _к Г
'^^‘^ o p yS a . + m e a l . P o p () . T o S t r i n g () . T o L o w e r () + " f l a p j a c k " ) ;
^ ~ —

} Метод meal.PopO возвращает перечисление, метод


___________ 1___________ ToStringQ которого возвращает строку, метод ToLowerO
которой возвращает другую строку.
перечисления и коллекции

„ Напишите программу, которая помогает хозяину кафе кормить лесорубов лепешками.


ажнение Начните с класса L u m b e r j a c k (Лесоруб). Сконструируйте форму и добавьте к кнопкам
обработчики событий.
enum F l a p j a c k {
А Д обавьте в класс L u m b e r j а с к м етод ч т е н и я для с в о й с тв а F l a p -
C risp y ,
j a c k C o u n t и м е то д ы T a k e F l a p j a c k s и E a t F l a p j a c k s . Soggy,
Browned,
c l a s s L u m b erjack { Banana
p r i v a t e s t r i n g name;
p u b l i c s t r i n g Name { g e t { r e t u r n
p r i v a t e S ta c k < F la p ja c k > m e a l;-
Oiftput
p u b l i c L m n b e r j a c k ( s t r i n g nam e) {
t h i s . n a m e = name;
m e a l = n e w S t a c k < F l a p j a c k > () , Ed's €3tif!g flapjacks
} Ed ate B brosmeit flapjack
p u b lic in t Flapj ackCount { g e t { // возвращает число } } Ed ate a soggy flapjack
Ed ate a soggy flapjack
p u b l i c v o i d T a k e F l a p j a c k s ( F l a p j a c k F o o d , i n t HowMany) { Ed a te a soggy flapjack
/ / Д обав л я ет в с т е к M eal у к а з а н н о е к о л и ч ес т в о лепеш ек Ed a te a crispy flapjack
} Ed a te a soggy flapjack
p u b l i c v o i d EatFlapjacks{) { Ed a te a banana flapjack
/ / В ы в е д и т е э т и с в е д е н и я н а к о н с о л ь ----------- - ' Ed ate a browned flapjack

© Ф о р м а п о з в о л я е т в в е с ти и м я л е с о р у б а в т е к с т о в о е п о л е , ч т о б ы п о м е с т и т ь е го в о че р е д ь.
В ы д а в п е р в о м у в о ч е р е д и л е с о р у б у л е п е ш к и , в ы о тп р а в л я е т е е го е с ть , щ е л к н у в н а к н о п ­
ке N e x t lu m b e rja c k . М ы н а п и с а л и о б р а б о т ч и к с о б ы т и й к н о п к и A d d fla p ja c k s . О т с л е ж и ­
в а й те с о с т о я н и е л е с о р у б о в п р и п о м о щ и о ч е р е д и b r e a k f a s t L i n e .

Щелчок на кнопке A dd Lumberjack до- Заметили, что перечисление


----------------------- ^ бавляет имя лесорцба из текстового Flapjack использует строчные
1шМаокпате ' 4УШ пят е в очсредь breukfastLine. буквы (Soggy), но на выходе
вы получаете прописные
ШШстт^ . Перетаскивая элементы RadioButton
внутрь элемента GroupBox, вы авт о­ исправить ситуацию. Метод
SredcfaSSne ]Г матически соединяете их и оставляе­
1.Ed те возможность выбрать только один ToStringO возвращает строку,
2.Шу одним из открытых членов
3. Jones переключатель за один раз. Узнать и м е­
4, Fred на переключателей можно в методе которой является метод
5. Jdiisns^
6 , ВЫ*у. Jr. addF!apJacks_Click. ToLowerO, возвращающий
Этот только прописные буквы.
элем ент p r i v a t e v o i d addFlapjack^Click (. . .) {
listbox F la p ja c k fo o d ;
называете i f t ^ r i s j y . C hecke3~5p tr u e ) „ « u ,,hnnnue Ha
tine.
W ? - F l i 5 3 ^ r i 3 p y , ^ l e ;f.
e l s e i f {s o g g y . C h e c k e d == true) CU
fo o d = F la p ja c k .S o g g y ; \
e l s e i f ( b r o w n e d . C h e c k e d == t rJ.uU eC )) )I
f o o d = F l a p j a c k . B r o w n e d ; м ет од PeekO ссы лку
Эта кнопка убирает из очереди e ls e m первого лесоруба в очерес^и.
следуюш,его лесорцба, вызывает его fo o d = F la p ja c k .B a n a n a ;
метод EatFlapjacksO и перерисовывает
содержимое текстового поля.
L um b erjack c u r r e n tL u m b e r ja c k = b r e a k f a s t L i n e . P eek О ;
Метод RedrawListQ выводит в т е к ­ c u r re n tL u m b e rj a c k . T a k e F la p j a c k s ( f o o d ,
стовое поле содержимое очереди. Его (in t)h o w M a n y .V a lu e );
вызывают все три кнопки. Подсказка: . e d r a w L i s t () ; д д ^ д д е н т у п р а в л е н и я N u m e r ic U p D o w n
он использует цикл foreach. I
называется howMany, a label называется
-------------------- nextlnLine.
л
9 Ч т е н и е U З а п и с ь ‘= = * а й л о Б

4 -

^ Сохрани массив байтов и спаси мир


4 -

Иногда настойчивость окупается.


П ока что все ваш и програм м ы жили недолго. О ни запуска­
лись, некоторое врем я р аб отал и и закры вались. Н о этого
недостаточно, когда им е е ш ь д е л о с важ ной инф орм ацией.
В ы долж ны уметь со хранять св ою работу. В этой главе мы
поговорим о том, как зап и сать д ан н ы е в ф айл, а затем о
том, как п рочитать э ту инф ор м ац и ю . В ы познаком итесь с
потоковы м и классам и .N E T и узнаете о тай н ах ш естн а д ц а­
те р и чн о й и д в о и ч н о й систем счисления.
острова в потоке

Для чтения u записи данных 6 .NET используются потоки


Поток (stream) — э т о с п о с о б , к о т о р ы м .N E T F ra m e w o rk о б м е н и ­
в а ется д а н н ы м и с п р о гр а м м о й . К а ж д ы й раз, к о гд а п р о гр а м м а ч и ­
Для чтения данных
т а е т и л и за п и с ы в а е т ф айл , с о е д и н я е т с я с д р у ги м к о м п ь ю т е р о м
с е т и и л и в ы п о л н я е т д р у ги е д е й с т в и я , с в я з а н н ы е с передачей
из файла и записи их
байтов и з о д н о го м е ста в д р у го е , р е ч ь и д е т о п о т о к е д а н н ы х . в файл используется
объект Stream.
П редставьте п р о стую п р о гр ам м у — ф о рм у с о б ­
раб отчико м собы тий, чи таю щ и м д ан н ы е из ф а й ­
л а. Э та оп ер ац и я о сущ еств л яется с пом ощ ь ю
о б ъ екта S tream .

Переменная input содержит


,
данные прочитанные ^ ■■■U п о и л о к
из объекта Stream Вы и с п о л ь з у е т е М оилает
о б ъ е к т S tr e a m ... ст венно

Д л я зап и си д ан н ы х в ф айл и спользуется д р у ­


гой о б ъ ект S tream .

stream.Write(...);
Переменная output
содержит данные,
занисьшаемые
в объект s tream .

•процесса не изменится.

396 глава 9
чтение и запись файлов

Различные потоки для различных данных


К а ж д ы й п о т о к я в л я е тс я п р о и з в о д н ы м о т а б с т р а к т н о го класса S tre am , и сущ е­
с тв у е т м н о ж е с т в о в с т р о е н н ы х кл а ссо в stre a m , п р е д н а з н а ч е н н ы х для р а з л и ч н ы х
о п е р а ц и й . В д а н н о й главе будут р а с с м о т р е н ы ч т е н и е и з а п и с ь в о б ы ч н ы е ф а й л ы ,
н о все э т и с в е д е н и я л е гк о п р и м е н и т ь к с ж а т ы м и з а ш и ф р о в а н н ы м ф айлам и даж е
к п е р е д а ч е д а н н ы х п о с е ти . является
абст ракт н ы м
поэтому вы не
можете создавать
его экземпляры.
Э т о некот оры е
метода класса
S tre a m .
ства и методы в со-

FileStream MemorvStream NetworkStream GZIoStream

CloseO CloseO Closed


CloseO
ReadO ReadO ReadO
ReadO
SeekO SeekO SeekO SeekO

WriteO WriteO WriteO WriteO

О бъект О бъект О бъект О б ъ е к т G Z ip S t r e a m


F ile S tr e a m M em oryS tream N e tw o rk S tre a m сж им ает данны е,
позволяет позволяет читать о с у щ е с тв л я е т у м е н ь ш а я за н им а е ­
ч и та ть ф айлы д а н н ы е и з п а м я ти ч те н и е и запись м ое и м и м е с то и тем
и з а п и с ы в а ть и з а п и с ы в а ть и х д а н н ы х п о с е ти . сам ы м о б л е гч а я и х
в них. в п а м я ть. ска чи ва н ие и хр а н е ­
ние.
Вы MO)ketne:

о Записы вать в поток.


Э та о п е р а ц и я о с у щ е с тв л я е тс я п р и п о м о щ и м етод а W r i t e О .
Потоки позволя­
ют читать и за­
О Читать из потока. писывать данные.
М е т о д R e a d {) да е т д о с т у п к ч т е н и ю д а н н ы х и з ф айла, с е ти
и л и и з п а м я ти . Выбор потока
О Менять свое пол о ж ени е в п отоке. осуществляется
Б о л ь ш и н с т в о п о т о к о в п о д д е р ж и в а ю т м е то д S e e k ( ) , ус та н а в ­
л и в а ю щ и й ваш у п о з и ц и ю в п о т о к е . в соответствии
с типом данных.
дальше ► 397
намного проще

Объект FileStream
В о т ч т о п р о и с х о д и т п р и з а п и с и в ф а йл н е ­ В верхней части любой
с к о л ь к и х с т р о к те к с та : программы, ра6отаюш,ей
с потоками, должна п р и ­
сутствовать строчка
u s in g S y s te m .10;

С озда е тся о б ъ е к т F i l e S t r e a m , п о л у ч а ю щ и й
к о м а н д у з а п и с ы в а ть в ф айл.

П рисоедини т ь
объект
•ект FileStream
Л10ЖН0 только
><одному объекту
Зй раз. ^

О б ъ е к т F i l e S t r e a m п р и с о е д и н я е т с я к файлу.

С т р о к и , к о т о р ы е .т р е б у е т с я з а п и с а ть , следует
п р е о б р а з о в а ть в м а сси в т и п а b y t e .
117 114 1 0 1 107 97 33
Эта процедура называется
перекодированием, мы
E ureka! П1П101П1П1П1П
поговорим о ней чут ь позже.

В ы з ы в а е тс я м е то д W r i t e ( ) , к о т о р о м у пе р е д а ­
е тся м а сси в т и п а b y t e .

П о т о к з а к р ы в а е тс я , ч т о б ы д р у ги е п р о гр а м м ы м о гл и п о л у ч и т ь
д о с т у п к файлу.

Забы в _

398 глава 9
чтение и запись файлов

ТрехшагоВая процедура записи те кста 6 файл


Класс StreamWriter авто­
в C # сущ е ств уе т у д о б н ы й класс StreamWriter, в ы п о л н я ­
ю щ и й о п и с а н н ы й в п р е д ы д ущ е м разделе а л г о р и т м за о д и н матически создает объект
шаг. Вам н у ж н о т о л ь к о со зд а ть о б ъ е к т S t r e a m W r i t e r и
п р и с в о и т ь ему и м я . О н автоматически со зда ст о б ъ е к т FileStream и управляет
F i l e S t r e a m и о т к р о е т ф айл. П о с л е ч е г о о с та е тс я т о л ь к о
в о сп о л ь з о в а ть с я м е то д а м и W r i t e () и W r i t e L i n e ( ) . им.
Д ля открытия и создания срайлов используйте конструктор класса S tr e a m W r ite r
И м я ф айла м о ж н о п е р е д а ть к о н с т р у к т о р у S t r e a m W r i t e r { ) . П р и э то м ф а йл о т к р ы в а е т с я
а в т о м а т и ч е с к и . В классе S t r e a m W r i t e r сущ е ств уе т т а к ж е п е р е г р у ж е н н ы й к о н с т р у к т о р ,
р а б о т а ю щ и й с л о г и ч е с к и м и п а р а м е тр а м и : tr u e с о о т в е т с т в у е т д о б а в л е н и ю т е к с т а в к о н е ц
с у щ е с тв у ю щ е го ф айла, а false — у д а л е н и ю с у щ е с тв у ю щ е го ф айл а и с о з д а н и ю н о в о г о с ана­
л о ги ч н ы м им енем.
StreamWriter writer = new StreamWriter(@"С:\newfiles\toaster oven.txt", true);

■Чнлк & п е р е д и т н е / л ф а й л а

/а т н а е т
кяк т е к с т о в а я с т р о к а д ез
е5С-последователшостеи.

Д л я запи си в qxlйл используйте м етоды W r ite Q и W rite L in e Q


М е т о д W r i t e () з а п и с ы в а е т те к с т, а м е то д W r i t e L i n e () д о б а в л я е т к т е к с т у з н а к п е р е н о с а
с т р о к и . С и м в о л ы { 0 } , { ! } , { 2 } и т. д. в з а п и с ы в а е м о й с т р о к е п о з в о л я ю т в к л ю ч и т ь в т е к с т
п а р а м е тр ы : { 0 } за м е н я е тся п е р в ы м п а р а м е тр о м п о с л е з а п и с а н н о й с т р о к и , { 1 } - в т о р ы м
И Т. д.
w r i t e r .W riteL in e(" T h e {0} is set to {l} d e g r e e s." , a p p lia n c e , tem p );

0 Д ля освобождения от ф а й л а используйте м ето д CloseQ


О с т а в и в п о т о к о т к р ы т ы м и с о е д и н е н н ы м с ф а йл о м , в ы з а б л о к и р у е те ф айл
для в с е х о с т а л ь н ы х п р о гр а м м . П о э т о м у н е за б ы в а й те п и с а т ь :

w r ite r .C lo s e о ;

дальше > 399


запишите это

Дьявольский план Жулика З й п ^ с ь /б а т ь файл в корневой


каталог — не очень хорошая
°^^^Р»Нчонная система
Ж и т е л и О б ъ е к т в и л я д о л го е вр е м я б о я л и с ь Ж у л и к а . И в о т о н ре- не позволить это сде-
ш и л в о с п о л ь зо в а ть с я о б ъ е к то м S t r e a m W r i t e r для р е а л и з а ц и и Укажите любой
зл о в е щ е го плана. П о с м о т р и м н а н е г о п о б л и ж е . С о з д а й те к о н с о л ь - ‘^Р&с по вашему выбору.
н о е п р и л о ж е н и е и доб авьте э т о т к о д в м е то д M a i n O : - ^

Эта строчка создает объект

С S tr e a m W r it e r и указывает его адрес.


esc ~1лоследовательностц^

S tre a m W r i t e r sw = n e w S t r e a m W r i t e r (@"С;\ s e c r e t _ p l a n . t x t " ) ;


»»чалом

s w t W r i t e L i n e ( "H o w I ' l l d e fe a t C a p t a in A m a z in g ");


Метод I-
VJriteLineQ s w . W r it e L in e ( "A n o th e r g e n iu s se cre t p la n by The S w in d le r " );
taepeHOCum _ '
строки после s w . U n it e ( " I ' 11 c re a te an arm y o f c lo n e s and ");
записи, в то
время как s w . W r i t e L i n e ( " u n l e a s h th e m u p o n th e c it iz e n s of
метод WriteQ obj e c t v ilie .") ; ^ ------------ — —
от правля­
е т обычный s t r i n(int
for g lo n
cua mt b
i oe n = 0;
r = "th e mb ae r
num l l " ;<= 6; пгдтЬег++) {
текст.
sw.Writ e L i n e ( " C l o n e #{0} attacks {j^", number, location);
if (location == "the mall") { location = " d o w n t o w n " T ' 7 \
else { location = "the mall"; } Скобки {n} внутри
^ т ек­
ста передают перем ен­
} ^ ------ Метод
CloseQ разрывает связь ные в записываемую ст ро­
файлом и прочими ресурса-
с ку. {О} замещается первым
SW .C l o s e О VlМ
ми
/ vЛ^ сU которыми
т• ^ fработает объект
...........л •! 1^1 1 п о ­
. п Л > >лл л !Л Г
парамет ром после строки
Stream W riter. Если не закрыть { 1 } вт оры м —и т. д
ток, запись т е к с т а осуи^ествлена
не будет.

О бъ ект Вот такой результ ат получается на выходе.


S tr e a m W r ite r

\
находится в
File Edit F orm at tietp
п ростран ств е How I ’l l defeat cap tain Amazing ......... . ~ .......... ■
“ -------------------- -
и м е н S y s te m .lO , Another genius secr et plan by The swindler
п оэтом у в в ер х­ cloTO « ® a t tt c k r ? ^ e ° L l1 ° " “ uf’teash them upon the c itiz e n s o f o b je c t v n ie .
Clone #1 attacks downtown
ней части п р о ­ Clone #2 attacks the mall
clon e #3 attacks downtown
гр а м м ы д о л ж н а clone #4 attacks th e mall
бы ть стр о ка Clone #5 attacks downtown
Clone #6 attacks the mall
u s in g S y s te m .lO ;

400 глава 9
чтение и запись файлов

Zap);
sw.WriteLine(Zap) I
^[аГ ниш ы ДЛЯ о б ъ е к т а ^еаш ^Г 1|;еГ Zap = ange";
orange"; t
return true;
у вас есть код для кнопки b u t t o n l _ C l i c k (). Расположи­
те магниты таким образом, чтобы создать класс Р1оЬЬо.
При этом обработчик событий должен привести к резуль­
тату, показанному внизу страницы. Удачи!
sw.WriteLine(Zap);
a
private void buttonl_Click(object sender, EventArgs e) {
sw.Close();
Flobbo f = new Flobbo("blue yellow");
return false;
StreamWriter sw = f.SnobboO;
f .Blobbo(f .Blobbo(f .Blobbo(sw), sw), sw);
} public bool Blobbo '
(bool Already, StreamWriter sw) {

■public b ool Blobbo(StreamWriter

sw.WriteLine(Zap);
Zap = "green purple";
return false;

public StreamWriter SnobboO {

Результат:
с m acaw .txt - Notepaa
Fte Е Л Format View Н ф
blue yellow
green purple
red orange

дальше > 401


прочитайте это

еШение Задачи с МаГнищаМи


В ам тр еб о в ал о сь сконструировать класс Flobbo, работаю щ ий определенны м образом.

private void buttonl_Click(object sender, EventArgs e) {


Flobbo f = new Flobbo("blue yellow");
StreamWriter sw = f.SnobboO;
f .Blobbo(f .Blobbo(f .Blobbo(sw), sw), sw);

class Flobbo {
private string Zap;

public Flobbo(string Zap) {


this.Zap = Zap; Еще раз напоминаем, что для
} ребусов мы намеренно ис­
пользуем произвольные имена
переменных и методов, пот о­
public StreamWriter SnobboO { м у что значимые имена дела­

L iJ
return new
StreamWriter("macaw.txt");
f ю т задачу слишком легкой! Но
пожалуйста, не нужно брать
с нас прим ер, когда вы пишете
программы.

r
bool Blobbo (StreamWriter sw) {~j^
sw.WriteLine(Zap);
Zap = "green purple";
return false; Метод BlobboQ nepe-
имеет два
UJ объявления с двумя
различными парам е-
Іграм и . ^
public bool Blobbo
(bool Already, StreamWriter sw) {

if (Already) {

sw.WriteLine(Zap);
s(Zap); I
7
sw.Close О
После завершения
return false; Т будьт е закрыть файлы.
} else {
I Результат:
sw.WriteLine(Zap);
Zap = "red orange"; macaw - Notepad
return true;
File Edit Format View Help
M o e y e flo w
g re e n p u rp le
r e d o ra n g e
< у ,J. ■

402 глава 9
чтение и запись файлов

Чтение Uзапись при noMoui^u двух объектов В данном случае мы слишком вольно
используем слово «поток». Класс
StreamReader (наследующий ОТ
TextReader) читает символы из по­
С е кретны й план Ж ул и ка м ы прочитаем п р и пом ощ и п о тока
тока, но это не поток. Поток создается,
S t r e a m R e a d e r . И м е н н о е го к о н с т р у к т о р у п е р е д а е тс я и м я
когда вы передаете имя файла в его
ф айла, к о т о р ы й тр е б у е тс я п р о ч и т а т ь . М е т о д R e a d L i n e () воз­ конструктор, и закрывается с помо­
в р а щ а е т с т р о к у с т е к с т о м и з ф айла. Д ля п р о ч т е н и я в с е х с т р о к щью метода Close О . Он имеет также
и с п о л ь з у й т е ц и к л , к о т о р ы й р а б о та е т, п о к а п о л е E n d O f S t r e a m перегруженный конструктор, которо­
не п о л у ч и т з н а ч е н и е tru e , т о е с ть п о к а н е з а к о н ч а т с я с т р о к и : му можно передать объект stream.
Теперь вы поняли, как это работает?
S tre a m R e a d e r re ad e r =
new S t r e a m R e a d e r ( @ " с : \ s e c r e t _ p l a n . t x t " ) Передайте файл, который т р е ­
буется прочит ат ь конструктору
S t r e a m W r it e r w r i t e r = класса StreamReader.
new S t r e a m W r it e r ( @ " c : \ e m a ilT o C a p t a in A m a z in g . t x t ' ) !
С помощью класса StreamReader программа читает план r f
Жулика, а средства класса StreamWriter позволяют на- )
писать файл, который будет отправлен по электронной ^
почте супергерою Капитану Великолепному.
w r it e r . W r it e L in e ("Т о : C a p t a in A m a z in g @ o b je c t v ille .n e t " ) ;

w r it e r . W r it e L in e ( "F r o m ; C o m m is s io n e r @ o b j e c t iv ille .n e t " ) ;

w r i t e r . W r i t e L i n e ( " S T jb j e c t : Can y o u save th e d a y ... a g a in ? ");


w r it e r .W r ite L in e 0 , ^ W r i t e U ' w O зш .ш в а е у . с тр о к а -

w r i t e r . W r it e L i n e ( " W e 'v e d is c o v e r e d th e S w i n d le r 's p la n :");

w h ile ( 'r e a d e r .E n d O fS tre a m ) { С бойстба

s t r in g lin e F r o m T h e P la n = r e a d e r .R e a d L in e 0 ;
Ъелить>^
w r it e r . W r it e L in e ("T h e p la n -> " + lin e F r o m T h e P la n );

} ^ Цикл чит ает строку при п о -


\ ^ ^ м о щ и считывающего уст рой­
w r it e r . W r it e L in e 0 ; ства и записывает ее при п о ­
w r it e r .W r it e L in e ( "C a n you h e lp us?");
мощи устройства записи

w r it e r .C lo s e О ;
Hrfp
r e a d e r .C lo s e О ; To : c a p t a 1 r w u n a z in g @ o b je c tv i li e .n e t
F ro m ; coB®»1 s s 1 o n e r @ o b 3 e c t 1 v i n e .n e t
s u b j e c t : c a n y o u s a v e t h e d a y . . . a g a in ?

w e ’v e d i s c o v e r e d t h e s w i n d l e r ’ s p l a n :
Вы должны закрыть все о т ­ The p l a n - > HOW I ' l l d e f e a t c a p t a i n /M nazing ,
крытые потоки, даже если T h e D la n - > AF>oTher g e n i u s s e c r e t p l a n b y T h e s w i n d l e r _ ^ ^
T h e p l a n - > I ’ l l c r e a t e a n arm y o f c l o n e s a n d u n l e a s h th e m u p o n t h e c i t i z e n s o f o b j e c t v i l l e .
всего лишь читаете файл. T h e p l a n - > c l o n e # 0 a t t a c k s t h e m a ll
T h e p l a n - > C lo n e #1 a t t a c k s dow ntow n
T h e p l a n - > c l o n e 42 a t t a c k s t h e » a l l
/|\ T he p l a n - > c l o n e # 3 a t t a c k s d<»^ntown
T h e p l a n - > c l o n e # 4 a t t a c k s t h e m a ll
T h e p l a n - > c l o n e # 5 a t t a c k s d o w n to w
T h e p l a n - > c l o n e # 6 a t t a c k s t h e m a ll
О б ъ е к т ы S tr e a m R e a d e r
и S t r e a m W r i t e r после созда c a n you h e lp u s?

ния их экземпляров открыли


собственные потоки. Чтоды
закрыть их, мы два раза оы
зываем метод CloseQ. дальше > 403
не пересекайте потоки

Данные м огут проходить через несколько потоков

Б о л ь ш и м п р е и м у щ е с тв о м р а б о т ы с п о т о к а м и в .N E T я в л я е тс я в о з м о ж н о с т ь
п усти ть данны е через н есколько п о то ко в . О д н им из м н о го ч и с л е н н ы х т и ­
п о в д а н н ы х в .N E T я в л я е тс я класс C r y p t o S t r e a m . О н п о з в о л я е т за ш и ф ­
р о в а т ь д а н н ы е , п е р е д те м к а к п р о д е л а ть с н и м и все о с та л ь н ы е о п е р а ц и и :

При работе с классом


FileStream данные в виде
текста записываются
непосредственно в ф д й л .

^П О Н О В и

Класс C ryptoStream
наследует от аб­
страктного класса
O iv e K m C r a p t o S t « « » Stream , как и все про­
соединяется с oov чие потоковые классы.
ект ом FileStream, '^'^Фрованньїй
и создает поток за- >^екст в файл.
мифрованного тек
ста.

р Л е ї’'

Потоки можно ПЕРЕДАВАТЬ ПО 1^RTT0ЧKF


Один ноток может быть записан в другой, кото­
рый, в свою очередь, записывается в еще один
поток... концом цепочки часто является файл.
404 глава 9
чтение и запись файлов

Б б а ссей н е
Fite Edft Format View Help
нужно взять фрагменты кода Infest
из бассейна и поместить их на
пустые строчки. Любой фраг­
мент можно использовать не­
East
so u th
North
That's all folks'
a
сколько раз. В бассейне есть
и лишние фрагменты. В ре­
зультате нужно получить окно
c la ss P iz z a {
с текстом, показанное справа.
p r i v a t e ____

c la ss P in e a p p le { p u b lic P i z z a (_______________ _ J {
c o n s t _________ d = " d e l i v e r y . t x t " ; _________. w r i t e r = w riter;

p u b l i c _______ _________ }
{ N orth , S ou th , E ast, W est, F la m in g o } p u b lic v o id ..F a r g o f) {

p u b lic sta tic v o i d M a in O { w r i t e r . ___ .(f) ;


_______________ o = n e w __________________( " o r d e r . t x t " ) ; w r i t e r . ___ _ () ;

P i z z a p z = n e w P i z z a ( n e w _______________( d , tru e)) }


p z . ____________( F a r g o . F l a m i n g o ) ;
for (_______ w = 3 ; W >= 0 ; w --) {

P iz z a i = new P i z z a c la s s P arty

( n e w ________________ ( d , fa lse ) ) ; p r iv a te reader;

i . Id a h o ( (F argo)w ); reader) {

P a r t y p = new P a r ty (n e w

p . ________________ ( o ) ;

дальше > 405


серьезный диалог

^ 0 in p H u e J * e ^ c a Б б а ссей н е

.■ s s ;- S * = = -

c la ss P in ea p p le {
const String d = " d e liv e r y .tx t" ;
p u b lic enum Fargo { N orth , S ou th , E ast, W est, F la m in g o }
p u b lic sta tic v o i d M a in O {
StreamWriter o = n e w StreamWriter ( " o r d e r , t x t " ) ;
P i z z a p z = n e w P i z z a (n e w StreamWriter ( d , t r u e ) ) ;
Это точка б х о -
да программы. 3<?еси p z . Idaho ( F a r g o . F l a m i n g o ) ;
создается обьект for (int w = 3; w >= 0 ; w --) {
З Ш а т Ш и г, кот о- P i z z a i = new P i z z a (new StreamWriter ( d , fa lse ));
т й передается в класс i . Id a h o ( (F argo)w );
РаНц. Зат ем члены P a r t y p = new P a r t y (new StreamReader ( d ) ) ;
перечисления Рагдо p.HowMuch( o) ;
в цикле п е р е д а й с я \}
методу Р 1» й Л ^ а К о О o . WriteLine ( "That' s a l l fo lks !") ;
для вывода в форму- o . Close () ;
}
}

c la ss P iz z a { /^ е т о д nepe-
e файл f
p riv a te StreamWriter writer;
численмя F 4nStringQ>
p u b lic P i z z a (StreamWriter vwiter) {
this . w r i t e r = w r i t e r ;
}
p u b l i c v o i d Idaho (Pineapple. F a r g o f )
w r i t e r . WriteLine (f ) ;
w r i t e r . Close 0 ;
}
}

c la ss P arty {
в классе Party p r iv a te StreamReader r e a d e r ;
имеется поле p u b lic P a r t y (StreamReader r e a d e r ) {
Stream Reader, и this . r e a d e r = reader;
метод HowMuckQ
читает о т т у ­ }
да сгшуоки, за ­ p u b l i c v o i d HowMuch (StreamWriter q)
писывая их в поле q . WriteLine ( r e a d e r . ReadLine ( ) ) ;
Stream W riter. r e a d e r . Close () ;

406 глава 9
чтение и запись файлов

Встроенные объекты для Вызова стандартных окон диалога


П р и р а б о те с п р о гр а м м о й , ч и т а ю щ е й и з а п и с ы в а ю щ е й
в ф айлы , п е р и о д и ч е с к и тр е б ую тся всплы ваю щ и е о к н а диа­
л о га , н а п р и м е р , д л я в ы б о р а и м е н и ф айла. И м е н н о п о э т о м у
в .N E T с у щ е с тв у ю т о б ъ е к т ы , в ы п о л н я ю щ и е э ту ф у н к ц и ю .

« Dsslogs
Pointer
IS CoiofDiatog
^ FoEderBrovwerOiatog
23 FontDiaiog
Это окно
F o !d e r8 r o w se P i« io 3 '
предназначенное
Зля работы с пап
KflMW-

в .NET им еет ся набор


вст роенны х окон диа­
лога, подобных показан
ном у на рисунке окну
OpenFilePialog, о т к р ы ­
ваю щ ем у файлы.

Мы р ассм от рим эт и
Метод Shov/DialogO б укваль­
но через м ин ут у.
Д ля в ы зо в а о к н а д и а л о га вам н у ж н о :

С о зд а ть э к з е м п л я р о к н а д и а л о га . Э т о м о ж н о сделать п р и п о м о щ и
о п е р а т о р а n e w и л и п уте м п е р е т а с к и в а н и я и з T o o lb o x .

© З адайте с в о й с т в а о к н а . Н а п р и м е р , T i t l e (т е к с т в с т р о к е з а го л о в ­
к а ), I n i t i a l D i r e c t o r y (ад рес о т к р ы в а е м о й п о у м о л ч а н и ю п а п к и )
и F i l e N a m e (д ля о к о н д и а л о га O p e n и Save).

@ В ы з о в и т е м е то д S h o w D i a l o g ( ) . О н в ы з ы в а е т о к н о д и а л о га и н е воз­
в р а щ а е т з н а ч е н и е , п о к а п о л ь з о в а те л ь н е щ е л к н е т н а к н о п к е О К и л и
C a n c e l и л и д р у ги м с п о с о б о м н е з а к р о е т о к н о .

О М е т о д S h o w D i a l o g О в о з в р а щ а е т п е р е ч и с л е н и е D i a l o g R e s u l t . Е го
ч л е н ы п р и н и м а ю т з н а ч е н и я ОК (о зн а ч а е т, ч т о п о л ь з о в а те л ь щ е л кн ул
н а к н о п к е О К ) , C a n c e l , Y e s и No (д ля о к о н д и а л о га Y e s /N o ) .

дальше * 407
окна диалога — это тоже объекты

Окна диалога - это элементы .NET '^^Иевизуальный»


означает всего лишь
Ч т о б ы д о б а в и т ь ста н д а р т н о е о к н о д и ал ога W in d ow s, д о ст а т о ч н о п е р е т а ­
1^ 0, что он не по­
щ и ть н а ф о р м у э л е м е н т уп р ав л ен и я O p e n F i l e D i a l o g и з о к н а T oolb ox. О н
является на форме
после перетаскива­
компонент. Т а к н а з ы ­
п о я в л я е т с я в п р о с т р а н с т в е п о д ф о р м о й , так как э т о ния из окна Toolbox.
вается специальны й вид невизуальных элементов управления. О н и н е
п о я в л я ю т с я н а ф о р м е , н о и х м о ж н о и с п о л ь з о в а т ь как и л ю б ы е д р у г и е э л е ­
менты .

л D ialo g s

% P o in te r
fnrmi
Щ] C o lo rD ia to g

О F o ld erB ro w serD ialo g

12 F o n tD ia lo g ^

^ O o en F ile D ialo g

^ S aveF ileD ialog

Перетащенные из окна Toolbox


на форму компоненты от о­
бражаются под редактором
\\
формы.

Свойство 1п1Ь1а101геЛогу определяет'


папку, которая предлагается для Свойство Filter п о ­
открытия по умолчанию. зволяет менять
фильтры, показыва-
. . Y- y емые в нижней части
o p e n F ile D ia lo g l. I n itia lD ir e c to r y = @ "с:\M y F o ld e r \D e fa u l t \ " ; окна диалога, напри -
мер, определяющие,
o p e n F ile D ia lo g l. F ilt e r = "Text F ile s (* . t x t ) | * . t x t | " какие типы файлов
5идцт показываться.
+ " C o m m a -D elim ite d F ile s (*.c s v )|* .csv|a11 F ile s (*.*)|*.*";

o p e n F ile D ia lo g l.F ile N a m e =


" d e fa u lt_ file .tx t" ;
Эти свойства ответственны за по -
o p e n F ile D ia lo g l.C h e c k F ile E x is ts = tr u e ; ( с о о б щ е н и я о б ошибке в случаях,
f когда пользователь пытается от -
o p e n F ile D ia lo g l.C h e c k P a th E x ists = fa lse ; ^ к р ы несуществующий файл.
т ь

D ia lo g R e su lt r e su lt = o p e n F ile D ia lo g l. S h o w D ia lo g ();

if (r e su lt == D i a l o g R e s u l t . O K ) {

O p e n S o m e F ile (o p e n F ile D ia lo g l. F ile N a m e );

жал ли пользователь кнопки »проверяет, на ­


значение PialogResult.OK. Значений п >лоявляется
ст вует кнопке Cancel. ^'^logResult.Cancel соот вет -

408 глава 9
чтение и запись файлов

Окна диалога - это объекты


О б ъ е к т O p e n F i l e D i a l o g п о к а з ы в а е т с та н д а р т н о е о к н о W in d o w s O p e n , а о б ъ е к т S a v e F i l e D i a l o g -
с т а н д а р т н о е о к н о Save. И х м о ж н о о т о б р а з и т ь и создав п р и п о м о щ и о п е р а т о р а n e w э к з е м п л я р , задав
е го с в о й с т в а и вы зва в е го м е то д S h o w D i a l o g { ) . Э т о т м е то д в о з в р а щ а е т п е р е ч и с л е н и е D i a l o g R e s u l t
( п р о с т о й л о г и ч е с к о й п е р е м е н н о й в д а н н о м случае н е д о с т а т о ч н о , т а к к а к н е к о т о р ы е о к н а д и а л о га им е­
ю т б олее д вух к н о п о к ). После м рет аскивйния элем ен­
та SaveFileDialog из окна Toolbox
^ ------- ---- н д форму к методу формы
InitializeComponentO добавляется
s a v e F i l e D i a l o g l = n e w S a v e F i l e D i a l o g {) ; эта строка.
Понять смысл
s a v e F i l e D i a l o g l . I n i t i a l D i r e c t o r y = @ "c:\M y F o ld e r \D e fa u l t \ " ;
■n свойства Filter
I несложно. Срав-
s a v e F ile D ia lo g l.F ilt e r = "Text F ile s (*.t x t ) | * .t x t | " \ H u m e написанное
, ? между символа­
+ " C o m m a -D elim ite d F i l e s (*. c s v ) [* .csv |A ll F ile s (*.*)!*.
..... с т ем , что
ми
показывается в
D ia lo g R e s u lt r e s u l t = s a v e F il e D i a lo g l . S h o w D ia lo g (); раскрывающемся
списке внизу окна.
if (r e su lt == D i a l o g R e s u l t .O K ) {
Метод ShowDialog Q и свой-
S a v e T h e F i l e ( s a v e F i l e D i a l o g l . F ile N a m e ) стьо FileName им ею т те
же функции, что и объект
OpenFileDialog.
}
Объект SaveFileDialog
соот вет ст вует
стандартному окну
диалога Save as...

<9SeveAs
, * UbrBries ► Documents ► 1 11 Sesfxh
» 4^ p
Свойство Title
меняет т екст 1=.; Р едакт и рован и е
раскрывающегося
в строке за ­ Favorites Afrsiigeby; FoWet с п и с к а Save as type
головка. ■ Desktop Irscfudes: 2 iocatfons
осущ ест вляет ся при
Downloads Name Date morirfted Type
^ Recent Places
пом ощ и свойст ва
Sterdock ЗЛг/ЖЗ 7tS?AM Fitefoide Filter.
^ Visual Studio 2010 зттш ^11 PM' Fifefoidei

Метод ^ f p y y m доступа к выбран­


ShowDialogO вы­ ному пользователем фаи-
зывает окно диа -
лога, открытое S T “™*'’?““*
на папке, задан­
ной свойством - м в м т , м еди.
Ш 1 а 1 0 1 'ге ^ о гу кн о п ку Н йж ал
пользователь-

дальше ► 409
справочная система

Встроенные классы File и Directory


П о д о б н о классу S t r e a m W r i t e r , класс F i l e создает п о т о к и , п о з в о л я ю щ и е р а б о та ть с ф а й л а м и в ф о­
н о в о м р е ж и м е . М е т о д ы э т о г о класса д а ю т в о з м о ж н о с т ь в ы п о л н я т ь б о л ь ш и н с т в о о п е р а ц и й без п р е д ­
в а р и т е л ь н о го с о з д а н и я о б ъ е к та F i l e S t r e a m . О б ъ е к т D i r e c t o r y п р е д н а з н а ч е н д ля р а б о т ы с п а п к а м и .

Класе File позволяет:

О Проверять, сущ ествует л и дю йл


М е т о д E x i s t s О в о з в р а щ а е т з н а ч е н и е tr u e
п р и н а л и ч и и у к а з а н н о го ва м и ф айл а и false —
в п р о т и в н о м случае.
При болшц>< объемах работы
О Читать из ф ай л а и записывать в него
I iS pT T создать
М е т о д O p e n R e a d O дает д о с т у п к ч т е н и ю экземпляр класса FUelnfo вместо
ф айла, а м е то д ы C r e a t e () и O p e n W r it e () —
к з а п и с и в ф айл. 1 °лТ«
О Добавлять в (райл текст
Класс РПеЫо отличается от клас-
File т ем, что для работы вам
М е т о д A p p e n d A l l T e x t () д о б а в л я е т т е к с т
•потребуется его экземпляр.
в с у щ е с т в у ю щ и й ф айл , и создает ф айл , е сли
о н о т с у т с т в у е т н а м о м е н т запуска м етода. К ^ м е того, класс File быстрее р а ~
небольшом количестве
О П о л у ч а е т инф ор м ац и ю о ф айле операции с в то время как
М е т о д ы G e t L a s t A c c e s s T im e о и G e t L a s t - F,telnfo лучше
W r i t e T im e O в о з в р а щ а ю т вр е м я и дату многочисленных операций.
п о с л е д н е го д о с т у п а и п о с л е д н е го редакти­
р о в а н и я ф айла.

Класс File является


Класс Directory позболяет: статическим, то есть
представляет содой на­
О Создать новую п а п ку бор методов для радо
те>1 с файлами. После
П р и р а б о те с м е то д о м C r e a t e D i r e c t o r y () вам
создания экземпляра
тр е б у е тс я ука за ть м а р ш р у т д о с т у п а к п а п к е . Filelnfo вы получаете

о П о л учи ть сп и со к ц)айлов в папке


М е т о д G e t F i l e s () создает м а сси в ф а й л о в , с о д е р ж а щ и х с я
доступ к тому же спи
ску методов, что и при
работе с классом File.
в п а п ке . Вам н у ж н о т о л ь к о у ка за ть м а р ш р у т д о с т у п а к н е й .

О Удалить пап ку
Э та н е с л о ж н а я п р о ц е д у р а в ы п о л н я е т с я м е то д о м D e l e t e ()

410 глава 9
4acm°
чтение и запись файлов
за д а в а ем ы е
B o Ilj^ o C b i

И все-таки зачем нужны символы {0} и {1} в объявлении ^ ! Наверное, вы уже слышали, что файлы на диске представ­
объекта StreamWriter? лены в виде битов и байтов. Другими словами, при записи фай­
ла на жесткий диск операционная система воспринимает его как
набор байтов. Объекты S t r e a m R e a d e r и S t r e a m W r i t e r
5 При вводе строк в файл иногда возникает необходимость
преобразуют эти байты в понятные вам символы, то есть вы­
ввести туда и содержимое многочисленных переменных, к при­
полняют кодирование и раскодирование. Помните, в главе 4
меру, можно написать:
упоминалось, что переменная типа b y t e хранит значения от
+ name +
О до 255? Все файлы на жестком диске представляют собой
w r i t e r . W r i t e L i n e ("Меня з о в у т
длинные последовательности чисел из этого диапазона. При от­
"мне " + а д е + " лет.");
крытии файла, скажем, в приложении Блокнот, каждый байт пре­
образуется в символ: например, Е соответствует 69, а а — 97
Но комбинировать строки таким образом долго, кроме того, воз­
(впрочем, все зависит от кодировки... но мы поговорим об этом
растает вероятность опечаток. Намного проще писать код:
чуть позже). Соответственно, при сохранении введенного вами
в Блокнот текста символы преобразуются обратно в байты. Это
w r ite r .W r ite L in e (
преобразование нужно и для записи переменной типа s t r i n g
"Меня з о в у т { о } м н е {1} лет",
в поток.
nam e, a g e);

Такой вариант легче читается, особенно, когда в одной строке Разве для записи в файл недостаточно объекта
оказывается много переменных. treamWriter? Зачем создавать объект FileStream?

I Зачем нужен знак (§ перед именем файла? ! Для записи строк в текстовый файл и их дальнейшего
чтения действительно достаточно объектов S t r e a m R e a d e r
и S t r e a m W r i t e r . Но для более сложных операций требуется
^ ; При добавлении в программу строковых констант компиля­
задействовать другие потоки. Записывать в файл числа, масси­
тор преобразует езс-последовательности ( \ п или \ г ) в специ­
вы, коллекции и объекты S t r e a m W r i t e r не умеет. Впрочем,
альные символы. Но косая черта присутствует и в маршрутах
эта тема будет подробно рассмотрена чуть позже.
доступа к файлам. Поместив в начало строки знак вы говорите
С#, что в строке отсутствуют езс-последовательности. Кроме
того, в нее начинают включаться знаки переноса (то есть нажа­ ; Как мне создать собственные диалоговые окна?
тия клавиши Enter фиксируются автоматически): Б

str in g tw oL in e = @ "это с т р о к а ,
5 Вы можете добавить в проект форму и придать ей нужный
за н и м а ю щ а я д в е с т р о ч к и ." ;
вид. Затем при помощи оператора n e w создается ее экземпляр
(именно так вы поступали с объектом O p e n F i l e D i a l o g ) . По­
сле чего остается вызвать ее метод S h o w D i a l o g (), и новое
Напомните еще раз, что означают символы \ п и \ t .
окно диалога готово. Подробно этот процесс будет рассмотрен
в главе 13.
I I; Это так называемые езс-последовательности. \ п — пере­
нос строки, \ t — табуляция, \ г — символ возврата. В тексто­
вых файлах Windows строки должны заканчиваться символами ; Зачем закрывать потоки после окончания работы?
Б
\ г \ п (об этом мы говорили в главе 8 при знакомстве с констан­
той E n v i r o n m e n t . N e w L i n e ). Чтобы использовать в строке ! Сообщал ли вам когда-нибудь текстовый редактор, что он
обратную косую черту, которую компилятор не воспринимает как не может открыть файл, потому что «файл используется другим
езс-последовательность, делайте ее двойной: \ \ . приложением»? Windows блокирует открытые файлы и не позво­
ляет открывать в других приложениях. Именно это происходит
А что там говорилось про преобразование строки в мас­ с вашей программой при открытии файла. Файл остается забло­
сив байтов? Как это работает? кированным, пока вы не воспользуетесь методом C l o s e ( ) .

дальше ► 411
напиш ит е эт о с а м о с т о я т е л ь н о

возьми в руку карандаш


Для работы с файлами и папками в .NET существуют два встроенных класса с многочис­
ленными статическими методами. Класс F i l e содержит методы работы с файлами, а класс
D i r e c t o r y дает возможность работать с папками. Напишите, как, с вашей точки зрения, ра­
ботают представленные слева строки кода.

Код Его ф ункция


if (!D ir e c to r y .E x ists(@ " c :\S Y P " )) {
D i r e c t o r y . C r e a t e D i r e c t o r y ( @ " c : \S Y P " ) ;
}
if (D ir e c to r y .E x ists(@ " c :\S Y P \B o n k " )) {
D i r e c t o r y .D e le t e ( @ " c :\S Y P \B o n k " );
}
D i r e c t o r y . C r e a te D ir e c to r y (@ " c :\S Y P \B o n k " );

D ir e c t o r y .S e t C r e a t i o n T i m e ( @ " c :\S Y P \B on k " ,


new D a t e T im e (1 9 7 6 , 09, 25));

s t r i n g [] file s = D ir e c to r y .G e tF ile s(@ " c :\w in d o w s\" ,


" * .lo g " , S e a r c h O p tio n .A llD ir e c to r ie s );

F i l e .W r ite A llT e x t(@ " c :\S Y P \B o n k \w e ir d o .t x t " ,


@"Это первая строчка
a это вторая строчка
а это последняя строчка");

F i l e . E n c r y p t (@"с: \S Y P \B o n k \w e ir d o . t x t " ) ;
к Это вы еще не проходили...
с м о ж е т е ли вы угадать
назначение этого метода?
F i l e . С о р у (@"с: \S Y P \B o n k \w e ir d o . t x t " ,
© " c :\S Y P \c o p y .tx t" );

D a t e T i m e m y T im e =
D ir e c t o r y .G e tC r e a tio n T im e (@ " c :\S Y P \B o n k " );

F ile .S e tL a s tW r ite T im e (@ " c :\S Y P \c o p y .tx t" , m yT im e);

F i l e .D e le te (@ " c : \S Y P \B o n k \w e ir d o .t x t " );

412 глава 9
чтение и запись файлов

Чтобы развернуть текстовое поле


на всю форму, перетащите на нее
Откры тие и сохранение файлоб при помощи элемент T a b l e L a y o u t P a n e l из окна
C o n t a i n e r s , присвойте свойству Dock
окон диалога значение F i l l , а при помощи редактора
свойств Rows и C o lu n m s создайте две
П о с т р о и м п р о гр а м м у , о т к р ы в а ю щ у ю т е к с т о в ы й ф айл. строки и один столбец, в верхнюю ячей­
О н а д о л ж н а п о з в о л я т ь р е д а к т и р о в а т ь ф а йл и с о х р а ­ ку перетащите элемент T e x t B o x , в ниж­
н я ть сделанные изм енения п р и п о м о щ и ста н д а р тн ы х нюю — элемент F l o w L a y o u t P a n e l .
э л е м е н то в у п р а в л е н и я .N E T. Свойству D o c k присвойте значение
F i l l , а свойству F l o w D i r e C t i o n —
^ ^ ------- У п р а ж н е н и е ! значение R i g h t T o L e f t и перетащите
Создание простои цюрмы . туда две кнопки. Размер верхнего ряда
элемента T a b l e L a y o u t P a n e l задайте
Вам п о т р е б у е т с я т е к с т о в о е н о л е и две к н о п
равным 100%, а размер нижнего ряда
ки. П еретащ ите н а ф о р м у т а к ж е э л е м е н ты поменяйте таким образом, чтобы там
O p e n F i l e D i a l o g и S a v e F i l e D i a l o g . Д войны м поместились две кнопки.
щ е л ч к о м н а к н о п к а х со зд а й те о б р а б о т ч и к и с о б ы ­
ти й и д о б а в ь те з а к р ы т о е с т р о к о в о е п о л е nam e.
Д об авьте о п е р а т о р u s i n g для S y s t e m . 1 0 .
jC Sid^Jle Tent Editor да

о П ривязка кнопки open к элем енту openFileDialog.


К н о п к а O p e n отоб раж ает объ ект O p e n F i l e D i a l o g и использует Это текстовое поле,
свойство Multiline ко­
м е тод F i l e . R e a d A l l T e x t () для ч т е н и я ф айл а в т е к с т о в о м п о л е :
торого имеет значе­
p r i v a t e v o i d o p e n _ C lic k ( o b j e c t se n d e r , E ven tA rgs e) {
ние true.
i f ( o p e n F i l e D i a l o g l . S h o w D i a l o g 0 == D i a l o g R e s u l t . OK) {
nam e = o p e n F i l e D i a l o g l . F i l e N a m e ;
Save.
te x tB o x l.C le a r ();
t e x tB o x l.T e x t = F ile .R e a d A llT e x t(n a m e );
Щелчок на кнопке Open
}
оелает видимым элемент
}
управления OpenFileDialog.
о Д ействия для кнопки Save.
1Снопка Save исп о л ьзуе т для сохранения ф айла м етод F ile .
(М е т о д ы R e a c tA llT e x tQ
и WriteAllTextO являются
W r ite A llT e x t О : частью класса File- долее
подробно мы поговорим
p r i v a t e v o i d s a v e _ C l i c k ( o b j e c t s e n d e r , E ven tA rgs e) { о них через несколько
i f ( s a v e F i l e D i a l o g l . S h o w D i a l o g 0 == D i a l o g R e s u l t . O K ) . страниц,.
nam e = s a v e F i l e D i a l o g l . F i l e N a m e ;
F ile .W r ite A llT e x t(n a m e , t e x t B o x l . T e x t );
}
}
Если не задать это свой­
ство. раскрывающийся
Д р у ги е свойства окна д и а л о га . список в нижней части окон
диалога Open и Save будет
★ П о м е н я й т е т е к с т в с т р о к е з а го л о в к а с п о м о щ ь ю с в о й с т в а пустым. В данном случае
T i t l e о б ъ е к та S a v e F i l e D i a l o g . используйте фильтр: Text
Files (*.txt)
★ У к а ж и т е о тк р ы в а е м у ю п о у м о л ч а н и ю п а п к у с п о м о щ ь ю
свойства i n i t i a l F o l d e r .

★ У к а ж и т е , ч т о о б ъ е к т O p e n F i l e D i a l o g д о л ж е н п о к а з ы в а ть
то л ько те ксто вы е ф айлы , с пом о щ ью свойства F i l t e r .

дальше ^ 413
бросайте мусор в нужную корзину

^ з ь м и в руку карандаш Вот какие действия производят строки кода.

Решение

Код Функция
if (! D i r e c t o r y . E x i s t s {@"с : \ S Y P " )) { проверяет существование папки C:\SYP
D i r e c t o r y . C r e a t e D i r e c t o r y ( @ " с :\ S Y P " ) ; и создает ее, если она от сут ст вует .
}
if (D ir e c to r y .E x ists(@ " c :\S Y P \B o n k " )) { Проверяет существование папки С:\
D i r e c t o r y . D e l e t e (@"C : \ S Y P \ B o n k " ); SYP\Bonk. Если папка сущ ест вует , она
} удаляется.
D i r e c t o r y . C r e a t e D i r e c t o r y ( @ " c : \ S Y P \ B o n k " );
Создает папку C:\SYP\Bonk.

D i r e c t o r y . S e t C r e a t i o n T i m e ( @ " c :\ S Y P \ B o n k " ,
Задает время создания папки C:\SYP\
new D a t e T im e (1 9 7 6 , 09, 2 5 )); Bonk, Z S сентября 1 9 7 6 .

str in g [] file s = D i r e c t o r y . G e t F i l e s ( @ " c :\ w i n d o w s \ " ,


Получает список всех файлов
" * .lo g " , S e a r c h O p tio n .A llD ir e c to r ie s ); с расширением *.1од в папке C:\WindowSj
включая файлы в подпапках.

F i l e . W r i t e A l l T e x t (@"с : \ S Y P \ B o n k \ w e i r d o . t x t " ,
Создает файл w eirdo.txt (если он
@"Это п е р в а я с т р о ч к а еще не создан) в папке C:\SYP\Bonk
a эт о вторая строчка и записывает в него три строки
а это последняя с т р о ч к а " ); текста.
F i l e . E n c r y p t (@"с : \ S Y P \ B o n k \ w e i r d o . t x t " ); Зашифровывает файл weirdo.txt
' Э то альтернативный способ исполь -■ при помощи встроенной системы
зования обьекта CryptoStream. шифрования Windows.

F i l e . C o p y (@ "с: \ S Y P \ B o n k \ w e i r d o . t x t " ,
К опирует содержимое файла C:\SYP\
© " C :\S Y P \c o p y .tx t" ); B onk\w eirdo.txt в файл C:\SYP\Copy.txt.

D a t e T i m e m y T im e =
Объявляет переменную m yTim e и п р и ­
D ir e c t o r y .G e t C r e a t io n T im e ( @ " c : \S Y P \B o n k " ) ; сваивает ей время создания п а п к и С:\
SYP\Bonk.

F ile .S e t L a s t W r it e T im e (© " c :\S Y P \c o p y .tx t" , m yT im e); Меняет последнее время записи файла
copy.txt в папке C:\SYP\, присваивая
ему значение переменной myTime.

F ile .D e le t e ( @ " c : \S Y P \B o n k \w eird o .t x t " ) ;


Удаляет файл C:\SYP\Bonk\weirdo.txt.

414 глава 9
чтение и запись файлов

интерфейс IDisposable
М н о ж е с т в о кл а ссо в .N E T р е а л и зуе т к р а й н е п о л е з н ы й и н т е р ­
ф ейс I D i s p o s a b l e . О н с о д е р ж и т в с е г о о д и н м е то д Dispose О .
При объявлении
Э т о т и н т е р ф е й с о б ъ я с н я е т п р о гр а м м е , ч т о е сть в а ж н ы е в е щ и ,
к о т о р ы е тр е б у е тс я сделать для з а в е р ш е н и я р а б о т ы . В едь р а с ­
о^ектов в разделе
п р е д е л е н н ы е р е с у р с ы н е о с в о б о ж д а ю т с я с а м о с то я те л ь н о .
Д ля э т о г о и м тр е б у е тс я м е то д D i s p o s e ( ) .
using вызов метода
В о сп о л ь зуе м ся ф у н к ц и е й И С Р G o То D e fin itio n для п р о с м о т р а
Dispose () таких объ­
о п р е д е л е н и я и н т е р ф е й с а I D i s p o s a b l e . В в е д и те « ID isp o sa b le »
в п р о и з в о л ь н о м м есте в н у т р и класса, щ е л к н и т е н а э т о й с т р о ч ­
ектов будет осущест­
ке п р а в о й к н о п к о й м ы ш и и в ы б р и т е в м е н ю к о м а н д у G o То
D e fin itio n . О т к р о е т с я вклад ка с ко д о м . В о т ч т о в ы у в и д и те :
вляться автоматически.
Многие классы распределяют между со­
бой такие важные ресурсы как память,
namespace System файлы и другие объекты. Они соединя- ^
ются с эт ими ресурсами и не разрыва­
{ ю т связь, пока не получат сигнал, что
работа закончена.
// Краткое описание:
II Дает метод освобождения распределенных р е с у р с о в .

public interface IDisposable

{
// Краткое описание:
// Выполняет определяемые приложением задачи,

// связанные с освобождением или сбросом

/ / неуправляемых ресурсов

void Dispose О;
/\юбой класс, реализуюш,ий интерфейс рас-пре-де-лять, гл.
IDisposable, немедленно освобождает любые
задействованные ресурсы после вызова его м е ­
раздавать ресурсы или
тода DisposeQ. Это последнее, что делается обязанности для опре­
перед заверилением работы с объектом.
деленных целей. Управ­
ляющий р а с п р е д е л и л все
конференц-залы под беспо­
лезный семинар по менедж­
менту.

дальше > 415


мне снова нужно к вет е р и н а р у

Операторы using Ьк средство избе)кать системных ошибок


Н а п р о т я ж е н и и гл а вы вам т в е р д и л и о н е о б х о д и м о с т и з а к р ы в а т ь п о ­
т о к и . В едь и м е н н о с э т и м связана о д н а и з с а м ы х р а с п р о с т р а н е н н ы х
В данном случае речь идет
Ш ее не о т ех операторах
о ш и б о к . К с ч а с т ь ю , в C # и м е е тс я з а м е ч а т е л ь н ы й и н с т р у м е н т , п о з в о ­ using, которые распола ­
л я ю щ и й избеж ать п о д о б н о й си туа ц ии — это и н те р ф е йс I D is p o s a b le гаются в верхней части
с е го м ето д о м D is p o s e ( ) . Е с л и п о м е с т и т ь к о д п о т о к а в о п е р а т о р кот.
u s i n g , п о т о к н а ч н е т з а к р ы в а ть с я а в т о м а т и ч е с к и . Вам н у ж н о т о л ь к о
о б ъ я в и т ь п о т о к о в у ю с с ы л к у с э т и м о п е р а т о р о м , п о м е с т и в следом в
ф и г у р н ы х с к о б к а х и с п о л ь з у ю щ и й э т у с с ы л к у ко д. О п е р а т о р u s i n g п о ­
сле з а в е р ш е н и я р а б о т ы с э т и м к о д о м будет а в т о м а т и ч е с к и в ы з ы в а т ь
м е т о д D i s p o s e ( ) п о т о к а . В о т к а к э т о р а б о та е т; ■М блок кода в ф и ­
операт ором using всегда гурных скобках.
о б е л е н и е объекта ■

using (StreamWriter sw = new StreamWriter("secret_plan.txt")) {

sw.WriteLine("Как победить Капитана Великолепного");


Эти операт о-
sw.WriteLine ("Еще один гениальный секретный план"); Р^'(используют
о Л е к т , со зд а н ^
sw.WriteLine("от Жулика")
usinfi Как и лю~
бой ругой.
} После завершения работы
оператора using, в з ы в а е т ­ в данном случае на п .
ся мет од DisposeQ
Disao'ip.n использи- « к т указывает объ~
'^од
емого

Все потоки имеют закрывающий метод D i s p o s e ().


При этом поток, объявленный внутри оператора
using, всегда закрывает себя сам!
Потоки ВСЕГДА
По одному оператору using на объект
О п е р а т о р ы u s i n g м о ж н о п о м е щ а т ь д р у г за д р у го м , п р и э т о м
следует т я в -
вам н е п о тр е б у е тс я д о п о л н и т е л ь н ы й н а б о р ф и г у р н ы х с к о б о к . лять внутри опе­
u s in g

u sin g
(Stream R ead er r e a d e r

(S trea m W riter w r i t e r
= new S tr e a m R e a d e r { " s e c r e t _ p l a n . t x t " ))

= new S t r e a m R e a d e r ( " e m a i l . t x t " ))


ратора using. Это
{
гарантирует, что
// операторы, использующие устройства чтения и записи после заверше­
ния работы они
u s in g закроет его автоматически. будут закрыты!
416 глава 9
чтение и запись файлов

Проблемы на работе
П е р е д в а м и Б р а й а н . О н р а з р а б о т ч и к п р о гр а м м н а С # и л ю б и т с в о ю работу, н о обожает у с т р а и в а т ь н е ­
з а п л а н и р о в а н н ы е в ы х о д н ы е . Е го н а ч а л ь н и к н е н а в и д и т та к и е с и т у а ц и и , п о э т о м у Б р а й а н у п р и х о д и т с я
придум ы вать веские п р и ч и н ы . ^ —

Сынок, с марта ты
О
отпрашиваешься к
ветеринару уже девятый раз.
Не поискать ли тебе новую
работу?

Создадим программу учета оправданий Брайана со


рАОеМ
И с п о л ь з у й т е с в о и з н а н и я о ч т е н и и ф а й л о в и з а п и с и в н и х для со з д а н и я V
п р о гр а м м ы п о п о и с к у п о д х о д я щ е го о п р а в д а н и я . Б р а й а н у н у ж н о о тс л е ­
ж и в а т ь , к а к и е п р и ч и н ы у х о д а с р а б о т ы о н и с п о л ь з о в а л в п о сл е д н е е
вр е м я , и к а к н а н и х о т р е а ги р о в а л е го н а ч а л ь н и к .
ео6> -

Excuse Manager
Брайан хочет
Иногда Брайану лень
выдумывать. Поэто­
хранить пере- Bccuse My dog has a headachae
м у добавим кнопку
чень причин ухода
с работы в одном FtemAs Didnt woik. Boss knows I dwit have a dog Random, которая
будет случайным
м ест е, поэтому Last Wednesday, Mardi 04. Ж Ш Q -»
выделим для него образом выбирать
3 ^ 1 /2 )1 0 12;®:51 PM причину прогула.
Ы кпку- Wedaie

f^ n k m

Папка содержит текстовые


файлы, в каждом из кот о­ ID:- 3
рых записано одно оправда­
ние. Щелчок на кнопке Save
сохраняет текущее объяс­
нение Брайана в такой файл.
Чтобы его открыт ь, ис­
пользуйт е кнопку Open.

дальше * 417
уваж ительные причины д ля брайана

Постройте для Брайана программу, выбирающую оправдание. E xcuse


ажнение Description: string
Results: string
Создание срормы LastUsed: DateTime
ExcusePath: string
В ам тр е б у е тс я ф о р м а со с л е д у ю щ и м и о т л и ч и т е л ь н ы м и п р и з н а к а м и :
★ П р и п е р в о й за гр узке д о л ж н а б ы т ь д о с т у п н а т о л ь к о к н о п к а F o ld e r . OpenFile(string)
Save(string)
★ П р и о т к р ы т и и и с о х р а н е н и и п р и ч и н ы п р и п о м о щ и элем ента L a b e l
ото б р а ж а е тся текущ ая дата. С в о й с тв о A u t o S i z e э т о го .элемента им еет
зн а че н и е F a l s e , а с в о й с тв о B o r d e r S t y l e — зн а че н и е F i x e d 3 D .
★ П о с л е с о х р а н е н и я о п р а в д а н и я п о я в л я е т с я о к н о E xcuse W ritte n .
★ К н о п к а F o ld e r о т к р ы в а е т о к н о д и а л о га для п р о с м о т р а п а п о к . П о с л е
в ы б о р а п а п к и п о я в л я ю т с я к н о п к и Save, O p e n и R a n d o m E xcuse. Дважды щелк-
нув на пере-
★ Е с л и п о л ь з о в а те л ь и з м е н и л л ю б о е и з т р е х п о л е й , в с т р о к у з а го л о в к а
>тащенном на
р я д о м с н а д п и с ь ю «E xcuse M a n a g e r» д о б а в л я е тс я зв е зд о чка ( * ) . О н а форму т ек ­
и с ч е з а е т п о с л е с о х р а н е н и я д а н н ы х и л и о т к р ы т и я н о в о г о ф айла. стовом поле,
★ Ф о р м а сл е д и т за т е к у щ е й п а п к о й и за те м , б ы л о л и с о х р а н е н о те к у ­ вы создадите
щ ее о п р а в д а н и е . Ч т о б ы с л е д и ть за п р о ц е с с о м с о х р а н е н и я , исполь­ для него обра­
зуйте обработчики событий Changed для т р е х э л е м е н то в ввода.
ботчик соды-

e Создание класса Excuse и хранение е го экзем пляра в qjopMe


_тий Changed.

Д о б а в и м в ф о р м у п о л е C u r r e n t E x c u s e для о т о б р а ж е н и я в ы б р а н н о г о о п р а в д а н и я . Вам п о ­
т р е б у ю т с я т р и п е р е г р у ж е н н ы х к о н с т р у к т о р а : для з а гр у з к и ф о р м ы , для о т к р ы т и я ф айла и
для с л у ч а й н о го о п р а в д а н и я . Д о б а вьте м е то д ы O p e n F i l e () и S a v e () для о т к р ы т и я и с о х р а ­
н е н и я о п р а в д а н и я . А затем ещ е и м е то д U p d a t e F o r m ( ) , о б н о в л я ю щ и й э л е м е н ты у п р а в л е ­
н и я (а в о т п о л е з н ы е п о д с к а з к и ) : Этот парам ет р определяет,
вносились ли изменения в ф ор­
p r i v a t e v o id U p dateF orm (bool changed)
му. Вам потребуется поле
i f (! ( ! changed) { '' ^
Помните, что для его хранения.
t h is .d e s c r ip t io n .T e x t = c u r re n tE x c u se.D e scr ip tio n ;
символ ! озна­ t h i s . r e s u l t s . T ext = c u r r e n tE x c u se .R e su lts,•
чает Н Е Т — то t h is j la s t u s e d .V a lu e = c u r re n tE x c u se .L astU sed ;
ест ь здесь п р о ­ i f ^ i g t r i n g . I s N u l l O r E m p t y ( c u r r e n t E x c u s e . E x c u s e P a t h ) )
веряется, не яв­ _ y T i r e D a t e . T e x t = F i l e . G e t L a s t W r i t e T i m e ( c u r r e n t E x c u s e . E x c u s e P a t h ) . T o S t r i n g O ;
ляется ли марил
у- , t h i s . T e x t = " E x c u s e M a n a g e r " ; <5 ..^ А ва ж д ы щ е л к н и т е на э л е м е н т а х в в о д а для
г о п іу а в - с о зд а н и я о б р а б о т ч и к а с о б ы т и й C h a n g e d . Т р и
обработ чи ка собы т ий сначала б уд ут м ен я т ь
(^дниеМ п у с t h i s . T e x t = " E x c u s e M a n a g e r * " ; ( ^ э к з е м п л я р E x c u se , а з а т е м в ы з о в у т м е т о д
или со зн ач е ^ h i s . form C han ged = c h a n g e d ; U p d a t e F o r m f tr u e ) , дальи ле с п о с о б и з м е н е н и я п о -
^ u ll- } ф о р м ы в ы б и р а е т е т о л ь к о вы.
П р и с в о й т е н а ч а л ь н о е з н а ч е н и е п а р а м е тр у L a s t U s e d в к о н с т р у к т о р е ф о р м ы ;
p u b lic F o rm lО {
In itia liz e C o m p o n e n t();
c u r r e n tE x c u s e . L astU sed = la stU s e d .V a lu e ;

© К н о п ка Folder открывает програм м у просмотра п ап о к


Щ е л ч о к н а к н о п к е F o ld e r д о л ж е н о т к р ы в а т ь о к н о д и а л о га B ro w se f o r F o ld e r. Ф о р м е
п о т р е б у е т с я п о л е для х р а н е н и я и н ф о р м а ц и и о п а п к е . П р и первом вызове ф о р м ы
к н о п к и Save, O p e n и R a n d o m E xcuse отключены, н о к н о п к а F o ld e r в к л ю ч а е т и х п о ­
сле в ы б о р а п о л ь з о в а те л е м п а п к и .

418 глава 9
чтение и запись файлов

Q Кнопка S ave сохраняет выбранное оправдание в qэaйл


Щ е л ч о к н а к н о п к е Save д о л ж е н в ы з ы в а ть о к н о д и а л о га Save As.

★ К аж дая з а п ись с о х р а н я е тс я в о тд е л ь н ы й т е к с т о в ы й ф айл. В п е р в о й с т р о ч к е ф и гу р и р у е т


оп ра в д а н и е , в о в т о р о й — р езультат, а в т р е т ь е й — дата п о с л е д н е го и с п о л ь з о в а н и я (о п р е ­
деляется м етод ом T o S t r i n g () о б ъ е к т а D a t e T i m e P i c k e r ) . К ласс E x c u s e д о л ж е н и м е ть
м е то д S a v e ( ) для с о х р а н е н и я о п р а в д а н и й в ф айл.
★ О к н о д и а л о га Save As д о л ж н о о т к р ы в а т ь папку, в ы б р а н н у ю п о л ь зо в а те л е м п р и п о м о ­
щ и к н о п к и F o ld e r. В к а ч е с тв е и м е н и ф айл а ф и г у р и р у е т н а з в а н и е о п р а в д а н и я с рас­
ш и р е н и е м .tx t.
★ В о к н е д и а л о га д о л ж н ы б ы т ь ф и л ь т р ы : т е к с т о в ы е ф а й л ы ( * .tx t ) и все ф а й л ы ( * .* ) .
★ П о п ы тк и сохранения п р и незаполненны х полях долж ны привод ить к появлению вот
т а к о г о о к н а д и а л о га :
Unable to save

Please specify an eccuse arvd a resutt

парамет р MessageBoxlcon.

О Кнопка Open откры вает сохраненны е оправдания


Щ е л ч о к н а к н о п к е O p e n д о л ж е н в ы з ы в а ть о д н о и м е н н о е о к н о д и а л о га .

★ В о к н е д и а л о га O p e n п о у м о л ч а н и ю д о л ж н а б ы т ь о т к р ы т а п а п к а , в ы б р а н н а я п о л ь з о в а ­
те л е м п р и п о м о щ и к н о п к и F o ld e r.
★ Д об а вьте м е то д O p e n () в класс E x c u s e .

★ М е т о д C o n v e r t . T o D a t e T i m e О з а гр у ж а е т с о х р а н е н н ы е д а н н ы е в э л е м е н т у п р а в л е ­
н и я D a t e T i m e P i c k e r , в ы б и р а ю щ и й врем я.

★ П о п ы т к а о т к р ы т ь н о в о е о п р а в д а н и е , н е с о х р а н и в п р е д ы д ущ е е , д о л ж н а п р и в о д и т ь
к п о я в л е н и ю в о т т а к о г о о к н а д и а л о га :
Д ля вызова подобных окон диалога
используйте перегруженный метод
MessageBox.ShowO, позволяющий за -
аавате> парам ет р MessageBoxButtons.
YesA/c?. При щелчке пользователя на
1^рпке No мет од ShowO возвращает
PialogResult.No. ^

О Н у , и , наконец, пусть кнопка Random Excuse загруж ает случайное оправдание


П р и щ е л ч ке н а к н о п к е R a n d o m E xcuse д о л ж е н с л у ч а й н ы м о б р а з о м о т к р ы в а т ь с я ф айл и з п а п ­
к и с оправданиям и.

★ О б ъ е к т R a n d o m н у ж н о будет с о х р а н и т ь в п о л е и п е р е д а ть о д н о м у и з п е р е гр у ж е н н ы х
к о н с т р у к т о р о в о б ъ е к та E x c u s e .

★ Е сл и о т к р ы т о е в д а н н ы й м о м е н т о п р а в д а н и е н е с о х р а н е н о , д о л ж н о п о я в л я т ь с я п о ­
ка з а н н о е в ы ш е о к н о д и а л о га с п р е д у п р е ж д е н и е м .

дальше ► 419
решение упражнения

Вот как выглядит код для поиска оправданий отсутствия на работе, который мы написали
ненке для Брайана.
ешение форма использует поля для сохранения
p r i v a t e E x c u s e c u r r e n t E x c u s e = n e w E x c u s e () текущего объекта Excuse о о ы д р а н н у ю
p r iv a te s tr in g se lec ted F o ld e r = i / папку, a также, запоминает, оыл ли из
p r i v a t e b o o l form C han ged = f a l s e ; менен эт от объект. К-роме того, она
R andom r a n d o m = n e w R a n d o m ( ) ; ( сохраняет объект Random для кнопки
Random Excuse.
p r i v a t e v o i d folder_Click( o b j e c t s e n d e r , E v e n t A r g s e ) {
fo ld e r B r o w s e r D ia lo g l.S e le c te d P a th = s e le c te d F o ld e r ; выбора ПОАЬЗО-
D i a l o g R e s u l t r e s u l t = f o l d e r B r o w s e r D i a l o g l . S h o w D i a l o g ( ) ; "Л ПоСЛе OWOop«
i f ( r e s u l t == D i a l o g R e s u l t .OK) { / бйИлелеМ ^
s e le c te d F o ld e r = fo ld e r B r o w s e r D ia lo g l.S e le c te d P a th ; сохр^няе
s a v e . E n ab led = t r u e ; ( %кл\ОЧает v
___ ) m e КН01А.КИ.
o p e n .E n a b le d = tr u e ;
ran d om E xcu se. E n ab led = t r u e ; Это символ оператора ИЛИ, выражение и м е­
} ет значение true (если не указано оправдание)
ИЛИ результ ат .
p r iv a te v o id sa v e _ C lic k (o b je c t sen d er, E v e n t A r g s e j /{
i f ( S t r i n g .I s N u l lO r E m p t y ( d e s c r i p t io n .T e x t ) m i t r i n g . IsN u llO rE m p ty ( r e s u l t s . T e x t ) ) {
M es sa g e B o x .S h o w (" P le a s e s p e c i f y an e x c u g ^ a n d a r e s u l t " ,
" U n a b l e t o s a v e " , M e s s a g e B o x B u t t o n s . OK, M e s s a g e B o x I c o n . E x c l a m a t i o n ) ;
retu rn ;
} Здесь задаются ф и л ь -
s a v e F ile D ia lo g l. I n itia lD ir e c to r y = se lec ted F o ld e r ; ^
<тры для окна S a v e A s .
s a v e F i l e D i a l o g l . F i l t e r = "Text f i l e s ( * . t x t ) | * . t x t | A l l f i l e s [ *. *) I* .* " ;
s a v e F i l e D i a l o g l . F ile N a m e = d e s c r i p t i o n . T e x t + " . t x t " ;
D i a l o g R e s u l t r e s u l t = s a v e F i l e D i a l o g l . S h o w D i a l o g ()
i f ( r e s u l t == D i a l o g R e s u l t . O K ) {
c u r r e n tE x c u s e . S a v e ( s a v e F il e D ia l o g l . F ile N a m e);
U p d a te F o r m (fa lse ); ч а с т и nurSJ 2 ^Уре- в нижнеи
M e s sa g eB o x .S h o w (" E x c u s e w r i t t e n " ) ; ст рочки две
1 ^ ^ <^екстовык
}

p r i v a t e v o i d o p e n _ C lic k ( o b j e c t se n d e r , E ven tA rgs e) {


i f (C h eck C h an ged ()) {
o p e n F ile D ia lo g l. I n itia lD ir e c to r y = se lec ted F o ld e r ;
o p e n F i l e D i a l o g l . F i l t e r = "Text f i l e s ( * . t x t ) | * . t x t | A l l f i l e s ( * . * ) |* . * " ;
o p e n F ile D ia lo g l.F ile N a m e = d e s c r ip tio n .T e x t + " .tx t" ;
D i a l o g R e s u l t r e s u l t = o p e n F i l e D i a l o g l . S h o w D i a ^ g () ;
i f ( r e s u l t == D i a l o g R e s u l t .OK) { Перечисление
c u r r e n t E x c u s e = new E x c u s e ( o p e n F i l e D i a l o g l . F ile N a m e DialogResult, возвраща­
U p d a te F o r m (fa lse ); емое окнами диалога
Open и Save, гарант и­
рует , что открытие
} и сохранение файла
будет происходить
p r i v a t e v o i d randomExcuse_Click( o b j e c t s e n d e r , E v e n t A r g s e ) только после щелчка на
i f (C h eck C h an ged 0 ) { кнопке ОК.
c u r r e n t E x c u s e = new E x c u s e (r a n d o m , s e l e c t e d F o l d e r ) ;
U p d a te F o r m (fa lse );
}
}

420 глава 9
чтение и запись файлов

p r i v a t e b o o l CheckChanged() {
i f (form C han ged ) {
D i a l o g R e s u l t r e s u l t = M e s sa g e B o x .S h o w (
"The c u r r e n t e x c u s e h a s n o t b e e n s a v e d . C o n t i n u e ? " ,
" W arn in g" , M e s s a g e B o x B u t t o n s .Y e s N o , M e s s a g e B o x I c o n .W a r n in g )
i f ( r e s u l t == Ц1 a 1 o g R e s u l t ^ I ^ ,
retu rn f a l s e , M essa g eB o x S h o w C ) в о зв р а щ а е т
} перечисление DialogResult, которое мы
retu rn tru e;
можем проверить. Это т ри обработчика
}
событшА СЫпдес1 рля
p r i v a t e v o i d description_TextChanged(object s e n d e r , EventArgs е) { ) лей ввода ^°рМЫ. Ш Ме-
c u r r e n tE x c u se .D e sc r ip tio n = d e s c r ip tio n .T e x t; / нение состояния
U p d ateF orm (tru e);
‘ из них говорит о >^оМ,
ч т о оправдание было от
} редактировано, поэтому
Сначала следует обно-
p r i v a t e v o i d results_TextChanged( o b j e c t s e n d e r , E v e n t A r g s e ) { \ вить экземпляр Excuse
c u r re n tE x c u se.R e su lts = r e s u lt s .T ex t;
^ т ем e^e^ZZb
> ГUpdateFormO,
U p d ateF orm (tru e); добавить
} звездочку в строку за ­
головка и
p r i v a t e v o i d la s t U s e d _ V a lu e C h a n g e d (o b je c t s e n d e r , E ven tA rgs e) { свойству Changed значе
c u r re n tE x cu s e . L astU sed = la s tU s e d .V a lu e ; ние true.

J
значения true методу UpdateFormO
U p d a t e F o r m ( t r u e T ^ — ./Те/зеЭдча
заставляет его пом ет ит ь форму как из-
J
, мененную, но не обновлять состояние полей
c l a s s E xcuse { ввода.
p u b lic s tr in g D e sc rip tio n { g e t; se t; }
p u b lic s tr in g R e su lts { g e t; s e t ; }
p u b l i c D ateT im e L a s tU s e d get; s e t ;
p u b lic s t r i n g E xcuseP ath get; se t;
Кнопка Random Exru<p , ^
^%ectoru.QetFiles(\ dAt u^ ^°^^^<^^'‘^ метод
p u b lic E xcuse 0 { лов Є выбранной n a n Z е Т . Т " ' <^^кстовык ф ай-
E xcu seP ath = чего выбиЪается с Т Х й н Т ^ ^ ^ ^ ^осТе
} крыть один из ф айлов чтобы от~
p u b lic E x c u s e (s tr in g excu seP ath ) {
O p e n F ile (ex c u seP a th );
} гарантировали
Mfc>i
p u b l i c E xcuse(R an dom random, s t r i n g f o l d e r ) использование опера-
s t r i n g [] f i l e N a m e s = D i r e c t o r y . G e t F i l e s ( f o l d e r . * .tx t" ) тора using при каждом
O p e n F ile ( f ile N a m e s [ r a n d o m .N e x t ( f ile N a m e s . L e n g t h ) ]) открытии потока.
} В эт ом случае наши
p r iv a t e v o id O p e n F ile (s tr in g excu seP ath ) { файлы всегда будут
th is.E x c u se P a th = excu seP ath ; закрыты.
u s i n g (S tr ea m R ea d er r e a d e r = new S t r e a m R e a d e r ( e x c u s e P a t h ) ) {
D e s c r ip tio n = r e a d e r .R e a d L in e 0 ;
R e s u lt s = r e a d e r .R e a d L in e 0 ;
L a stU se d = C o n v e r t. T o D a te T im e (r e a d e r .R e a d L in e 0 ) ;
}
}
p u b l i c v o i d S a v e ( s t r i n g file N a m e ) {
u s i n g ( S t r e a m W r i t e r w r i t e r = n e w S t r e a m W r i t e r ( f i l e N a m e ) )'.
{
w r ite r .W r ite L in e (D e sc r ip tio n );
w r ite r .W r ite L in e (R e su lts);
w r ite r .W r ite L in e (L astU sed ) ;
Вы вызывали мет од LastUsed.ToStringO? Помните, что мет од \л!гiteUneO вызывает
его автоматически!
дальше > 421
блок вы б о р а

Запись файлоб сопровождается принятием решений


Вам п р едстои т написать м н ож ество програм м , которы е берут входную ин ф орм ац и ю ,
н а п р и м ер , и з ф айла, и реш аю т, ч то с н е й п о т о м делать. В о т к р ай н е т и п и ч н ы й код с
одним длинны м оператором i f . О н проверяет перем енную p a r t и выводит в файл
различны е стр очк и в зави си м ости о т и спол ьзуем ого п ереч и сл ен и я . М н ож еств о вари­
антов приводит к м нож ественны м комбинациям операторов e l s e if:

enum BodyPart {

H ead, Пергд нами перечисление частей


тела — голова, плечи, колени
S h o u ld e r s, и пальцы ног. Мы собираемся срав­
K n ees,
нивать пеоеменную с каждым из
элементов и выводить строку в з а ­
Toes висимости от результ ат а.
}

p r iv a te v o id W r ite P a r tln fo (B o d y P a r t p art, S tr e a m W r iter w r ite r ) {


if (p a rt == B o d y P a r t.H e a d )

w r i t e r . W r i t e L i n e ( "на голове вол осы " );


e ls e if (p art == B o d y P a r t. S h o u ld e r s)

w r i t e r . W r it e L in e ( "плечи ш ирок ие"); Используя операторы if/else,


e ls e if (p art == B o d y P a r t. K nees)
мы вынуждены снова и сно
ва писать строчку it (part
w r i t e r . W r it e L in e ( "колени у зл ов ат ы е" ); ==[option]).
e ls e if (p art == B o d y P a r t. T oes)

w r i t e r . W r i t e L i n e ( "пальцы ног м ал ен ь к и е" ); Последний оператор else понадобится,


если ни одного соответствия не будет
e ls e
■найдено.
w r i t e r . W r i t e L i n e ( "а про эту часть тела мы н и ч е г о не зн а ем " );

WTVPM
Какие проблемы могут возникнуть при написании кода
с многочисленными операторами if/else? Подумайте об
опечатках, несовпадающих скобках и т. п.

422 глава 9
чтение и запись файлов

Оператор switch не им еет никаких


Оператор switch особенностей, предназначенных
для работы с файлами. Это всего
С р а в н е н и е п е р е м е н н о й с н а б о р о м р а з л и ч н ы х зн а ч е ­ лишь полезный инст рум ент ,
н и й — ч а с т о в с тр е ч а ю щ а я с я с и т у а ц и я , о с о б е н н о п р и который мы можем использовать
в текущей ситуации.
ч т е н и и и з а п и с и ф а йл о в. О н а н а с т о л ь к о р а с п р о с т р а ­
н е н а , ч т о в C # и м е е тс я о с о б ы й о п е р а т о р .

Э т о о п е р а т о р switch. В о т к а к с е го п о м о щ ь ю будет в ы ­ Оператор switch


гл яд е ть к о д с п р е д ы д у щ е й с т р а н и ц ы , п о с т р о е н н ы й н а
м н о го ч и с л е н н ы х к о м б и н а ц и ях о п е а то р о в i f / e l s e : сравнивает ОДНУ
переменную
Ключевое слово break; п о -
-казывает, где заканчиваемся
один оператор case и начи­
с МНОЖЕСТВОМ
enum BodyPart
нается следующий. Ч
значений.
Завершить оператор case можно
H ead, знаком переноса строки. Про­
S h o u ld e r s, грамма все равно будет ком пи­
лироваться, так как один опера­
Начните
н а ч н и т е сс ключевого слова тор case заканчивается maMj где
T oes, ■ ■ за которым
switch, ------- - следует
начинается следующий.
сравниваемая переменная.

W r ite P a r tln fo (B o d y P a r t p a rt, S tr e a m W r iter w r iter )

sw itc h ^ { p a r ^ { Тело оператора switch


e B o d y P a rt.H ead: представляет собой набор
w r i t e r . W r i t e L i n e (" н а голове вол осы " ); операторов case, срав­
break;
нивающих переменную,
следующую за ключевым
e B od yP art.S h o u ld e r s:
словом switch, с предлага­
w r i t e r . W r i t e L i n e (" п л еч и ш и р о к и е" ); емыми значениями.
• break;
case B od yP art. K n ees: За ключевым словом
w r i t e r .W r it e L i n e ( "колени у зл ов ат ы е" ); case следует значение
break;
для сравнения, двоето­
чие и набор операторов,
case B od yP art.T o e s:
завершающийся сло­
w r i t e r .W r it e L in e ( "пальцы ног м а л е н ь к и е " )(; вом break;. Именно эти
break; операторы выполняются
d e fa u lt: при совпадении значений.
w r i t e r . W r i t e L i n e { "a про эту часть тела мы н и ч е г о не зн аем ");
break;

default-., кот о-

дальше > 423


застигнутый врасплох

Чтение U запись информации о картах В файл


З ап и сь кол оды в ф ай л осущ ествить легко — д о ст а т о ч н о цикла, зап исы ва­ Оператор switch
ю щ е г о им я к а ж дой карты. В о т м ет о д , к о т о р ы й м о ж н о д о б а в и т ь к о бъ ек т у
D eck; позволяет срав­
p u b lic v o id W r ite C a r d s(str in g file n a m e ) {
нивать одно
u sin g
for
(S tream W riter w r i t e r
(in t i = 0; i
= new S t r e a m W r i t e r ( f i l e n a m e ) )
< c a r d s . C o u n t; i+ + ) {
{
значение с на­
}
w r i t e r . W r i t e L i n e ( c a r d s [ i ] .N a m e);
бором перемен­
}
ных.
A как о с у щ е с т в и т ь ч т е н и е и з ф а й л а ? И м е н н о з д е с ь вам н а п о м о щ ь п р и д е т
о п е р а т о р sw itch .

switch начинает
работу с указания значения
-------- с которым будет осущТст:
Suits suit; ,/ Дйннмы
^ оператор switch вт ь7а
swxtch (suitstring) ( метода, поэтами
переменная suit сохранена
case "Spades' о строке. ^

suit = Suits.Spades;
break;
case "Clubs": . Каждая из строчек case срав­
нивает некоторое значение
suit = Suits .Clxibs; с переменной, указанной в
строчке switch. В случае со­
break; впадения вьтолнян?тся все
операторы, расположенные до
case "Hearts" ; — ключевого слова break.
suit = Suits.Hearts;
break; Строчка default стоит
в самом конце. Если значе­
case "Diamonds"; ние переменной не совпало
ни с одним из предложенных
suit = Suits.Diamonds; вариантов, будут выполнены
операторы, стоящие после
break; ключевого слова default.
default:
MessageBox.Show(suitstring + " не подходит!");

424 глава 9
чтение и запись файлов

Чтение карт из файла


О п е р а т о р s w itc h п о з в о л я е т п о с т р о и т ь н о в ы й к о н с т р у к т о р для
класса D e c k и з п р е д ы д у щ е й гл а вы . О н ч и т а е т и з ф а йл а и п р о в е р я ­
е т к а ж д ую с т р о ч к у н а п р е д м е т с р а в н е н и я с к а р та м и . П р и у д а ч н о м
результате с р а в н е н и я к а р та п о п а д а е т в колоду.

В о т к р а й н е п о л е з н ы й м е то д S p l i t О . О н р а з б и в а е т к а ж д у ю с т р о ­
ку н а м а сси в б олее м е л к и х с т р о к , пе р е да ва я е й м а с с и в c h a r [ ]
р а з д е л и те л ь н ы х зн а ко в . Здесь строку nextCard
разбивают при помощи пробелов.
В результ ат е строковая
p u b lic D e c k (s tr in g file n a m e ) { переменная Six of Diamonds
c a r d s = new L i s t < C a r d > ( ) ; превращается в массив {"Six”,
S tr e a m R e a d e r r e a d e r = new S t r e a m R e a d e r ( f i l e n a m e ) ; "o f’\ "Diamonds”}.
w h i l e ( ! r e a d e r . E ndO fStream ) {
b o o l in v a lid C a r d = f a l s e ; J
s t r i n g n e x t C a r d = r e a d e r . R e a d L i n e () ; U :
s t r in g [ ] ca rd P a rts = n e x tC a r d .S p lit(n e w ch a r[] { ' });
V a lu es v a lu e = V a lu e s.A c e;
s w it c h ( c a r d P a r t s [0]) {
c a s e "A ce": v a l u e = V a l u e s . A c e ; b r e a k ;
c a s e "Two": v a l u e = V a l u e s . T w o ; b r e a k ;
Onejoamop switch
c a s e "Three" v a lu e = V a lu e s.T h r e e ; break;
сравнивает зна­
c a se "Four"; v a lu e = V a lu e s.F o u r ; break; чение с первым
c a se " F iv e" : v a lu e = V a lu e s.F iv e ; break; словом строки.
case " S ix " : v a l u e = V a l u e s . S i x ; b r e a k ; В случае совпаде­
case "Seven": v a lu e = V a lu e s . S even; b rea k ; ния значение при -
case " E ig h t" : v a lu e = V a l u e s . E i g h t ; b rea k ; сваивается пере­
case " N in e" : v a l u e = V a l u e s . N i n e ; b r e a k ; менной value.
case "Ten": v a l u e = V a l u e s . T e n ; b r e a k ;
case Jack": v a lu e = V a lu e s.J a c k ; break;
case Q ueen": v a l u e = V a l u e s . Q ueen; b r e a k ;
c a s e " K in g" : v a l u e = V a l u e s . K i n g ; b r e a k ;
d e f a u lt : in v a lid C a r d = tr u e ; break;
}
S u its s u it = S u its .C lu b s ;
s w it c h ( c a r d P a r t s [2]) {
c a s e "Spades": s u i t = S u it s .S p a d e s ; b rea k ; с 6
c a s e " C lu b s" : s u i t = S u i t s . C l u b s ; b r e a k ;
c a s e "H earts" : s u i t = S u i t s .H e a r t s ; b reak ;
c a s e " D iam on d s" : s u i t = S u i t s . D i a m o n d s ; b r e a k ;
d e f a u lt : in v a lid C a r d = tr u e ; break;
}
if ( ! in v a lid C a r d ) {
c a r d s.A d d (n e w C a r d (su it, v a lu e ));
}

дальше > 425


p.s. я нашел свою лягуш к у

Столько кода для чтения всего одной карты? Не


многовато л и ? А что делать с объектами с большим
количеством значений и полей? Неужели мне
потребуется оператор switch для каждого?
У ~

Е с ть более простой сп о со б сохранения объектов


в ф а й л — сериализация (serialization).
В м есто скрупулезной зап и си в ф айл каж дого поля и
зн ачени я м о ж н о сохран и ть объект сер и ал и зац и ей его
в поток. О бъект при этом превращ ается в набор бай­
т о в . О б р а т н ы й п р о ц е с с н а з ы в а е т с я десер и а л и за ц и ей , т о
ест ь вы в о с с о зд а е т е о б ъ е к т и з п о т о к а байтов.

З а т т и т на будущее, ч т о сущ ествует


метод Bnum.ParseQ (с ним вы познакоми­
тесь в главе 1 4 ) . который преобразует
строку Spades 8 элемент перечисления
Suits.Spades. Впрочем, в рассматриваемом
случае лучиле всего использовать сериали­
зацию, в чем вы скоро убедитесь...

426 глава 9
чтение и запись файлов

Ч то происходит с объектом при сериализации?


П р о ц е с с к о п и р о в а н и я о б ъ е к та и з к у ч и и п о м е щ е н и я в ф а йл к а ж е т с я т а и н с т в е н ­
н ы м , н а сам ом деле та м все о ч е н ь п р о с т о .

О б ъ ект в куче © С е ри ал и зо ванны й об ъ ект

Э кз е м п л я р о б ъ е к та и м е е т н е к о е П ри с е р и а л и з а ц и и полностью
состояние. О б ъ е к т п р и э то м сохраняется состояние объек­
«знает» т о л ь к о , ч т о д елает экзем ­ та, п о э т о м у к а ж д ы й э к з е м п л я р
п л я р о д н о го класса о т л и ч н ы м м о ж е т б ы т ь в о з в р а щ е н в кучу.
о т д р у г о го э к з е м п л я р а э т о г о ж е
класса.

00100101 ^ ^с о х р а н е н ы в ^
_ I вм ест е с аополнителЙ ой
01000110
да л ьн ей ш его в о с с т а ­
новлени я о б ъ ек т а ( н а п ш -

file.d at

О б ъ ект снов а в куче

И за те м ...
З атем , м о ж е т б ы т ь , че р е з м н о го д н е й и
в д р у г о й п р о гр а м м е , в ы м о ж е т е п р о в е ­
с т и десериализацию о б ъ е кта . И с х о д ­
н ы й класс будет в о с с та н о в л е н и з ф айла
ровно в то ж е состояние, в к о т о р о м о н
б ы л , со все м и е го п о л я м и и з н а ч е н и я м и .

дальше > 427


сохраняйте основной объект

Ч то именно мы сохраняем
В ы уж е зн аете, ч т о объект хранит и н ф о р м а ц и ю в полях. С о о т в е т ­
с т в е н н о п р и с е р и а л и з а ц и и н у ж н о с о х р а н и т ь ка ж д о е и з п о л е й .

Ч е м с л о ж н е й , об ъ е кт, те м с л о ж н е е е го с е р и а л и з а ц и я . К а к 37, т а к и 70,


в с е го л и ш ь б а й т ы , з н а ч и м ы е т и п ы , к о т о р ы е м о ж н о з а п и с а ть в ф айл
без и з м е н е н и й . А к а к б ы т ь , е с л и в со ста в е о б ъ е к та п р и с у т с т в у е т ссылка^
А п я т ь ссы ло к? А ч т о е с л и э т и с с ы л к и , в с в о ю о ч е р е д ь, с с ы л а ю тс я н а
д р у ги е о б ъ екты ?

П о д у м а й т е о б это м . К а ка я ч а с т ь о б ъ е к та я в л я е тс я п о т е н ц и а л ь н о у н и ­
ка л ь н о й ? П р е д с та в ь те , ч т о и м е н н о н у ж н о в о с с т а н о в и т ь , ч т о б ы п о л у ­
ч и т ь о б ъ е кт, к о т о р ы й б ы л с о х р а н е н . Т а к и л и и н а ч е , н о все с о д е р ж и м о е
к у ч и н у ж н о з а п и с ы в а ть в ф айл.

ИЛПРЯГИ
м о з г и
Каким образом следует сохранить объект Car, чтобы потом его
можно было восстановить в исходное состояние? Предположим,
что в автомобиле едут три пассажира, он имеет трехлитровый
двигатель и всесезонные шины... разве вся эта информация —
не часть состояния объекта Car? И что с ней делать?

Объект Engine
является закры­
тым. Нужно ли
(U Juna) его сохранять?
p a s s e n g e r С 6 ^ , g масілл«
Э1ЛЛ.0 a Ц іл^о
«о н»«“

К аж ды й из о б ъ е к ­
т о в перечи слени я
^ P assen ger и м е ет
ссы лки на д р у ги е
объект ы . С ледует
ли их сохран ят ь?

■ ^ L is\*

428 глава 9
чтение и запись файлов

Сериализации подвергается не только сам объект...


. . . н о и о б ъ е к т ы , н а к о т о р ы е о н с с ы л а е т с я , и д а ж е в с е о б ъ е к т ы , н а к о т о р ы е с сы ­
л а ю т с я э т и об ъ ект ы и т. д . Н е в о л н у й т е с ь , э т о з в у ч и т с л о ж н о , н о в с я п р о ц е д у - П о д о б н ы е г р и п п ы
р а п р о и з в о д и т с я автом ати ч еск и . С # н а ч и н а ет с объ ек т а, к о т о р ы й вы х о т и т е свя за н н ы х о б ъ е к т о в
сериализовать, и см отр и т на его поля в поисках связанны х объектов. Н айдя и н о гд а н а з ы в а ю т
такой объект, о н п ов тор я ет аналогичную оп ер ац и ю . Э то п озвол яет сохр ан и ть « гр а ф а м и »
всю нужную ин ф орм ац ию .

Пр» *“

объект ы .

Одним из полей
объекта Кеппе!
является коллекция
Ust<V 0 3 >, в кот о­
рой содержатся Ш
объекта Vog (Со- ^
бака). Ш также q
требуется сериа-
лизовать. к
оба объекта Dog и м е­
ю т ссылки на объект
DoggylD (И дент иф и­
катор собаки) и объ­
ект Collar (Ошейник).
Они тоже будут
подвергнуты сериали­ ^ ъ е к т О О '^ II
зации.

дальше > 429


сериализован для вашей защиты

Сериализация позволяет чи тать и записывать Чтение и запись


объект целиком объектов в файл
в ф айлы м о ж н о записы вать не то л ь к о с т р о ч к и текста. П ро це д ур а се­
р и а л и з а ц и и п о з в о л я е т к о п и р о в а т ь в ф айл ц е л ы е о б ъ е к т ы и ч и т а т ь осуществляются
и х о ттуд а... а ведь э т о в с е го н е с к о л ь к о с т р о ч е к ко да ! В ам п о т р е б у е т ­
ся п р о в е с т и н е б о л ь ш у ю п о д г о т о в и т е л ь н у ю р а б о т у — д о б а в и т ь с т р о ч к у быстро. Про­
[ S e r i a l i z a b l e ] в в е р х н ю ю ч а с т ь с е р и а л и з у е м о го класса. П о с л е э т о ­
г о все будет г о т о в о к з а п и с и . сто сериализуйте
Объект BinaryFormatter или дисериали-
С е р и а л и з а ц и я любого о б ъ е к та н а ч и н а е т с я с с о з д а н и я э к з е м п л я р а о б ъ ­
е к та B i n a r y F o r m a t t e r . Э т о о ч е н ь п р о с т о . Д о с т а т о ч н о д о б а в и т ь ещ е
зуйте обьект.
о д н у с т р о к у u s i n g в в е р х н ю ю ч а с т ь класса:

using System.Runtime.Serialization.Formatters.Binary;
• • •
BinaryFormatter formatter = new BinaryFormatter();

Осталось создать поток u приступить к чтению и записи объектов МетоЭ


новые ^ f создает
М е т о д S e r i a l i z e () о б ъ е кта B i n a r y F o r m a t t e r за п и сы в а е т о б ъ е к ты м ия от кры -
В поток. ЛЙ

using (Stream output = File.Create(filenameString)) {


formatter.Serialize(output, objectToSerialize);
^ ^ Метод SerializeQ 5epem
' объект и записывает его
Ч тобы прочитать сериализованны й об ъ е кт, испол ьзуйте м е то д в поток. Вы избавлены от
D e s e r i a l i z e {) о б ъ е к та B i n a r y F o r m a t t e r . О н в о з в р а щ а е т ссы лку, т и п необходимости делать это
к о т о р о й м о ж е т н е с о в п а д а ть с п е р е м е н н о й , п р е д н а з н а ч е н н о й для к о п и - вручную!
р о в а н и я . В э т о м случае и с п о л ь з у й т е п р и в е д е н и е .

using (Stream input = File.OpenRead(filenameString)) {


SomeObj obj = (SomeObj)formatter.Deserialize(input);
} ^
( Используй метод PeserializeQ для чтения обьек-
V_ та из потока, не забывайте осуществить п р и ­
ведение возвращаемого значения к т ипу чит ае­
мого объекта.

430 глава 9
чтение и запись файлов

Атрибут [Serializable]
А т р и б у т о м н а зы в а е т с я сп е ц и а л ь н ы й тег, д о б а в л я е м ы й в вер х н ю ю ч асть классов. С пом ощ ью атри бутов
в C# с о х р а н я ю т с я м е т а д а н н ы е , т о есть и н ф о р м а ц и я о том , как нуж но и с п о л ь зо в ат ь код. Д о б а в и в а т р и ­
бут [ S e r i a l i z a b l e ] н а д о б ъ я в л е н и е м к л а с с а , вы п о к азы в а ет е, ч т о э т о т класс м ож н о сер и ал и зо вать.
П о д о б н о е допусти м о, скаж ем , д ля классов с п о л я м и зн а ч и м ы х т и п о в (н ап р и м ер , i n t , d e c i m a l и л и
епитп). О тсу тстви е это го атр и бу та, как и н а л и ч и е в классе п о л ей , с е р и а л и за ц и я к о т о р ы х н ево зм о ж н а,
п р и в о д я т к со о б щ ен и ю об ощ иб ке. Убедитесь в этом сами...

Создание и сериализация класса


П о м н и т е класс G uy и з главы 3? С ер и ал и зуем Д ж о, ч то б ы и н ф о р м а ц и я о к о л и ч е ст в е его
н а л и ч н о с т и о стал ась в ф ай л е.
[S e r ia liz a b le ]
c la ss G uy
в верхней части л ю ­
бого файла класса, кот орый Вы собирает есь сериализовать.
Э то т код с ер и ал и зу ет класс в ф а й л G u y _ f i l e . d a t, а т а к ж е д о б ав л я е т к н о п к и Save J o e
(С о х р а н и ть Д ж о) и L o a d J o e (З агр у зи ть Д ж о):
Эти две строки using
u sin g S y ste m .lO ; обязательно должны
u sin g S y stem .R u n tim e . S e r i a l i z a t i o n . F o r m a tte r s . B in a r y ; быть. Первая указыва­
ет пространство имен,
в котором происходят
операции ввода и вы­
p r iv a te v o id sa v e J o e _ C lic k (o b je c t sender, E ven tA rgs e) вода, вторая делает
допустимой процедуру
u s in g (Stream o u tp u t = F i l e . C r e a t e ( " G u y _ F ile . d a t " )) { сериализации.
B in a ry F o rm a tter form atter = new B i n a r y F o r m a t t e r ( ) ;
fo r m a tte r .S e r ia liz e (o u tp u t, jo e);
F un w ith J o e a n d 8 o b
}
} Jo e h a s S »
p r iv a te v o id lo a d J o e _ C lic k (o b je c t sen d er, E v e n tA r g s e) B ebhasS lffl}

{ î h e b a r * h a s $100
u s in g (Stream in p u t = F il e .O p e n R e a d ( " G u y _ F i l e . d a t " ))
B in aryF orm atter fo rm atter = new B i n a r y F o r m a t t e r ( ) ; (3»в$№ Receive
jo e = (G u y )fo r m a tte r .D e s e r ia liz e (in p u t); to Jae

} Joetfïes Bob gives


$10 to В * $ 5 !o Jo e
U p dateF orm O ;
}
load Joe

О З ап уск и тестирование програллмы


Если Д ж о в р езультате о б м е н н ы х о п е р а ц и и с Б о б о м получил
2 0 0 д о л л ар о в , в р я д л и о н за х о ч е т п о т е р я т ь их п р и вы ход е и з

пр о гр ам м ы . Т еп ер ь Д ж о м о ж ет с о х р а н и т ь с во и к а п и т а л ы в ф а й л
и в о с с та н о в и т ь и х в л ю б о й м ом ент.

дальше ► 431
сериализация колоды карт

Сериализуем и десериализуем колоду карт


Д а в а й т е ещ е р а з зап и ш ем в ф а й л колоду карт. Б л а го д а р я се­
р и а л и за ц и и э т а зад ач а зн а ч и т е л ь н о уп р о сти л ась. Вам нуж но
всего л и ш ь со зд ать п о т о к и за п и с ат ь в н его о б ъ екты .

О Создание нового проекта


Щ е л к н и т е п р а в о й к н о п к о й м ы ш и н а и м е н и н о в о го п р о е к т а в о к н е S o lu tio n E xplorer, в ы б ер и ­
т е ком анду A d d /E x is tin g Ite m и добавьте классы C a r d и D e c k (а такж е п е р е ч и с л е н и я S u i t s
и V a l u e s и и н т е р ф е й с ы C a r d C o m p a r e r _ b y S u it и C a r d C o m p a r e r _ b y V a lu e ) , к о т о р ы е вы
и с п о л ь зо в ал и в главе 8. Вам такж е п о тр еб у ю тся д ва класса, ср ав н и ваю щ и х карты . Н е забудьте
о т р е д а к т и р о в а т ь стр о ку n a m e s p a c e , п о сл е т о го как все ф а й л ы будут с к о п и р о в а н ы в н о в ы й
проект.

© Д обавление атрибута
Д о б ав ьте атр и б у т [ S e r i a l i z a b l e ] к о б о и м классам , скоп и ро-
ван ы м в п р о ек т. , э т о г о сериализация
^ ___ У невозможна.

О Д обавление методов к дюрме


М етод R andom D eck случайн ы м о б р азо м со зд ает колоду карт, а
м етод D e a l C a r d s р а зд ае т и х и в ы в о д и т резул ьтат н а кон соль.
С оздает ся п у с т а я к о м -
R andom r a n d o m = n e w R a n d o m ( ) ;
да, в к о т о р ую случайн ы м
p riv a te D eck Random Deck(i n t num ber) { об р а зо м доШ ля ьот ся
D e c k m yDeck = new D e c k ( n e w C a r d [] { }) к а р т ы из к л а сса С а га , ^
fo r (in t i = 0; i < num ber; i+ + ) с о зд а н н о го в п р е д ы д ущ е й
{ главе.
m y D e ck .A d d (n ew C a r d (
( S u i t s ) r a n d o m . N e x t (4 ) ,
(V a lu e s )r a n d o m .N e x t(1 , 14)));
}
r e t u r n m yDeck;
}

p riv a te v o i d D ea lC a rd s(D ec k d eck T o D ea l, str in g title ) {


C o n so le .W r ite L in e (title );
w h ile (d e c k T o D e a l.C o u n t > 0)
{
C a r d n e x t C a r d = d e c k T o D e a l . D e a l (0 ) ^^^^одыи e^TdZ
C o n so le .W r ite L in e (n e x tC a r d .N a m e );
^У л ь т а т на консоль.
}
C o n so le .W r ite L in e ( ')

432 глава 9
чтение и запись файлов
Предварительная подготовка з а ко н ч е н а ... сер иал и зуем колоду
Д об авьте к н о п к и , у п р а в л я ю щ и е з а п и с ь ю и ч т е н и е м к о л о д ы . С в е р я й те с ь
с р е зул ьтата м и н а к о н с о л и . К о л о д а , к о т о р у ю в ы з а п и с ы в а е те в ф айл ,
д о л ж н а с о в п а д а ть с к о л о д о й , к о т о р у ю в ы ч и та е те . 0 5 ш к т B in a r ijfo r m a U e r
бер&т объекты, п о ­
private void buttonl_Click(object se n d e r , EventArgs a) { м еченны е а т р и б у т о м
Deck deckToWrite = R a n d o m D e c k (5);
using (Stream output = F i l e .C r e a t e (" D e c k l .
“ “ к “ «:“ Г сы в » е» «
B i n a ryF ormatte r bf = n e w B i n a r y F o r m a t t e r ();
bf.Serialize(output, deckToWrite); в i o K с « о » » »
т о д а S e r i a l iz e Q .
}
DealCards(deckToWrite, "Что было з а п и с а н о в ф а й л " );
Метод peserializeQ объ­
} екта BinaryFormatter
private void button2_Click(object sender, EventArgs e) ■
возвращает объект
обобщенного типа, п о ­
using (Stream input = F i l e . O p e n R e a d ( " D e c k l . d a t " )) {
эт ому требуется осу­
Bina ryForma tter bf = n e w B i n a r y F o r m a t t e r (); ществить приведение
Deck deckFromFile = (D e c k ) b f . D e s e r i a l i z e ( i n p u t ) ; к объекту Реек.
DealCards (deckFromFileTT^TO бьіло п р о ч и т а н о и з ф а й л а " ) ;

}
}
С ериализация набора колод
П о с л е о т к р ы т и я п о т о к а в н е го м о ж н о з а п и с ы в а ть п р о и з в о л ь н о е к о л и ч е ­
с т в о и н ф о р м а ц и и . П о э т о м у д о б а в и м две к н о п к и , п о з в о л я ю щ и е с о х р а ­
н я т ь в ф айл п р о и з в о л ь н о е к о л и ч е с т в о ко л о д .
Обратите внимание,
как в строчке, чит а­
private void button3_Click(object sen d e r , EventArgs e) {
ющей информацию
using (Stream output = F i l e .C r e a t e (" D e c k 2 .d a t " )) { из файла, осущ ест ­
B i n a ryF ormatte r bf = n e w B i n a r y F o r m a t t e r (); вляется приведе­
for (int i = 1; i <= 5; i++) { ние результ ат ов
3 один поток Deck deckToWrite = R a n d o m D e c k ( r a n d o m . N e x t (1,10)); работы метода
можно сериали­ PeserializeQ к объек­
bf.Serialize(output, deckToWrite);
зовать несколь т у Реек.
DealCards(deckToWrite, " К о л о д а #" + i + " записана");
ко объектоо.

}
Число сериализуемых
} объектов может быть
private void button4_Click(object sender, EventArgs e) { произвольным, главное —
using (Stream input = F i l e . O p e n R e a d (" D e c k 2 . d a t " )) { не забывать приводить
Binary Formatt er bf = n e w B i n a r y F o r m a t t e r ();
читаемые из потока объ-
екты к нужному типу.
for (int і = 1; і <= 5; І+-І-) {
Deck deckToRead = (Deck)bf.Deserialize(input);
DealCards(deckToRead, " К о л о д а #" + і + " прочитана")

}
}
}

О П р осм о тр результата
О т к р о й т е ф айл D e c k l . d a t в п р и л о ж е н и и Б л о к н о т . В ы н а й д е те та м всю
и н ф о р м а ц и ю , н е о б х о д и м у ю для в о с с т а н о в л е н и я о б ъ е к та D e c k .

дальше > 433


внешний вид символов

Мне не нравится, что когда я открываю срайл, в который


был записан объект, я вижу какой-то мусор. П осл е записи
колоды в виде набора строк я открывала файл в приложении
Блокнот и спокойно его читала. Мне казалось, что в С # я должна
— I без проблем понимать все, что делаю.

С ер и ал и зо в анны е об ъ екты запи сы в аю тся в д в ои чном


ф орм ате.
О н и к о м п а к т н ы . П о э т о м у в ы м о ж е т е р а с п о з н а ть с т р о к и , о т к р ы в
ф айл с с е р и а л и з о в а н н ы м о б ъ е к то м : ведь н а и б о л е е к о м п а к т н ы й
с п о с о б и х з а п и с и в ф а йл — и м е н н о в в ид е с т р о к . Н о п и с а т ь в та ­
к о м в ид е ч и с л а н е и м е е т см ы сла. Л ю б о е ч и с л о т и п а i n t м о ж н о
с о х р а н и т ь в ч е т ы р е х б а й та х . П о э т о м у б ы л о б ы с т р а н н о х р а н и т ь ,
к п р и м е р у , ч и с л о 49 369 144 к а к 8 -с и м в о л ь н у ю стр о к у , у д о б н у ю
для ч т е н и я . Э т о б ы л а б ы п у с та я т р а т а м еста!

сД еной
Для представления символов или строк в виде байтов в .NET используется Ю ни­
код. К счастью, в W indows имеется инструмент, позволяющий понять принцип работы Юникода. Откройте
приложение Character Мар (выберите в меню Start команду Run. введите «charmap.exe» и щелкните на
кнопке ОК).

П р и взгляде н а с и м в о л ы и з р а з н ы х я з ы к о в с т а н о в и т с я п о н я т н о , с к о л ь к о и н ф о р м а ц и и тр е б у е тся
за п и с а т ь в ф айл для с о х р а н е н и я т е к с т а . П о э т о м у .N E T п р е о б р а з у е т все с т р о к и и с и м в о л ы в ф о р ­
м ат Ю н и к о д . Б е р у тс я д а н н ы е (н а п р и м е р , буква Н ) и п р е о б р а з у ю т с я в б а й т ы (ч и с л о 72). В едь б укв ы ,
ц и ф р ы , п е р е ч и с л е н и я и д р у ги е д а н н ы е х р а н я т с я в п а м я т и и м е н н о в в ид е б а й то в . У з н а ть ж е соответ^
с т в и е м еж д у ч и с л а м и и с и м в о л а м и м о ж н о в Т а б л и ц е с и м в о л о в (C h a ra c te r М а р ).

в Chaiacttr Мар
Выдерите в с п и ­
ске шрифтов A nal и Pont! О feW
ЙФ
прокрут ит е вниз до
еврейского алфавита. (h 4 j J b fb H j Hj G G Ъ Ъ G e Ц Q q w
Щ елчком выберите д A. Юникод — это
6 w
Л A
W V Ч-- V Л, V 0
символ Shin. 0 A Л A Л
•w' д 0 индустриальный
у V' V У w W 0 0
■ Л, 1 0
стандарт. Вы
После выбора символа в t 9 H 3. я T можете посе­
строке состояния поя­ IN Ü t Э ■? □ n
1 Э Y i Л о M n т ит ь сайт h ttp : //
вится его код. Д ля буквы « ' )i_ i . Л.
ё w Л unicode.org/
>
Shin ~ это число OSE 4 ip f S’ 1 i
j ] iS i г Cl ih <- с
о шестнадцатеричной п
J j iß i t t - Ji 4 J и
системе счисления.
J 0 У Л V 0 Q 0 0 Д Ö Г: 6
V 0 ö * > т r £ a 1 у A % z , ♦
J
Д ля преобразования р- ____ ^
полученного значения
в десятичную систему
воспользуйтесь калькуля­ Hehiewletar
тором Windows в режиме
Scientific.

434 глава 9
чтение и запись файлов

.NET использует Unicode для хранения симВолоб и текста


Т и п ы , хр а н я щ и е те к с т и сим волы , — s t r i n g и c h a r — в пам яти хр а н я т
и н ф о р м а ц и ю в Ю н и к о д е . П р и з а п и с и д а н н ы х в ф а йл с о х р а н я ю т с я сим -
в о л ы Ю н и к о д . С о з д а й те н о в ы й п р о е к т , п о с т р о й т е ф о р м у с т р е м я к н о п -
ка м и , ч т о б ы п о с м о т р е т ь н а р а б о ту м е то д о в F i l e . W r i t e A l l B y t e s {) и
R e a d A l I B y t e s () и п о н я т ь , к а к и м е н н о о с у щ е с т в л я е т с я з а п и с ь в ф а й л . _ ^ ^ у Т ).Р а Ж Н 0 Н и 0

/ ^ ^ Ф
^ З ап и ш ем в ф айл обы чную строку и прочитаем е е .
В о с п о л ь з у й те с ь м е то д о м W r i t e A l l T e x t { ) , ч т о б ы з а с та в и ть п е р в у ю к н о п к у з а п и с ы в а ть
с т р о к у « E u re ka !» в ф айл « e u re k a .tx t» . З атем со зд а й те м а сси в б а й т о в e u r e k a B y t e s , п р о ч и ­
т а й т е в н е го и з ф айл а и в ы в е д и т е п о л у ч е н н ы й р е зультат:

F i l e .W r ite A llT e x t( " eurek a. t x t " , "Eureka!" );


byte[] eu rek aB ytes = F ile .R e a d A llB y te s (" e u r e k a .tx t" );
foreach (b y te b in eu rek aB ytes) ^ Метод R e a d A l l B y t e s Q возвращает ^
c o n s o l e w r i t e (" f 0 )
C o n s o l e . w r i t e ( 10}
"
, b) ■
),
V сш лк у на
Saumbi, новш массив, с^ерж ащии
п рочи т ан н ы е из ф ай ла.
C o n so le.W rite L in eO ;
Н а КО Н С О Л Ь б у д е т в ы в е д е н о : 6 9 1 1 7 114 lO i 107 97 33. Н о если открыть файл в прило­
ж е н и и Simple Text Editor, в ы у в и д и т е с т р о к у «E u re ka !»

О П у сть вторая кнопка отображает байты в ш естнадцатеричной си стем е.


Ч и с л а в э т о й систем е п о к а з ы в а ю тс я не то л ь к о в п р и л о ж е н и и C h a ra c te r М а р , п о э то м у им еет
см ы сл н а у ч и ть с я с н и м и ра б о та ть. К о д о б р а б о т ч и к а с о б ы т и й для в т о р о й к н о п к и д о л ж е н о т ­
л и ч а ть с я м етод ом C o n s o l e . W r i t e ( ) , для к о т о р о г о н а п и ш и те :
г о п ч п Т р W r i t e ( " f 0 - x 2 ) " Ь) ■ с и с т е м е ы т о л ь з у к ) т с я числа
C o n s o l e . w r i t e ( I 0 .X 2 ) ' от о до я и буквы от А до F. Так 6 8 рябно 1 0 7 . ^
В итоге м етод W r ite O б у д е т в ы в о д и т ь п а р а м е т р О ( п е р в ы й п о с л е в ы в о д и м о й с т р о к и )Л
в в и д е кода. П о э т о м у вы у в и д и т е с ем ь бай тов: 45 7 5 7 2 65__6Ь 6 1 2 1 ^ 1 — -------------------- ---------

о А третья кнопка долж на выводить буквы еврейского алдю вита


В е р н и т е с ь в Т а б л и ц у с и м в о л о в (C h a ra c te r М а р ) и дваж д ы щ е л к н и те н а сим воле S h in , ч т о ­
б ы д о б а в и ть е го в поле. П р о д е л а й те э т о для о с т а л ь н ы х с и м в о л о в в слове «Shalom »: L a m e d
(U + 0 5 D C ), V av (U + 0 5 D 5 ) и M e m (U -t-05D D ). В к о д о б р а б о т ч и к а с о б ы т и й для т р е т ь е й к н о п к и
добавьте с к о п и р о в а н н ы е б у к в ы и добавьте п а р а м е тр E n c o d i n g . U n i c o d e :

F ile .W r it e A llT e x t (" e u r e k a .tx t" , " Ш *?Ш" , E n c o d i n g . U n i c o d e ) ;

О б р а т и л и в н и м а н и е н а обратный порядок букв п о с л е в ста вки ? Д е л о в т о м , ч т о е в р е й с к и е


сл ова ч и т а ю т с я сп р а в а нале во . З а п у с ти т е к о д и п о с м о т р и т е н а п о л у ч е н н ы й р е зультат: f f
f e е 9 0 5 d c 0 5 d 5 0 5 d d 0 5 . П е р в ы е два с и м в о л а — « F F Е Е » , Ч Т О о з н а ч а е т с т р о к у и з д вух­
б а й т о в ы х с и м в о л о в . О с т а л ь н ы е б а й т ы п р е д с т а в л я ю т с о б о й б у к в ы е в р е й с к о г о алф ави та, -
н о п е р е в е р н у т ы е , т а к U + 0 5 E 9 будет п о к а з а н о к а к е9 05. Н о е сл и о т к р ы т ь ф айл в ваш ем
п р и л о ж е н и и S im p le T e x t E d ito r, все будет в ы гл я д е ть п р а в и л ь н о !

дальше ► 435
изменение порядка байтов

Перемещение данных Внутри массиВа байтоВ


Так как в с е д а н н ы е в и т о г е п е р е к о д и р у ю т с я в б а й т ы , Этот код создает массив
ф ай л и м еет см ы сл п р едставить в ви де б о л ь ш о го м а с с и ­ байтов с названием
ва б а й то в . С п особы ч тения таких массивов и зап иси в П р и вет ст ви е» , открывает
н и х вы уж е зн ает е. входящий поток и
данные в байты массива о
нулевого до шестого.

byte[] greeting;
Ые\\о\и
greeting = File.ReadAllBytes(filename

7 переменных типа byte

/ ІПІПІПІПІПІПІПІ
0 1 2. } ^ 5 4
JZ 101 108 108 111 33 33
Это статический
метод для объекта
Arraцs, меняюищи
В ои /
порядок байтов н й
обратный. Здесь он
используется, чтобы
показать, как внесенные
в массив байтоб
изменения б л м я ю т Нй \
вид читаемой из <раила
информации.
Array.Reverse(greeting);
File.WriteAllBytes (filen^e, greeting)

массива
После за п иси
байтов в файл текст
также оказывается 7 переменных типа byte
^ перевернутым.
ОІПІПІПОІПІП
0 1 г г ^ ч к
33 33 111 108 108 101

Изменение порядка следования байтов в слове Hello!! работает,


так как каждый символ занимает Всего один байт. Можете ли
436 глава 9 вы объяснить, почему это не сработает для слова 131 “гш.?
чтение и запись файлов

О дьект Stream W riter


' т ак же зашифровывает
Класс BinaryVi/riter оанные,, но он работает
только с текстом.
Д а н н ы е р а з л и ч н ы х т и п о в п е р е д з а п и с ь ю в ф а йл можно п р е о б р а з о в ы в а т ь в мас­
с и в ы б а й т о в , н о э т о к р а й н е р у т и н н а я р а б о та . П о э т о м у в .N E T п о я в и л с я класс
BinaryWriter, автоматически преобразующий данные и з а п и с ы в а ю щ и й и х
в ф айл . Вам н у ж н о т о л ь к о создать о б ъ е к т F i l e S t r e a m и п е р е д а ть е го к о н с т р у к - ф ^ .
т о р у класса B i n a r y W r i t e r . П о с л е ч е г о в ы з ы в а е тс я м е то д з а п и с и д а н н ы х . Д оба-
ви м в п р о гр а м м у ещ е о д н у к н о п к у , ч т о б ы п о с м о т р е т ь н а с п о с о б р а б о т ы с кл а ссо м X J
B in a r y W r it e r ().

О С о зд а й те к о н с о л ь н о е п р и л о ж е н и е и у к а ж и т е д а н н ы е для з а п и с и в ф айл.

in t i n t V a l u e = 4 8 7 6 9 4 1 4 ;
String stri n g V a l u e = "Hello!"; д а ^ ^ ^ с о з а ^ е т новый файл,
даже при наличии уже суш,ест6ующего.
byte[] byteArray = { 47, 129, 0, 116 };
float floatValue = 491.695F;
открывает
имеющийся файл и записывает новию
char charValue = 'E '; информацию поверх старой.
© П р и п о м о щ и м етода F i l e . C r e a t e () о тк р о е м н о в ы й п о т о к : ^

using (FileStream output = F i l e .C r e a t e (" b i n a r y d a t a . d a t " ))


using (BinaryWriter writer = new BinaryWriter(output)) {

О П р и ка ж д о м в ы з о в е м е то д а W r i t e О в к о н е ц ф а йл а , в к о т о р ы й о с у щ е с тв л я е тс я
за п и с ь д а н н ы х , будет д о б а в л я ть с я байт.

w r i t e r . W rite (intvalue) ; 1<.аждый оператор WriteQ пре~

w r i t e r . W r i t e (byte A r r a y ) ; g обьект FileStream. Любой записывает байты


writer.Write(floatValue) ; \ значимый m un будет преобра- до конца файла.
J зован автоматически.
writer.Write(charValue)

} возьми в руку карандаш


Подсказка: Каждая
строка начинается
И с п о л ь з у е м н а п и с а н н ы й р а н е е к о д д ля ч т е н и я т о л ь к о ч т о з а п и с а н н о г о
с числа, указыва­
byt e [ ] d a t a W r i t t e n = F i l e . R e a d A l l B y t e s (" b i n a r y d a t a .d a t " );
ющего на ее длину.
f o r e a c h (byte b i n d a t a W r i t t e n ) Кроме того, вы
C o n s o l e . W r i t e ( " { 0 : x 2 } ", b ) ;
Можете восполь­
зоваться Таблицей
C o n s o l e . W r i t e L i n e ( " - {0} b y t e s " , d a t a W r i t t e n . L e n g t h ) ; символов для поис­
ка соответствий
Console.ReadKeyO ; между строковыми
символами и значе­
Н а п и ш и т е р е зул ь та т в с в о б о д н ы е п о л я внизу. В ы п о н и м а е т е , какие бай­ ниями. I
т ы соответствуют к а ж д о м у и з п я т и о п е р а т о р о в W r i t e О ? П о м е т ь т е к а ж ­
д ую г р у п п у б а й т о в и м е н е м п е р е м е н н о й .
b y tes

дальше * 437
смесь байтов
Значения типов float и int занимают
по 4 бита, в то время как типы long
и double т риебую т по 8 байт. ^озьми в руку карандаш
V Решение
^ ^ q z о& ± 8 ^ вс вс Z 1 Z f 8 1 0 0 7 4 f e d s f s 4 3 4 -s - ZO b y te s
-J I____ ______ _ — »I--------- jn-Z
in t V a lu e ~ Ш ш д \/ а (и е b yte A rray V
c k a r V a iu e

Первый байт это длина строки. ^П ерем енная типа char


riocMompemt? соответствие ш Воспользовавшись
Воспользовйвшиа:> кл кальку
ль кулляя -
'Е’ занимает всего
можете в Таблии,е символов. Слово тором Windows для преоо- один байт, он соот ­
I I . 11 I
«Hello! >. ^ ^
» начинается с U+0048 разования
^ ^ ...ß л . , .,^4 значений
II и н и ииз
-2 ш
t л ^ест ­
Z' 1ЛЛ —

надцатеричной системы в вет ст вует символа


и заканчивается U+OOZ1. U+004S.
десятичную, вы обнаружите
числа, составляющие массив.
Класс BinaryReader
К л а сс B in a r y R e a d e r р а б о т а е т а н а л о г и ч н о кла ссу B i n a r y W r i t e r .
Ие верьте нам на слово. З а ­
В ы создаете п о т о к , п р и с о е д и н я е т е к н е м у о б ъ е к т B in a r y R e a d e r и мените строку для чтения
в ы з ы в а е те е го м е то д ы . Н о п р о гр а м м а для ч т е н и я н е з н а е т , ч т о з а типа float вызовом м е т о ­
д а н н ы е с о д е р ж а т с я в ф а й л е ! И н е м о ж е т у з н а ть . З н а ч е н и е т и п а да Readlnt3Z(). (Вам п о ­
flo a t 4 9 1 . 6 9 5 F п р е о б р а з о в а л о с ь в d 8 f 5 43 4 5 . Н о э т и ж е б а й т ы п од- требуется заменить т ип
,,FlnnVpZT"
х о д я т к з н а ч е н и ю т и п а i n t - 1 140 185 334. П о э т о м у следует в я в - у в и д и т е , ч т Т ч и т а е ^^я
из
н о м в и д е ука зы в а ть т и п ч и т а е м о го з н а ч е н и я . Д о б а в ьте к ф о р м е ещ е файла.
о д н у к н о п к у и займ е м ся ч т е н и е м т о л ь к о ч т о з а п и с а н н ы х д а н н ы х :

О Н а ч н е м с за д а н и я о б ъ е к т о в F i l e S t r e a m и B in a r y R e a d e r :

u s in g (F ile S tr e a m in p u t = F i l e . O penR ead( " b in a r y d a ta . d at' ) )


u s in g (B in a r y R e a d e r r e a d e r = new B i n a r y R e a d e r ( i n p u t ) ) {

© У к а ж и т е т и п д а н н ы х , к о т о р ы й будет в о з в р а щ а ть к а ж д ы й и з м е то д о в о б ъ е к та
B in a r y R e a d e r .

in t in tR ea d = r e a d e r . Readlnt32();
str in g str in g R e a d = reader.ReadStringO; ) ЙЗ которых возвращает данные
byte[] byteA rrayR ead = r e a d e r . R e a d B y t e s (4) ;
flo a t flo a tR ea d = r e a d e r . ReadSingle () ; ^ ^ a d B y te s Q ^ M ^ t) /^ '^ ’’
char c h a r R e a d = r e a d e r . ReadChar(); Щет парам ет р, сообщающий

© В ы ве д е м р е зул ь та т ч т е н и я д а н н ы х н а к о н с о л ь : байтов требуется прочитать.


C o n so le .W r ite ( " in t : {о} str in g : {l} b ytes: ", in tR e a d , S trin gR ead )
fo rea ch (b y te b in b yteA rra y R ea d )
C o n so le.W r ite (" {o } ", b );
C o n so le .W r ite (" flo a t: {0 } char: {l} flo a tR e a d , charR ead)
}
C o n so le.R ea d K ey O ;

В о т к а к о н будет в ы гл я д е ть :

in t: 48769414 str in g : H ello ! b ytes: 47 129 0 116 flo a t: 4 9 1 .6 9 5 char: E

438 глава 9
чтение и запись файлов

Чтение U запись сериализованных файлов Вручную


О т к р ы т ы е в п р и л о ж е н и и Б л о к н о т с е р и а л и з о в а н н ы е ф а й л ы в ы гл я д я т н е о ч е н ь т I
к р а с и в о . В се з а п и с а н н ы е в а м и ф а й л ы н а х о д я т с я в п а п к е Ы п \ О е Ь и д . Д а в а й те у11Р аЖ Н Р Н и01
п о см о то и м на д о п о л н ите л ьн ы е п р ие м ы раб оты с н и м и . 1

Г
С ериализуем два объекта C ard в различны е ф айлы
И с п о л ь з у й т е н а п и с а н н ы й р а н е е к о д , ч т о б ы с е р и а л и з о в а ть тройку крестей в ф айл t h r e e - с .
d a t , а шестерку червей — в ф айл s i x - h . d a t . У б е д и те с ь , ч т о о б а ф айла н а х о д я тс я в о д н о й
п а п к е , и м е ю т о д и н а к о в ы й р а зм е р и о т к р о й т е и х в Б л о к н о т е :

ß файле
встречаю тся
1 three-C - N otepad
З Н Й К О М Ь іЄ СЛОбЙ,
но по больше-и File Edit F o rm at V iew H e lp
т ст и он не- W yy P S e r i a liz e a deck of cards, v e r s i o n = l . 0 . 0 . 0 .
~ ^
ч іхтй еМ - ^
Cu1ture=neutral, PubTicKeyToken=nu11 serialize_a_deck _of_card s.C ardi
i< S u it > k _ B a c k in g F ie ld r < v a lu e > k _ B a c k in g F ie 1 d -5 e r ia l1 z e _ a _ d e c k _ o f_ c a r d s . S u it S T
s e r ia T iz e _ a _ d e c k _ o f_ c a r d s . v a lu e s , | y y y y t 5 e r ia l- iz e _ a _ d e c k _ o f _ c a r d s . s u i t s
. v a l u e __ s- ‘u y ^ iy s e r i a l l z e _ a _ d e c k _ o f _ c a r d s . v a l u e s .v a lu e — a, >• /

О Ц и кл , сравниваюи^^й два двоичны х ф айл а


П р и ч т е н и и б а й то в и з п о т о к а м е то д R e a d B y t e () во звращ ает з н а че н и е т и п а i n t . П о л е
L e n g t h п о т о к а п о з в о л я е т уб едиться, ч т о ф айл п р о ч и т а н п о л н о с т ь ю .

b yte[] fir stF ile = F ile .R e a d A llB y te s (" th r e e - с . d a t" );


b yte[] se c o n d F ile = F ile .R e a d A llB y te s (" s ix -h .d a t" );
fo r (in t 1 = 0 ; i < fir s tF ile .L e n g th ; i+ + )
if (fir stF ile [i] != s e c o n d F i l e [ i ] )
C o n so le .W r ite L in e (" B y te #{0}: {l} versus {2}",
i, fir stF ile [i], s e c o n d F i le [ i ] );

Этот цикл сравнивает первые байты из обоих


Так кйк эти файлы были п ро- файлов, пот ом вторые, пот ом т рет ьи и т. д.
чм тйни в разные массивы, мы Информация об обнаруженных различиях выво­
имеем возможность сравнить дится на консоль.
мХ побайтно. В данном случае
в два разных файла были се­
риализованы объекты одного
класса, поэтому они должны З ап и сь в ф айл не всегда осущ еств ляется
быть практически идентичны., f i с чи стого листа!
но давайте с й М и п 0 СМ01^ и М ,
НАСКОЛЬКО они совпадают.
])удДыне
кш е
осщ ор>оЖ Н ь11 осторожны с методом File.
OpenWriteQ. Он не удаляет имеющийся файл, а начинает
запись поверх уже записанной информации. Поэтому мы
предпочли метод File.CreateQ, создаюи^ий новый файл.

IJ e J ^ e ^ ej^ H u ix ie с ш р а н и Щ ) U И ^ ^ о д о л ж и м !
439
от празднуем наши различия

Найдите отличия и отредактируйте файлы


Н а п и с а н н ы й нам и ц и к л ч е тк о указы вает на р а зл и чи я между Возможна сериализация объек-
двум я с е р и а л и з о в а н н ы м и ф а й л а м и C a r d . З а п и с а н н ы е объ- ^ О в в формат XML
е кты различаю тся полям и S u i t и V a l u e , со о тве тстве н н о ,
и м е н н о в э т о м будут р а з л и ч а ть с я ф а й л ы . Н а й д я б а й т ы , с о ­
д е р ж а щ и е и н ф о р м а ц и ю о м а с ти и с т а р ш и н с т в е к а р т ы , м ы
сможем о т р е д а к т и р о в а т ь и х , получив в и т о ге соверш енно
н о в у ю к а р т у с н у ж н ы м и нам п а р а м е тр а м и !

О Рассмотрим выведенные на консоль результаты


О н и п о к а з ы в а ю т, чем р а з л и ч а ю т с я ф а йл ы :

B yte #322 : 1 v e r s u s 3
B yte #382: 3 versus 6

В е р н и т е с ь к п е р е ч и с л е н и ю S u i t s в п р е д ы д у щ е й главе, и в ы у в и д и т е , ч т о м а с ти C lu b с о о т ­
ве тс тв у е т з н а ч е н и е 1, а м а с ти H e a rt - з н а ч е н и е 3. Э т о п е р в о е р а з л и ч и е . В т о р о е р а з л и ч и е ,
к а к л е г к о д о га д а ть ся, — с т а р ш и н с т в о к а р т. Р а з л и ч н о м у к о л и ч е с т в у б а й т т а к ж е и м е е тс я о б ъ ­
я с н е н и е : о б ъ е к т ы м о гл и н а х о д и т ь с я в р а з н ы х п р о с т р а н с т в а х и м е н , ч т о и з м е н и л о д л и н у
ф айла.
Помните, каким образом ин­ Получается, что если байт в сериа­
формация о пространстве имен лизованном файле соот вет ст вует м а ­
ёкАючается в с е р и ^ и зо в а н н ь Т ст и, мы сможем поменять маст ь карты,
пространства имен прочитав файл, изменив один байт и сно­
различаются, размер байтов ва записав информацию в файл. (И м ей­
о сохраненном файле также те в виду, что в ваияем сериализованном
оуоет отличаться. файле информация о маст и может быть
сохранена в другом м ест е)

Вручную создадим новый ф айл с королем п и к.


О т р е д а к т и р у е м о д и н и з п р о ч и т а н н ы х м а с с и в о в и с н о в а з а п и ш е м е го в ф айл.

f irstF il^ [3 2 2 ]\ = (b yte) S u it s . Spades ;

Если числа, fir s tF ilA ,|3 8 2 y = ( b y t e ) V a l u e s . K in g ;


которые вы F i l e . D e l e t e ( " k i n g - s . d a t " ) ;
однаружи -
ли в своем F i l e . W r i t e A l l B y t e s ( " k i n g - s . d a t " , f i r s t F i l e ) ;
файле, о т ­
личаются Т е п е р ь д е с е р и а л и з у й т е ф а й л k i n g - s . d a t и у б е д и те сь, ч т о в н е м с д е р ж и т с я к о р о л ь пик!
от наших,
подставьте
их сюда. Зная, какие именно байты содержат
информацию о маст и и значении
карты, мы редакт ируем эти байты
перед записью в файл king-s.dat.

440 глава 9
чтение и запись файлов

Сло)кности работы с двоичными файлами


Ч т о дел а ть, е сл и в ы н е зн а е те т о ч н о , ка ка я и н ф о р м а ц и я с о д е р ж и т с я в ф айле? Н е и з в е с т н о даж е, к а к и м
п р и л о ж е н и е м э т о т ф а йл б ы л создан, а п р и о т к р ы т и и е го в Б л о к н о т е в ы в и д и т е т о л ь к о м усо р . Я с н о
т о л ь к о , ч т о Б л о к н о т н е с л и ш к о м п о д х о д и т д ля о т к р ы т и я т а к о г о р о д а ф а йл о в.

Перед вами сериализованная карма,


открытая в Злокноте. Вряд ли кто -
то сможет воспользоваться этой
информацией.

3 king-s - Notepad

File Edit Formst View H dp


p s e r l a l i z e a deck o f c a r d s , v e r s i o r - l 0 . 0 . 0 ,
I^
C u 1 t u r e = n e u t r a 1 , PublicKeyToken=nuI ____— . d T T i
и sг - e— n« -a- чn- і zЛ e- ї т_л a _-лa eA fcs jK
r-h ’ л - Р
_ o r _ ci—a»г*r a s . t a i u-
i< su 1t> k __ Back1ngFie1dT<value>k__ B a c k 1 n g F ie 1 d - J S e r ia l i2 e _ a _ d e c k _ o f _ c a r d s . s u i t s ,
s e r i a l і ze_ a _ d eck _ o f_ ca rd s.v a lu es, , iÿ y ÿ ÿ seria lize_ a _ d eck _ o f. c a r d s . s u i t s
• v a l u e __ a lüÿyÿ s e r 1 a 1 i z e _ a _ d e c k _ o f _ c a r d s . v a l u e s « valu e

Ho этой информации явно недостаточно.

С ущ е ств уе т т а к о й ф о р м а т к а к дам п д а н н ы х (h e x d u m p ) , о б ы ч н о и с п о л ь з у е м ы й для п р о с м о т р а д в о и ч ­


н о й и н ф о р м а ц и и . О н н а м н о го б олее и н ф о р м а т и в е н . К а ж д ы й б а й т п р е д с та в л е н в в ид е д в у х с и м в о л о в
в ш е с т н а д ц а т е р и ч н о й с и с те м е , ч т о п о з в о л я е т с к о н ц е н т р и р о в а т ь м н о го ч и с л е н н ы е д а н н ы е в н е б о л ь ­
ш о м объем е. Д в о и ч н ы е д а н н ы е т а к ж е х о р о ш о п р е д с т а в л я т ь в с т р о к а х д л и н о й п о 8 ,1 6 и л и 32 б а й та , т а к
к а к о н и д е л ятся и м е н н о н а т а к и е к у с о ч к и . К п р и м е р у , ф о р м а т i n t за н и м а е т д о 4 б а йт, с о о т в е т с т в е н н о ,
и м е н н о т а к у ю д л и н у о н и м е е т п о с л е с е р и а л и з а ц и и . В о т к а к н а ш ф а йл в ы гл я д и т в в ид е ш е с т н а д ц а т е р и ч ­
н о г о дампа:

М ож носразу ; (Bj C:\Windows\system52\cmd.exe


Г|
00 00 00 00 00
увидеть чис­ Г500О ; 00 01 00 00 00 fd fd fd -- fd 01 00
74 65 72 33 2c ..... ?Chapter9,
С1010: 00 0c 02 00 00 00 3f, 43 -- 68 6,1 78
ленное значение 0020; 20 56 65 72 73 69 6f Se -- 3d 31 2e 30 2e 30 2e 38 Uersion=1,0.0.0
каждого байта. 0030; 2c 20 43 75 6c 74 75 72 -- 65 3d 6e 65 75 74 72 61 . Culture=neu'tra
0040; 0c. ;.r 20 50 75 62 Sc 69 -- 63 4b 65 73 54 6f 6b 65 1, Publ i c K e y T o k e ’
75 6c 6c 05 01 — 08 00 00 0d 43 Є8 61 70 n=riull.... ;Chap
0060: 74 72 39 2e 43. 61 72 -- 64 02 80, 00 00 04 53 75 ter9.Card, . . .Su Вы може­
61 70 i t .Ualue...Chapt
69 74 05 s'e 61 6c 75 65 -- 04 04 13 43 68 74
er S .Card+Suits.,
те читать
ж о ї в ) ) 65 ,72 39 2e 43 61 72 64 -- 2b'53 75 69 74 73 02 00
m w 00 00 14 43 68 81 70 74 -- 65 72 33 2e 43 61 72 64 . . .C h a p t e r s .Card исходный
-- 80 00 00 02 08 80 00 .05 + U a l u e s ......... т екст , а м у ­
Число в начале 08a0 2b 56 61 6c 75 65 ,73. 02
00Ь0 fd fd fd fd 13 43 68 61 -- 70 74 65 72 39 2e 43 61 .... Chapters.Ca сор заменен
каждой строки 150с0 72 64 2b 53 75 69 74 73 -- 01 00 '00 00 07 76 61 Ec r d + S u i t s .....yal точками.
указывает на P)0d0 ?5 65 5f 5f 00 08 82 00 -- 00 00 01 00 00 08 05 fd ue
сдвиг первого 0Ое0 fd fd fd 14 43 68 61 70 -- 74 65 72 39 2© 43 61 72 ... Chapters.Car
76 d + U a l u e s .... ual
байта о оче­ nofo 64 2b 56 61 6c 75 65 73 -- 01 00 00 00 07
00 00
61
0b
6c
02
ul 00 7S 65 5f 5f 00 S8 02 00 -- 80 00 03 00
реди. 00 03 00 00 oa Ob -- 73 65 72 6Э 61 6c G9 ?3
...........
........serisliir^
: 110 80 00
ei 20 65 ?2 2e 43 61 72 64 2b. -- 56 61 6c 75 65 Î3 01 08 er.Card+Ualues. .
П130 00 00 9T 76 61 6c 75 65 -~ 5f 5f 00 08 02 00 00 00 ...ualue ....:.
Ul 40 03 00 00 00 Ob --

дальше ¥ 441
69 73 6е 27 74 20 74 68 69 73 20 66 75 6е 3f0a

Программа для создания дампа


Д а м п о м д а н н ы х (h e x d u m p ) н а з ы в а е тс я п р е д с т а в л е н и е с о д е р ж и м о го ф айл а в шестнадцатеричном виде.
Э т о п р е д с т а в л е н и е ч а с т о и с п о л ь з у е т с я для п о л у ч е н и я с в е д е н и й о в н у т р е н н е й с т р у к т у р е ф айла. В боль­
ш и н с т в е о п е р а ц и о н н ы х с и с т е м и м е е тс я в с т р о е н н а я п р о гр а м м а для р а б о т ы с д а м п о м д а н н ы х . К со ж а л е­
н и ю , W in d o w s в и х ч и с л о н е в х о д и т, п о э т о м у со зда д им п р о гр а м м у с а м о с то я те л ь н о .

Создание дампа данных


Н а ч н е м с те кста :

We t h e P e o p le of th e U n ite d S t a t e s , in O rder t o fo rm a m ore p e r f e c t U n io n ...

В о т к а к о н будет в ы гл я д е ть в дам пе д а н н ы х :

Вы можете видеть численное


представление каждого байта.
0000: 57 65 20 74 68 6 5 (^ 5 0 65 6 f 7 0 6 c 65 20 6 f 66 We t h e P e o p l e o f
0010: 20 74 68 65 20 55 6е 69 74 6 5 64 20 5 3 7 4 6 1 74 th e U n ite d S ta t
0020: 65 73 2с 20 69 6е 20 4f 72 6 4 6 5 72 20 74 6 f 20 e s , in O rder t o
0030: 66 6f 72 6d 20 61 20 6d 6f 7 2 6 5 20 7 0 6 5 72 66 form a m ore p e r f
65 63 74 20 55 6e 69 6f 6e 2e 2e 2e e c t U n io n ...
Мусор был
Число в начало каждой строки добавляется, заменен
используя смеш,ение первого байта. точками.
К а ж д о е и з э т и х ч и с е л — 57, 65, 6F — з н а ч е н и е о д н о го б а й та в ф айле в ш е с т н а д ц а т е р и ч н о м п р е д с та вл е ­
н и и (h e x ). Е сл и в д е с я т и ч н о й с и с те м е в ы и с п о л ь з у е те ч и с л а о т О до 9, т о здесь к чи с л а м д о б а в л я ю тс я
ещ е и б укв ы о т А д о F.

К а ж д а я с т р о ч к а в дам пе д а н н ы х п р е д с т а в л я е т ш е с т н а д ц а ть в ы в о д и м ы х с и м в о л о в , и з к о т о р ы х о н а со б ­
с т в е н н о и б ы л а создана. П е р в ы е ч е т ы р е ц и ф р ы в н а ш е м случае — э т о с м е щ е н и е в н у т р и ф айла: п е р в а я
с т р о ч к а н а ч и н а е т с я с О, след ую щ ая с 16 (в ш е с т н а д ц а т е р и ч н о й с и сте м е э т о 10), след ую щ ая с 32 и т. д.
(Д р у ги е ш е с т н а д ц а т е р и ч н ы е д а м п ы м о гу т в ы гл я д е ть п о-д ругом у.)

Работа 6 шестнадцатеричной системе счисления


Д ля ввода ш е с т н а д ц а т е р и ч н ы х ч и с е л в п р о гр а м м у д о с т а т о ч н о д о б а в и т ь
пе р е д о б ы ч н ы м ч и с л о м с и м в о л ы Ох (н о л ь , а затем х ):

in t j = 0x20;
M e s s a g e B o x .S h o w ( " T h e v a l u e is " + j );

О п е р а т о р +, п р е о б р а з у ю щ и й ч и с л а в с т р о к и , в о з в р а щ а е т з н а ч е н и е в де­ Метод Strina. Formate)


с я т и ч н у ю с и сте м у с ч и с л е н и я . Д ля п р е о б р а з о в а н и я к ш е с т н а д ц а т е р и ч н о й использует 8 качестве
си сте м е п о л ь з у й те с ь с т а т и ч е с к и м м е то д о м S t r i n g . F o r m a t { ) : парамет ра метод
Console. WriteUneQ,
s tr in g h = S tr in g .F o r m a t{ " { о :x 2 } " , j) ; так что вы без про­
блем можете с ним
работать.

442 глава 9
чтение и запись файлов

Достаточно StreamReader и StreamVl/riter


М е т о З назы вает ся
Н а ш а п р о гр а м м а будет п и с а т ь д а н н ы е н е п о с р е д с тв е н н о в ф айл. Т а к к а к м ы
R e a d B lo c k ( ) . п о т о -
записы ваем все го л и ш ь те кст, воспользуем ся о б ъ е к то м S t r e a m W r i t e r . Н а м ч т о п р и вы зове
п о тр е б уе тся та к ж е м е то д R e a d B l o c k O об ъ е кта S t r e a m R e a d e r , ч и та ю ш ;и й он « б л о к и р у е т с я »
б л о к и си м в о л о в в м ассив т и п а c h a r ; н у ж н о т о л ь к о указать разм ер блока. Т ак (т о е ст ь не в о зв р а ­
щ ает ся 6 п р о гр а м м у,
ка к в с т р о к е м ы о то б р а ж а е м 16 си м в о л о в , о н будет б л о к и ч и т а т ь и м е н н о та ­
а р а б о т а ет ), пока
к о го размера. не п р о ч и т а е т или все
за к а за н н ы е в а м и с и м ­
Д об авьте ф о р м е ещ е о д н у к н о п к у , с н е й м ы с в я ж е м п р о гр а м м у с о з д а н и я дам­
вол ы или ф а й л до конца.
па д а н н ы х . И з м е н и т е м а р ш р у т ы д о с т у п а в п е р в ы х д в у х с т р о ч к а х , ч т о б ы ohki
ук а зы в а л и н а р е а л ь н ы е ф а й л ы . Н а ч н и т е с с е р и а л и з о в а н н о го ф а йл а C a r d .

u sin g ( S t r e a m R e a d e r r e a d e r = n ew S t r e a m R e a d e r (@ " c: \ f i l e s \ i n p u t F i l e . t x t " ))

u s in g (S trea m W riter w r i t e r = new S t r e a m W r i t e r ( © " c : \ f i l e s \ o u t p u t F i l e . t x t " , fa lse ))


f ^ С во й с т во E n d O fS tr e a m о б ъ е к т а S tr e a m R e a d e r в о з в р а щ а е т зн а ч е -
. /^ ^ 1 Т ^ ,п ^ ^ о с т а ю т с я п р е д н а з н а ч е н н ь > е д л я ч т е н и я с и м в о л ы .

i n t p o s itio n 0; Этот мет од ReadBlockO чи-


т а е т в м а сси в т и п а ch a r бло -
w h ile ( 1r e a d e r . E n d O f S t r e a m ) { Ки р а з м е р о м д о сим волов.
ch ar[] b u ffer = new c h a r [ 1 6 ] ; ^ __ _ Статический метод
in t ch aractersR ead = r e a d e r .R e a d B lo c k (b u ffe r , 0, 16); String.ForM at преобразу -
ет числа в строки. З а ­
w r i t e r .W r it e ( " { 0 } : ", S tr in g .F o r m a t(" {O :x4}", p o s i t i o n ) ) ; пись «(О: х 4 } » указывает
мет оду Format Q выве­
p o sitio n += c h a r a c t e r s R e a d ;
сти второй парамет р,
в нашем случае, position,
fo r (in t і = 0; і < 16; i+ + ) { о виде ^--значного ш ест ­
if (i < ch aractersR ead ) { надцатеричного числа.
Э т о т цикл
по очереди str in g h ex = S t r i n g . F o rm a t( ' { 0 :x 2 }", ( b y t e ) b u f f e r [І] )

вы водит w r ite r .W r ite (h e x + " ");


все с и м в о ­
лы. } Массив char[] мож­
но преобразовать
e lse в ст року, передав
м Г з а м е н я е м их т и к а м и . его перегруженному
w r i t e r . W r i t e (" ') ;
конст рукт ору пере­
if (і == 7) { w r ite r .W r ite (" -- " ); } менной string ^
if ( b u f f e r [i] < 32 I I b u ffe r [i] > 250) { b u ffe r [i] =

}
str in g buf ferC o n ten ts = new string (buffer) ; ^ ^ ^
w r i t e r W r i t e L i n e (" " + b u f f e r C o n t e n t s . Substring(0, charactersRead));

Метод Substring возвращает ф рагмент строки. В данном случае он возвращает первые


символы переменной charactersRead, отсчитывая ик от начала (position О). (Посмотрите на
процедуру задания переменной charactersRead в цикле while —мет од ReadBlockO возвращает
в массив число прочитанных им символов.)

дальше у 443
построение дампа данных

Чтение байтоб из потока


С т е к с т о в ы м и ф а йл а м и н а ш а п р о гр а м м а п р е к р а с н о работает. Н о п о п р о б у й т е в о сп о л ьзо в а ться м етодом
F i l e . W r i t e A l l B y t e s () для з а п и с и в ф а йл м ассива б а й то в с о з н а ч е н и я м и б ольш е 127 и п о с м о т р и т е на
дам п д а н н ы х . В ы о б н а р у ж и т е т о л ь к о н а б о р с и м в о л о в «fd»! Д ело в то м , ч т о объект StreamReader предна­
значен для чтения текстовых файлов, с о д е р ж а щ и х б а й т ы со з н а ч е н и я м и д о 128. Д авайте п р о ч и т а е м б ай­
т ы н е п о с р е д с тв е н н о и з п о т о к а п р и п о м о щ и м етода Stream. Read О . М ы п о л у ч и м п р а к т и ч е с к и р еальное
п р и л о ж е н и е для р а б о т ы с д ам пом д а н н ы х : в ы см о ж е те даж е передавать ему и м е н а ф а йл о в к а к аргументы
из командной строки.

С о зд а й те к о н с о л ь н о е п р и л о ж е н и е с и м е н е м hexdumper. К о д для н е г о н а х о д и т с я н а с л е д ую щ е й с т р а н и ­
ц е, а в о т к а к в ы гл я д и т р е з у л ь та т е го р а б о т ы :

Попытка зап \стить наше


приложение гбез указания
ш зания и м
м ее­- При попытке передать приложению
ни файла приводит к сооби^е- имя несуш,ествующего файла
нию об ошибке. появляется сооби^ение об ошибке.

I C;\Windo№s\system32Sc!nd.f

|‘
,::\teiiip>hexdurapei* 1
111 sage - >iexdrkpe;I- ,fi l s - -to ~ d l np
1
г :\t erap^Jiexdumpe i* dot JS~!11./t~exi St-dat
1lie doe:s n o t ISXi:St: doe not“exist -dat' ^ — "
А а м п данных
usage - hexdpipejr file-' to "'.durip суи^ествуюи^его
файла выводит­
r ‘:\temp>hexduripei' kina- s.dat ся на консоль.
‘«100: 00 01 00 00 00 ff ff ff 01 08 00 .00 00 00 00 ..
00 0c 02 00 80 00,/50 53
И020: 20 61 20 64 65 63 6b 20
— 65 72 69 61 6c 69 7a 65 . . , - ..PSes'ialxae
6f 66 2,0 63 61 72 ■64 73 cl d.i?ck of cai'ds
ЙЙ30: 2c 20 S6 6S 72 69 6f 6e 3d 31 2e 30 2e 30 2e .. Uersion =1.0,0.
H040: 30 2c 20 43 75 бЗ 74 75 — 72 65 3d 6e 65 75 74 72 .0, Gultui'e -neiiti'
И0\.0:- 61 6c 2c 20 50 7S 62 6c — 69 63 4b 65 79 54 6f 6b
И060: 65 6e 3d 6e 7S 6|fc 6c 05 — 01 00 00 '00 le 53 65 72
al, PublicKeA^Tok
e n 11 11..... 8er
i Обычно вывод на
И07В: 69 61 6c 69 7a 6Б Sf 61 — Sf 64 6S 63 6b Sf 6f 66 ia iis e_;a__deck_o f консоль осущ ест ­
ИИ80: 5f 63 61 72 64 ,?3 2e 43 — 61 72 64 02 00 80 00 15
ИЯ90: 3c S3 75 69 74 3e 6b Sf — Sf 42 61 63 6b 69 6e 67
„cai'-ds,Card..... вляется методом
<Suit >k_.Backing
46 69 65 6c 6(4 16 3c 56 — 61 6c 75 65 3e 6b 5f 5f Fie id.<Ualue>k__ Console.WriteLineO.
H0b0: 42 61 63 6b 6:9 бе 67 46 69 65 6c 64 04 04 If &3 BackingField...S Но мы восполь­
И0С0: 65 72 69 61 6c 69 7a 65 — ' Sf 61 Sf 64 65 63 6 b e y ia 1ia'e_a deck
ti!Jd0: 6f 66 Sf 63 61 72 64 73 2e 53 75 69 74 73 02 00 of __cai'ds .Suits .. зовались м ет о ­
ИиеВ: 00 0Й 20 S3 65 72 69 6,1 — 6c 69 7a 65 5f 61 5f 64 - - St^rialise_a_.d дом Console.Error.
it0f0: 65 63 6b Sf 6f 66 5f 6;-! 61 72 64 73 2e 56 .61 6c e c k„o f ,_cards .U a 1
Й10Й: ?5 6S 73 02 j00 00 00 00 00 00 05 fd ff ff ff wes ............. WriteLineQ для
И110: If 53 65 72 69 61 « 6jV 7a 6S 5f 61 Sf 64 65 63 вывода сообщений
(1120: 6b 5f 6f 66 Sf 63 i i .Sei*iali2 e_a dec
64 73 2e 53 7S 69 74 73 ^—0f_cai'ds .Suits
W130: 01 00 00 00 07 76 t 1 75 65 Sf Sf 00 08 02 m «... .value об ошибке.
HI 40: 00 00 00 00; 00 08 05 ff ff ff 20 53 65 72 69 Seri
i!lS0: 61 6c 69 7a ,65 Sf 61 64 65 63 6b Sf 6f 66 5f a 1;ijje ,_a„deck.__of„
«160: 63 61 72 64, 73 2e Su ,1 ■
--- 6c 75 65 73 01 00 00 00 carcts.Ualues«...
(S178: 07 76 61 .6cj 75 65 Sf k t ■
— 00 08 02 00 00 00 0d m -value_-..... .
И180; 00 08 0b “ .
1
;C:\tenp> I

Для передачи аргумент ов комаиР}ипГ ^ Р<^^ожении Windows Forms.


е p . , , . , „

444 глава 9о ' ' и м — в— —■ _


о Е с л и с в о й с т в о a r q s .L e n a t h н е р а в н о 1 , чт ение и запись ф айлов
^ f ^ 9a4 u а р гум ен т о б значит , в ком андн ую ст р о к у а р гу -
ком андной ч /м е н т ы не п е р е д а ю т с я и ли , н а о б о р о т ,
зует ся п арам р ^ ' \ ф п е р е д а е т с я б о л ее о д н о го а р г у м е н т а .

sta tic v o id M a in (str in g [] args) вн и м ан и е, как E x it( ) з а в е р ш а е т


здесь и сп о л ьзует ся м ет о д п р о г р а м м у . В о т в е т нл
if (a r g s .L e n g th != 1 ) C o n s o l e . E r r o r .W r i t e U n e Q . п е р е д а ч у е м у значени я т и п а
^ ~ imn ft ОН
пи вв оо зз вв рюаашш,аа е т к о д о ш и о
C o n so le .E r r o r .W r ite L in e (" u s a g e : hexdm per f i l e - t o - d u m p " ); (что п о л е з н о п р и н а п и
S y s t e m . E n v i r o n m e n t . E x i t (1) ; сании к о м а н дн ы к с ц е н а р и е в
и к о м а н д н ы х ф а й л о в ).

f { ' F i l e . E x i s t s ( a r g s [0 ] ) ) .^ Е с л и п ер ед а н н о го ф а й л а
не с у щ е с т в у е т , в ы в о -
C o n s o ll ee ..EE rrrroorr . W r i t e L i m
ne (" F ile does not e x ist: {O }", a rg s[0 ]); д и т с я д р у г о е сообщ ение
S y s te m .E n v ir o n m e n t.E x it(2 ); и во зв р а щ а ет ся д р уго й
-у код ош ибки.
u s in g (S tream in p u t = F i l e . O p e n R e a d ( a r g s [0] ) )

т а к как байт ы ч и т а - М е т о д S tr e a m .
in t p o sitio n = 0 ; ю т ся непосредст вен но R -e a d () ч и т а е т
b y t e [ ] b u f f e r = new b y t e [ 1 6 ] ; из п от ока. — ^айт ы в буф ер.
w h ile (p o sitio n < i n p u t . L en gth ) i ^ данном случае
{ оиф ера и гр а ет
in t c h a r a c t e r s R e a d = i n p u t . R ead (b u f f e r , 0, b u f f e r . L ength) ; fe d b M b ^ ^ ^ a c M ^ ’
if (ch a ra ctersR ea d > 0) байт ы из т ек ст а -
{ во го ф ай ла.
C o n so le .W r ite (" {0 }: ", S t r i n g . F o r m a t ( " { о : х 4 } ", p o s itio n ));
p o sitio n += c h a r a c t e r s R e a d ;

/ fo r (in t i = 0; i < 16; i+ + )

if (i < ch aractersR ead )

str in g hex = S tr in g .F o r m a t(" {0 :x 2 }" , ( b y t e ) b u f f e r [ i] );


Э т а част ь
п рограм м ы C o n so le .W r ite (h e x + " ");
ост алась н еи з­ }
м ен ной, п р о ст о e ls e
буф ер т еп ерь
C o n s o l e . W r i t e (" ");
содерж и т бай ­
т ы , а не с и м ­
вол ы (но м е т о д if ( i == 7)
S tr in g . fo r m a tQ } C o n so le.W r ite (" -- ") ;
работ ает \
в л ю б о м с л у ч а е ). \ if (b u ffe r [i] < 32 I I b u ffe r [i] > 250) { b u ffer [i] = (b y te )'.'; }
}
strin g b u fferC o n ten ts = E n c o d in g .U T F 8 . G e t S t r i n g ( b u f f e r ) ;
C o n so le .W riteL in e ( " b u f f e r C o n t e n t s . S u b s tr in g (0, ch aractersR ead ));

и же м асси в a« “ з“ «

дальше * 445
часто задаваемы е вопросы

_ Чаацо

^аД аБ аеМ ы е которая будет открывать файл, о наличии


Почему после методов Б о 11| > о с ь 1 символов с высокими значениями в нача­
File.ReadAllTextо и File. ло файла помещается зарезервированная
WriteAllText О я не пользуюсь последовательность символов: FF FE. Это
Q ; Основным их различием являет­
методом Close () ? так называемая «отметка порядка бай­
ся тот факт, что методы класса F i l e
являются статическими, поэтому вам тов». Находя ее, программа узнает, что
Q ; в классе F i l e присутствуют не нужно создавать их экземпляры. все символы закодированы в двух байтах.
несколько статических методов, кото­ А класс F i l e l n f o требует создания
рые автоматически открывают файл, экземпляра с указанием имени файла. Почему эти символы называются
читают или пишут данные и затем Его не имеет смысла использовать для отметкой порядка байтов?
автоматически закрывают его. Вдо­ единичных операций (например, удаления
бавок к уже упомянутым сюда от­
носятся методы R e a d A l l B y t e s ()
или перемещения одного файла). Но для И; Помните, мы меняли порядок
многочисленных операций с одним фай­ ййтов? Значение буквы Shin в Юникод
и W r i t e A l l B y t e s ( ) , работа­ лом он более эффективен, так как вам U+05E9 было записано в файл как Е9
ющие с массивами байтов, а так­ достаточно один раз передать имя этого 05. Вернитесь к коду, записывающему
же методы R e a d A l l L i n e s () файла. Выбор класса зависит только от эти байты, и поменяйте третий параметр
и W r i t e A l l L i n e s ( ) , читающие конкретной ситуации. Грубо говоря, для на W r i t e A l l T e x t О : E n c o d i n g .
строковые массивы, в которых каждая единичных операции выбираем класс B i g E n d i a n U n i c o d e . Порядок следова­
переменная становится новой строкой F i l e , а для последовательного редакти­ ния байтов станет прямым 05 Е9. Изменит­
в файле. Все эти методы автоматически рования — класс F i l e l n f o . ся и отметка порядка байтов: FE FF. Кстати,
открывают и закрывают потоки, поэтому
написанное вами приложение Simple Text
дополнительные операторы вам не тре­
» Почему символы в слове Editor может читать как в прямом, так и в

г
буются.
1гека!» записываются как единичные обратном порядке!
байты, в то время как для записи букв
Если в классе FileStream еврейского алфавита требуется по два
имеются методы чтения и записи, за­ байта? И что это за символы FF FE
чем нужны классы StreamReader
и StreamWriter?
в начале? Символы с неболь­
! Вы увидели разницу между двумя шими значениями
^ ! Класс F i l e S t r e a m используется
для чтения и записи байтов в двоичный
файл. Его методы работают с байтами
и массивами байтов. Но есть и про­
О 13K0 связанными кодировками Юникод.
Латинским буквам, цифрам, знакам пре­
пинания, а также ряду стандартных симво­
лов (фигурным скобкам, амперсандам и
Юникод записы­
ваются но одному
граммы, работающие только с тексто­ другим элементам вашей клавиатуры)
выми данными, например, наша первая
версия программы Excuse Generator.
соответствуют небольшие значения Юни­
код — от О до 127. (Аналогичная ситуация
в байт, в то время
Там мы использовали методы классов
S t r e a m R e a d e r ИS t r e a m W r i t e r ,
с символами кодировки ASCII.) Если файл
содержит только такие символы, они вы­
как для записи сим­
созданные специально для работы со
строками текста. Без них вам приходи­
водятся в виде одиночных байтов.
волов Юникод вы­
лось бы сначала читать массив байтов
и писать цикл, ищущий в этом массиве
Все усложняется, когда на сцену выходят
символы с большими значениями. Ведь
деляются два байта.
знаки переноса строки, так что иногда эти байт может иметь значение от О до 255,
классы сильно облегчают жизнь. и не больше. А вот в двух байтах уже
можно хранить числа от О до 65,536 — Это используем м в -NET по
Когда используется класс File, у м о л ч л н м ю кодировка называется
в шестнадцатеричной системе счисления
а когда класс Filelnfo? U T F -8 . Д л я смены кодировки
это FFFF. Чтобы сообщить программе,
нужно передать методу File.
WriteAllTextO другое значение.

446 глава 9
чтение и запись файлов

___ О тр е д а к ти р у е м п р о гр а м м у Б р а й а н а E x c u s e M a n a g e r таким о б р азо м , ч то б ы о н а


аЖ НбНКб начала р аботать с двоичны м и ф айлам и, содерж ащ им и сериали зованны е объ ­
екты E x c u s e .

С ериализация класса Excuse


П о м е т ь т е класс E x c u s e а т р и б у т о м [ S e r i a l i z a b l e ] . П отреб уется
такж е добавить стр о ч ку u s i n g :
u s in g S y s te m .R u n tim e .S e r ia liz a tio n .F o r m a tte r s.B in a r y ;
Подсказка: И с-
О О тред актируйте м етод Excuse.Save О
Т е п е р ь м е то д S a v e о
пользуйт е
в м е с то и с п о л ь з о в а н и я о б ъ е к та S t r e a m W r i t e r , ^ ^ ^ е в о е Т л о в о
д о л ж е н о т к р ы в а т ь ф айл и с е р и а л и з о в ы в а т ь е го . Вам н у ж н о п о н я т ь , ка- возвра-
КИМ о б р а з о м п р о и с х о д и т д е с е р и а л и з а ц и я те к у щ е го класса. щает ссылку на
эт от ж е класс.
О О тред актируйте м етод Excuse.OpenFile О
Вам п о т р е б у е т с я в р е м е н н ы й о б ъ е к т E x c u s e , в к о т о р ы й будет п р о и с ­
х о д и т ь д е с е р и а л и з а ц и я , п о с л е ч е г о е го п о л я с к о п и р у ю т с я в т е к у щ и й
класс.

О тредактируйте срорму
М ы б о л ь ш е н е р а б о та е м с т е к с т о в ы м и ф а й л а м и , п о э т о м у р а с ш и р е н и е . t x t н е п о д ­
х о д и т . И з м е н и т е о к н а д и а л о га , за д а н н ы е п о у м о л ч а н и ю и м е н а ф а й л о в и к о д п о и с к а
п а п к и , п о д с т а в и в туда р а з р е ш е н и е * . e x c u s e .

Потрясающе! Весь код для


сохранения и открытия оправданий находится внутри
класса Excuse. Изменения вносились в класс, форму почти
не трогали. И форма работает вне зависимости от того, как класс
сохраняет данные. Она просто передает имя (райла, и индюрмация
сохраняется. ~ '

Р азум еется! Код та к л егко ред акти руется благод аря


и нкапсул яц и и класса. ПoмнumeJ что и н­
капсуляция является
К л а сс, в к о т о р о м все в н у т р е н н и е о п е р а ц и и с к р ы т ы о т о с та л ь ­ одним из чет ы­
н о й ч а с т и п р о гр а м м ы , а о т к р ы т ы м я в л я е тс я т о л ь к о т о , ч т о рех основных при -
н е о б х о д и м о , н а з ы в а е тс я инкапсулированным. В п р о гр а м м е ’ - знаков объектно-
E xcuse IM anager ф о р м а н е и м е е т и н ф о р м а ц и и о т о м , к а к и м ориентированного
о б р а з о м о п р а в д а н и я с о х р а н я ю т с я в ф айл . О н а в с е го л и ш ь
!лрограммирования?
Вот наглядный п р и ­
п е р е д а е т и м я ф айл а классу, а у ж о н дел ает все н е о б х о д и м о е . мер того_, каким об­
И м е н н о п о э т о м у в ы с м о гл и т а к л е г к о о т р е д а к т и р о в а т ь с п о с о б разом она облегчает
р а б о т ы класса с ф а йл а м и. Ч е м л у ч ш е и н к а п с у л и р о в а н класс, нам жизнь.
те м п р о щ е вам р а б о та ть с н и м в будущем.

дальше > 447


решение упражнения

1Н2НИ2 выглядит код программы Excuse Manager после редактирования.

еш ш е 8 форме требуется отредактировать только т ри оператора: два в обра­


ботчике событии кнопки Save и один для кнопки Open, они меняю т расилирение
(раилоб и задают имя, под которым файлы сохраняются по умолчанию
p r iv a te v o id sa v e _ C lick (o b je ct sen d er, E ven tA rgs e) {
// существующий код
saveFileDialogl.Filter = "Excuse files (*.excuse)|*.excuse All files ( * . * ) I * . * » ;
saveFileDialogl.FileName = description.Text + ".excuse";
// существующий код
} Ч дт исчолоззк»««

p riv a te v o id o p e n _ C lic k (o b je c t send er, E ven tA rgs e) {


// существующий код
openFileDialogl.Filter =
"Excuse files (».excuse)I*. excuse|All files (*.*}!*.*")
// существующий код
}

[S e r ia liz a b le ] Это код всего класса Excuse.


c l a s s E xcuse {
p u b lic s tr in g D e sc r ip tio n { g e t; se t; }
1 и о т о р л я онй
p u b lic s t r in g R e su lts { g e t; s e t ■ }
p u b l i c D ateT im e L a s tU s e d { g e t ; s e t ;
p u b lic s t r i n g E xcu seP ath { g e t; s e t ;
p u b lic E xcuse 0 {
E xcu seP ath =
}
p u b lic E x c u s e (s tr in g excu seP ath ) {
O p e n F ile (ex c u seP a th );
}
p u b l i c E xcuse(R an dom random , s t r i n g f o l d e r ) {
s t r i n g [ ] file N a m e s = D i r e c t o r y .G e t F il e s ( f o ld e r , " * .ex cu se" )
O p e n F ile (file N a m e s[r a n d o m .N e x t(file N a m e s.L e n g th )] ) ;
}
К онст рукт ор, за -
p r iv a t e v o id O p e n F ile (s tr in g excu seP ath ) {
гружаюш,ий случайные
th is.E x c u se P a th = excu seP ath ;
оправдания, теперь
B in a r y F o r m a tte r f o r m a t t e r = new B i n a r y F o r m a t t e r ( ) ;
E xcu se tem pE xcuse;
ищ ет расширение
u s in g (S tream in p u t = F ile .O p e n R e a d ( e x c u s e P a t h ) ) {
.excuse вместо расш и­
^ tem p E xcu se = ( E x c u s e ) f o r m a t t e r .D e s e r i a l iz e ( i n p u t )
рения *.txt.

D e s c r ip tio n = te m p E x c u se .D e s c r ip tio n ;
R e s u lt s = te m p E x c u s e .R e s u lts ;
L a stU sed = tem p E x cu se. L astU sed ;
}
p u b lic v o id S a v e ( s t r i n g file N a m e ) {
B in a r y F o r m a t t e r f o r m a t t e r = new B i n a r y F o r m a t t e r ( ) ;
u s i n g (S tr e a m o u t p u t = F ile.O p er^ J jsifee ( f i le N a m e ) ) {
fo r m a tte r .S e r i a li z e (ou tp u t, (th is)); у и a
} ^ ------ С -X Ключевое слово this т у т ф игури-
j рует , так как требуется сериалы -
зооать именно эт от класс.

448 глава 9
1 0 о б |> а б о 1 ][1 х а и с:1С Я 1«ч ;ен и й

Борьба с огнем надоедает ^

Программисты не должны уподобляться пожарным.


В ы усер дно работали, ш туд ир овал и технические руководства и н а­
конец достигли верш ины : те п е р ь вы главны й п рограм м и ст. Н о
вам д о сих пор п родолж аю т звон ить с р аб о ты по ночам, потом у что
програм м а уп ал а или раб отает неправи л ь но. Н ичто так не в ы б и ­
вает из колеи, как необ ходим ость устр ан ять стр ан н ы е ошибки... но
благодаря об р аб о тке и скл ю чений вы смож ете нап и сать код, кото­
рый сам будет р азби рать ся с возм ож ны м и проблем ам и.
мобильные программы, мобильные проблемы

Брайану ну>кнЬ1мобильные опрабдания


Н е д а в н о Б р а й а н п е р е ш е л в м е ж д у н а р о д н ы й отд ел и т е п е р ь о н п у те ш е ­
ств у е т п о всем у миру. Н о ему п о -п р е ж н е м у п р и х о д и т с я о п р а в д ы в а ть с я ,
п о э т о м у о н у с т а н о в и л н а п и с а н н у ю н а м и п р о гр а м м у н а н о у тб у к .

Программу для
ге нерац и й оправданий
установил на
^ ґ ^ о у т б у к , и она
Г осегда под рукой.
Старит
оп я т ь и щ е т побод
сбежать с работы

Но программа не работает!
П о с л е щ е л ч ка н а к н о п к е R a n d o m E xcuse (С л у ч а й н о е о п р а в д а н и е ) п о ­
я в л я е тс я с о о б щ е н и е о б о ш и б к е . О п р а в д а н и я н е н а й д е н ы ? К а к ж е так?

ІЖШЄ Manager

UnharidledetcefrtionhBsocainBdiiyotK-apf^c^jn. If you «Л*


Corttwe. Л е ар(йс0*іоп wffl іщтоге We « w and rtlempt to oorthue. ff
you eW t Оій, the aw^icatism w l dose immeSatdy.

W ee was oijside the ixsunds of the atray.

Иеобработанное 0Ш
и с к л ю ч е н и е ... эту
проблему МЫ не
учли.

450 глава 10
обработка исключений

Возьми в руку карандаш


Вот пример неработающего кода. Справа вы видите сообщения о пяти
исключениях. Укажите, какие строки кода стали причиной появления
этих сообщений.

p u b lic sta tic v o id B e e P r o c e s s o r {) {


М е т о Э d ow W e.P arseC 'S Z )
o b j e c t myBee = new H o n e y B e e ( 3 6 . 5 , " Z ip p o " );
flo a t howM uchHoney = (flo a t)m y B ee;
CO зн(яченм ем 32..
H oneyBee a n o th e r B e e = new H o n e y B e e ( 1 2 . 5 , "B uzzy")
d o u b le beeNam e = d o u b l e . P a r s e (a n o th e r B e e .M y N a m e );

d o u b le to ta lH o n ey = 3 6 .5 + 1 2 .5 ;
s t r i n g beesW eC anFeed =
for (in t 1 = 1 ; i < (in t) to ta lH o n ey ; i+ + ) {
b e e s W e C a n F e e d += i . T o S t r i n g ( ) ;
; \ OvertlowException was unhandled (J
Vdue was ather too large or too sma# for a Single.
}
flo a t f =
f l o a t . P arse(b eesW eC an F eed );
i^ NullReferenceException was unhandled
in t drones = 4;
I Object refererrce not set to m hstance of an object
in t queens = 0;
in t dronesP erQ ueen = d ro n es / queens;

an oth erB ee = n u ll; t \ I nvaHdCastExceptlon was unhandled


у / if (d r o n e sP e rQ u e en < 10) { j SpecifledcastisnotvaW.
[ a n o th erB ee.D o M y J o b O ;

\ ' i DivldeBvZeroException was unhandled


\ Присвоение 1 Attempted to dMde by zero.
\ ссылке
значения nul! Troubleshooting tips;
означает^ что Ea!s.§...M§_..!b§..Yalue,of.^e je n o [ riia t o [ J s not гего
она никуда не G et general help for thiis exception,
ведет. , -v'
Search for more Help O tiine,,.

FomiatException was unhandled


I Input string was not in a correct format
Troubleshooting tips;
[Make sure your..m e#gia! 3 u m e n t ^ e Jn,,tii^
W hen converting a string to DateTime, parse the string to take the date before putting each variable into the DateTime object,
Get general help for this exception.
j Search for more Help Online,,.

дальше > 451


вопреки правилам

Возьми в руку карандаш


Решение В о т какие строки кода стали причиной
появления сообщ ений о б ошибках.

o b j e c t myBee = n ew H o n e y B e e ( 3 6 . 5 , " Z ip p o " );


flo a t howM uchHoney = (flo a t)m y B ee;
клнзченмм InvalidCastBxception.

t l \ InvafidCastException was unharKfled


Spedfied cast is not vatel.

н у ж н о передать
H oneyB ee a n o th e r B e e = new H o n e y B e e ( 1 2 . 5 , "Buzzy") 6 определенном ф орм а-
'лле. При эт ом мет од не зна-
d o u b le beeNam e = d o u b l e . P a r s e ( a n o th e r B e e .M y N a m e ); , ет , как преобразовать строки
Виггу в число. Это и становится
о С Г л сообщ ениГ

.1 \ FwmatEKceptlQn was unhancRed


Input string was not h a correct format
Troubleshooting tips;
I '—....... ............ — .-------------------- :—
j When converting a string to DateTime, parse the string to take the date before putting each variable into the DateTime object m
•I^Get general help for this except]on, v
Search for more Help O rine ..,

Ц и кл for создает строку beesVJeCanFeed.


d o u b le to ta lH o n ey = 3 6 .5 + 1 2 .5 ; содержаилцю число, которое состоит
str in g b e e s W e C a n F e e d = "" ; более ‘^ е Т и з 6 0
float невозможно присвоить с т м ь
for (in t i = 1; i < (in t) to ta lH o n ey ; i+ + ) { больилое число, именно поэтому мы
видим исключение OverflowException.
b e e s W e C a n F e e d += i . T o S t r i n g ( ) ;

}
flo a t f = f l o a t . P arse(b eesW eC an F eed );
Разумеется, все эти исключения
появляются по очереди, программа
выводит первое из них и останавливается.
OverflowException was uniiandled Второе исключение вы можете увидеть,
Vakje was either too large or too smd for a Single. только после того как исправите ошибку,
приведшую к первому исключению.

452 глава 10
обработка исключений

in t drones = 4;
Увидеть такое сообщение
in t queens = 0; об ошибке очень легко.
in t dronesP erQ ueen = d ro n es / queens;
Достаточно поделить
любую переменную на ноль.

[ Ж MwkleByZeroException was unhandled


Attempted to dvide by zero.
Troubleshooting tip s;
Make sure the value o f the denominator is not zero before perform ing a division operation ,.
Get general help for this exception. i
Search for more Help O nlne.,,

Чтобы предот врат ит ь появление ошибки, до- U\


статочно проверить значение парамет ра queens, |
перед т е м как делить на него переменную drones.

Присвоив ссылке anotherBee значение


null, вы дали понять компилят ору,
anoth erB ee = n u ll;
что она никуда не ведет. И склю ­
if (dronesP erQ ueen < 1 0 ) { чение NullReferenceException — это
a n o t h e r B e e . DoM yJob{
способ, которым сообщает вам
_^об от сут ст вии объекта, метод
} PoMyJobQ которого вы вызываете.

NuBteferenceException was unhandied


Object refeence not set to an instance of an object
i
О ш и б ка D lv ld e B y Z e r o в ооб щ е не д о л ж н а появ л ять ся. В едь ее м ож но за м е ­
ти ть, просто посм отр ев на код. В прочем , то ж е сам ое м ож но сказать и про
остал ь ны е искл ю чения. Все эти о ш и бки пред отв ращ аем ы . Чем бо ль ш е вы
узнаете об и скл ю чениях, тем м еньш е о ш и б о к б уд ет в в аш их програм м ах.

дальше ► 453
арахисовая карамель

Объект Exception
И т а к , в ы у зн а л и , к а к и м о б р а зо м .N E T с о о б щ а е т вам,
ч т о с п р о гр а м м о й ч т о -т о н е та к , — э т о и с к л ю ч е ­
н и я ( e x c e p tio n ). П р и и х п о я в л е н и и создается о б ъ ­
ект, к о т о р ы й н а зы в а е тся , к а к н е с л о ж н о до га д а ть ся. ис-клю-че-ние, сущ.
E x c e p tio n . человек или вещь, выда­
П р е д с та в ь те м а сси в и з ч е т ы р е х эл е м е н то в . А т е п е р ь ющиеся из общего ряда,
п о п ы т а й т е с ь п о л у ч и т ь д о с т у п к э л е м е н ту н о м е р ш е с т­
не подчиняющиеся пра­
н а д ц а ть (с и н д е к с о м 15, т а к к а к о т с ч е т ве де тся с н у л я ):
Очевидно,
вилам. Джим ненавидит
i n t [ ] апАггау = {З, 4 , 1 , что это арахисовое масло, но дела­
11}; ошибочный
код. ет исключение для конфет
i n t aV alue = апА ггау[ 1 5 ] ; от Кена.

Д л^ просмотра подроб­
ной информации щелкните
При возникновении на строчке View Detail 6 окне
1ясключения создается ^ с сообщением о необработан-
объект, содержаш,ий ■КТ Ехс«'^ н о м исключении. ф
сведения об ошибке.
Detail

Exceptionsnapshot
е строке Message л System.Ind€3cOutDffengeException {"Ind©( wasoutsidethe bounds of the array."} |
обьясняетсй смысл [System,b\dexOtJtOfRengeException] {"Indexwasoutsidethe boundsof thearray,”} 1
^ Data {System.Col!ectior\s.ListDictiDnaf>^nternal}
ошибки, а также HefpLink
с о Э е р ж и т с я список веек nutt
Inr>erExceptior> nuff
обращений к памяти, ытзт
к событию, ■ H Indexwas outsidethe bounds of thearray.
Source BrokenCodeExceptions
ставшему причинои StackTrace at Broken_Code_Excepttons.Program.BeeProcessor1
ошибки. _____ i> TargetSite {Void BeeProcessorO}

...J

.N E T создает о б ъ е кт, ч т о б ы п р е д о с т а в и т ь вам и н ф о р м а ц и ю о б о ш и б к е , с та в ш е й п р и ч и н о й и с к л ю ч е н и я .


М о ж е т п о т р е б о в а т ь с я р е д а к т и р о в а н и е ко да и л и д р у ги е и з м е н е н и я .

В д а н н о м случае и с к л ю ч е н и е IndexOutOfRangeException ук а з ы в а е т н а п о п ы т к у о б р а т и т ь с я к эле м ен­


т у м асси ва с несущ ествую ы цим и н д е к с о м . К р о м е т о г о , в ы зн а е те , в к а к о м м есте п р о гр а м м ы в о з н и к л а
п р о б л е м а . Т о е сть в ы м о ж е т е л е гк о л о к а л и з о в а ть ее д аж е в случае о ч е н ь д л и н н о г о кода.

454 глава 10
обработка исключений

Чаапо

за д а в а ем ы е
B o r ij= » o c:b i

Почему исключений так много? То есть исключения придуманы, чтобы помочь


пользователям?
! Существует много способов сделать ошибку. В случае
оНщей формулировки («Проблема в строчке 37») сложно 0 ; Да! Большинство пользователей расстраиваются при
понять смысл проблемы. Ошибку проще исправить, когда виде сообщения об исключении. Но эти сообщения нужно
точно знаешь, в чем она заключается. воспринимать как помощь в отслеживании ошибок.

Б• Так что же такое исключение? Правда ли, что появление исключения вовсе не
означает, что я сделал что-то не так?
Q ; Это объект, который .NET создает в случае возникно­
вения проблем. Впрочем, вы и сами можете создавать такие ^ ; Правда. Иногда данные ведут себя не так, как вы
объекты (об этом мы поговорим чуть позже). ожидаете, например, можно написать метод, который будет
работать с массивом иной длины, чем изначально пред­
Что? Это объекты? полагалось. Следует помнить, что пользователи действу­
ют непредсказуемым образом. Благодаря исключениям
программы не останавливаются в нетипичных ситуациях,
^ ! Да, исключение — это объект. Свойства объекта сооб­ а продолжают работу.
щают вам информацию об исключении. Например, свойство
M e s s a g e — ЭТО строка вида «Указанное присвоение неосу­
ществимо» или «Слишком большое или слишком маленькое После сообщений об ошибках стало ясно, что код
значение для переменных данного типа», которая появляет­ на предыдущей странице работать не будет. Всегда ли
ся во всплывающем окне. Вам дается максимум возможной исключения позволяют понять, что происходит?
информации о том, что именно происходит при выполнении
оператора, который стал причиной исключения. ^ ! к сожалению, иногда локализовать проблему, просто
посмотрев на код, невозможно. Поэтому в ИСР существует
К сожалению я все равно не понял, зачем нужно отладчик. Он выполняет программу строчка за строкой,
такое количество исключений? оператор за оператором, показывает мгновенное значение
каждой переменной и каждого поля. Это позволяет обнару­
жить, где именно код работает не так, как вы предполагали.
Q ; Потому что способов некорректной работы кода вели­
кое множество. Существуют ситуации, в которых код просто
перестает работать. Не зная, что стало причиной, устранить
проблему крайне сложно. Создавая разные исключения,
.NET дает вам информацию, позволяющую отследить ошибку
и исправить ее.
Исключения помогают
обнаружить и исправить
код, который работает не
так, как вы думали.

дальше > 455


никто не ожидал, что...

Код Брайана работает не так, как предполагалось


К о гд а м ы п и с а л и п р о гр а м м у для п о и с к а о п р а в д а н и й ,
н и к т о н е п р е д п о л а га л , ч т о п о л ь з о в а те л ь н а ч н е т и с ­
к а ть о п р а в д а н и я в п у с т о й п а п ке .

О П о с л е п е р е н е с е н и я п р о гр а м м ы E xcuse M a n a g e r н а н о у тб у к , о н а стала
с сы л а ть с я н а п у с ту ю папку. В и т о г е щ е л ч о к н а к н о п к е R a n d o m п р и в е л
к по я в л е н и ю о кн а с сообщ ением об необ работанном и скл ю ч е н и и :

Excuse M anager

IW ieidedare^itiw hasoccunBdhyoiffappteatton. fyoucW c


Cortmue. the вдрИсайда igrasne this w w aid Лепфі to conttiue. tf
© you d ck О Л. tfie адЛсгйоп чЛ dose іптаесіійеіу.

W e t was w iade Йш boimds trf the апау.

M ato
□ Ccrtmje ал

О О к н о со о б щ а е т, ч т о и н д е к с в ы ш е л за г р а н и ц ы м ассива. П о с м о т р и м н а
ко д для о б р а б о т ч и к а с о б ы т и й к н о п к и R a n d o m Excuse:

p r iv a te v o id r a n d o m E x c u se _ C lic k (o b je c t sen d er, E ven tA rgs e) {


if (C h eck C h an ged 0 ) {
cu rren tE xcu se = new E x c u s e (r a n d o m , se le c te d F o ld e r );
U p d a te F o r m (fa lse );
}
}

М а с с и в о в т у т нет. З а то п р и п о м о щ и о д н о г о и з п е р е гр у ж е н н ы х к о н ­
с т р у к т о р о в создается о б ъ е к т E x c u s e . М о ж е т б ы т ь , м а сси в с о д е р ж и т с я
в ко де э т о г о к о н с т р у к т о р а ?

p u b lic E xcuse(R an dom random , str in g F o ld er ) {


str in g !] file N a m e s = D ir e c to r y .G e tF ile s(F o ld e r , " * .ex cu se
O p e n F ile (file N a m e s[r a n d o m .N e x t(file N a m e s.L e n g th )] ) ;
В о т оно! М ы н а ш л и
}
м ассив. Д ол ж н о бы т ь,
м ы попы т ались ука
з а т ь ин декс, вы ходя-
щым з й е г о г р а н и ц ы .

456 глава 10
обработка исключений

О ка зы в а е тс я , м е то д D i r e c t o r y . G e t F i l e s { ) , п о с л е т о г о к а к в ы
указали н а п у с т у ю папку, ста л в о з в р а щ а ть п у с т о й м ассив. И с п р а в и т ь
ситуац ию м о ж н о , добавив п р о в е р к у с о д е р ж и м о го п а п к и перед о т­
к р ы т и е м ф айла. И в м е с то о к н а с и н ф о р м а ц и е й о н е о б р а б о т а н н о м и с ­
к л ю ч е н и и будет п о я в л я т ь с я о к н о с с о о б щ е н и е м .

p r iv a te v o id r a n d o m E x c u se _ C lic k (o b je c t sender, E ven tA rgs e) {


str in g [] file N a m e s = D ir e c t o r y .G e t F ile s ( s e le c t e d F o ld e r ," * .e x cu se " );
if ( f i l e N a m e s . L e n g t h == 0) {
M es sa g e B o x .S h o w (" P le a s e s p e c i f y a fo ld e r w ith ex cu se file s in it'
"No e x c u s e file s fou n d " );
} e lse { Проверив наличие
айлов с. оправданиял/{14

Г
if (C h eck C h an ged 0 == t r u e ) {
C u rren tE xcu se = new E x c u s e ( r a n d o m , F o ld er )
папке ^ создания
о6ъект 1^ Е хси 5е, м ы
U p d a te F o r m (fa lse );
предотвращаем
появление сообщения
об исключении —
и вызываем окно
с вспомогательной
информацией.

Я понял, что исключения — это не всегда


плохо. П ор ой они указывают на ошибки, хотя
в большинстве случаев мне просто сообщается,
что все идет не так, как я думал.

И м енно так. И скл ю чени я явл яю тся полезны м


инстр ум ентом , которы й н ахо д и т код, раб отаю щ и й
н еож ид анны м д л я нас способом .
М н о г и е п р о гр а м м и с т ы р а с с т р а и в а ю т с я , к о гд а в п е р в ы е стал­
киваю тся с исклю чением . Н о искл ю чени я м ож но превратить
в п р е и м у щ е с тв о . В едь о н и д а ю т к л ю ч к п о н и м а н и ю п р и ч и н н е ­
в е р н о й р а б о т ы кода. И в ы м о ж е т е р а з р а б о та ть н о в ы й , более
у д а ч н ы й с ц е н а р и й п р о гр а м м ы .

дальше ► 457
генеалогия исключений

исключения наследуют о т объекта Exception


в .N E T сущ е ствуе т м н о ж е с т в о и с к л ю ч е н и й . Т а к к а к м н о ги е и з н и х с х о ­
ж и , и м е е т см ы сл г о в о р и т ь о н а с л е д о в а н и и . .N E T о п р е д е л я е т б а з о в ы й
класс E x c e p t i o n , о т к о т о р о г о и н а с л е д у ю т все т и п ы и с к л ю ч е н и й .

С в о й с т в о M e s s a g e э т о г о класса с о д е р ж и т с о о б щ е н и е об о ш и б к е . А с в о й ­
с т в о S t a c k T r a c e указы вает, к а к о й к о д в ы п о л н я л с я в м о м е н т п о я в л е н и я
и с к л ю ч е н и я и ч т о и м е н н о п р и в е л о к е го п о я в л е н и ю .

Exception
Метод ToStringO Message
сум м ирует всю StackTrace

информацию GetBaseExceptionO
ToStringO
из полей объекта
и возвращает ее
в виде строки.

ции. Прочитав т екст


исключения, вы
можете понять, что
именно идет не так.

458 глава 10
контрольное значение

Поиск ошибки 6 п|>ило}кении Excuse Manager с помощью отладчика


В о сп о л ь зуе м ся о тл а д ч и к о м для п о и с к а о ш и б к и в п р и л о ж е н и и E xcuse
M a n a g e r. В п р е д ы д у щ и х гл авах вам уж е п р и х о д и л о с ь р а б о та ть с э ти м
и н с т р у м е н т о м , те м н е м ен е е р а с с м о т р и м п р о ц е д у р у п о ш а го в о , ч т о ­
б ы н е у п у с т и т ь н и к а к и х дета ле й.
^Отладьте I
Т о ч ка останова у обработчика собы тий кнопки Random ^
В ы зн а е те , с ч е г о н а ч а ть: и с к л ю ч е н и е п о я в и л о с ь п о с л е щ е л ч к а н а к н о п к е R a n d o m E xcuse. П о э ­
то м у о т к р о й т е к о д э т о й к н о п к и , п о м е с т и т е к у р с о р в п е р в у ю с т р о ч к у м е то д а и в ы б е р и т е в м е н ю
D e b u g к о м а н д у T o g g le B re a k p o in t. З а п у с ти т е п р о гр а м м у. В ы д е л и т е п у с ту ю п а п к у и щ е л к н и т е на
к н о п к е R a n d o m для п е р е х о д а к т о ч к е о с та н о в а :

private void randoraExcuse_Click(object sender, EventArgs e)


{
Ш І fileWawes = Directory.6etFiles(selectedFolder, "‘.excuse“)
if (fileNames.Length == #) у f ier4ames.Length == 0 true
I
{
-Наведите ука ­
MessageBox.Sho«("Please specify a folder with excuse files in it", зат ель мыши
"No excuse files found”); на свойство
} f i i e N a m e s .L e n g t h ,
else а когда появит ­
{ ся oкиoJ щ елк­
if (CheckChanged0 ) ните на кноп­
ке со значком
{ скрепки, чтобы
currentExcuse « new Excuse(random, selectedFolder)j его зафиксиро­
UpdateForra(false); вать.
}

О бработчик собы тий и конструктор Excuse


В о с п о л ь з у й те с ь к о м а н д о й S te p I n t o для п р о с м о т р а п р о гр а м м ы с т р о ч к а за с т р о ч к о й . Т а к к а к в ы
в ы д е л и л и п у с ту ю п а п к у в ы у в и д и т е , ч т о п о с л е в ы п о л н е н и я д и р е к т и в ы M e s s a g e B o x . S h o w O
п р о изой д ет вы ход из обработчика соб ы тий .

Теперь в ы д е л и те папку, с о д е р ж а щ у ю ф а й л ы о п р а в д а н и й , и снова щ е лкн и те на кн о п ке


R a n d o m . П о ш а го в о п р о с м о т р и т е ко д. (В ы д о л ж н ы п о л ь з о в а ть с я ф у н к ц и е й S tep In t o , а не Step
Over, х о т я и и м е е т см ы сл о б о й т и м е то д C h e c k C h a n g e d ( ) . ) П е р е д со зд а н и е м о б ъ е к та E x c u s e
у п р а в л е н и е будет п е р е д а н о к о н с т р у к т о р у О б о й д и т е п е р в у ю с тр о ч к у , т а к к а к в н е й задается
з н а ч е н и е п е р е м е н н о й f i l e N a m e s v a ria b le . З атем н а в е д и те указател ь м ы ш и н а п е р е м е н н у ю ,
ч т о б ы у в и д е ть ее з н а ч е н и е .

460 глава 10
обработка исключений

Работа с отладчиком
П е р е д те м к а к д о б а в л я ть о б р а б о т к у и с к л ю ч е н и я , н у ж н о п о н я т ь , к а к и е
и м е н н о о п е р а т о р ы с та л и п р и ч и н о й е го п о я в л е н и я . В э т о м вам п о м о ж е т
в с т р о е н н ы й в И С Р о т л а д ч и к . Вам уж е п р и х о д и л о с ь и м п о л ь з о в а ть с я , н о Панель инст румент ов
д ава й те т е п е р ь р а с с м о т р и м е го п о д р о б н о . П о с л е е го за пуска п о я в л я е тс я Debug появляется только
п а н е л ь и н с т р у м е н т о в с к н о п к а м и . П о о ч е р е д и н а в е д и те к у р с о р н а ка ж д ую в режиме отладки п р о ­
граммы.
и з н и х и п о с м о т р и т е н а результат:

Шаг с обходом: выполняет


следующий оператор. Методы
выполняются как один Включает и выключает
Остановить отладку: оператор шестнадцатеричный вывод
выходит из режима
отладки
Продолжить: Вывод: показывает значение
выполняет программу всех локальных переменных,
до следующей точки ‘л"} Ф «1 Сз % } Не* - г которые находятся в памяти
останова или конца
Шаг с выходом: запускает
все остальные операторы
текущего метода и прерывает
Показать следующий Шаг с заходом: выполняет выполнение, когде они
оператор следующий оператор. заканчиваются
В методе выполняется
лучше всего первый
оператор

Bug панели Debug 6 ре)киме Expert


П о у м о л ч а н и ю V is u a l S tu d io 2 010 E xpress н а х о д и т с я в р е ж и м е B asic S e ttin g s, к о т о р ы й за м е ч а те л ь н о п о д ­
х о д и т д ля н а ч и н а ю щ и х . Н о т е п е р ь д а в а й те п е р е й д е м к р а с ш и р е н н ы м п а р а м е тр а м . Д л я э т о г о в ы б е р и т е
в м е н ю T o o ls к о м а н д у S e ttin g s » E x p e r t S e ttin g s (п е р е х о д в д р у г о й р е ж и м м о ж е т з а н я ть н е к о т о р о е
в р е м я ). П о с л е э т о г о н а п а н е л и и н с т р у м е н т о в D e b u g в ы о б н а р у ж и т е две н о в ы е к н о п к и :

Перезапустить: останавливает
Прервать все: прекращает работу программы и начинает ее
работу программы, как при сначала
достижении точки останова

Шестнадцатеричный Вывод
Н а ж м и т е H e x для в к л ю ч е н и я р е ж и м а ш е с т н а д ц а т е р и ч н о го в ы в о д а и н а в е д и те к у р с о р н а л ю б о е п о л е
и л и п е р е м е н н у ю . С н о в а щ е л к н и т е н а к н о п к е для в о з в р а щ е н и я в о б ы ч н ы й р е ж и м . П р е о б р а з о в а н и е зна­
ч е н и я в ш е с т н а д ц а т е р и ч н у ю с и с те м у с ч и с л е н и я будет о с у щ е с тв л е н о а в т о м а т и ч е с к и . В п р о ш л о й главе
в ы у зн а л и , зачем э т о н у ж н о .
Это одно число, слева
¥ va(uel 0 k 3 sfb 8 3 d9
в шестнадцат еричной, ------ ^

дальше * 459
обработка исключений

О Работа с окном W a tc h
Щ елкните правой кнопкой м ы ш и на перем енной file N a m e s и вы берите ко м а н д у
E xpression: ‘fileN am es’ » Add Watch. З атем щ елкните на пустой строчке под перем ен­
ной fileN am es и введите r a n d o m . N e x t ( f i l e N a m e s . L e n g t h ) , ч т о б ы о т л а д ч и к д о б а в и л
к о н т р о л ь н о е з н а ч е н и е . В о т к а к будет в ы гл я д е ть о к н о W a tc h для п а п к и с т р е м я ф а йл а м и
о п р а в д а н и й (к о гд а п е р е м е н н а я f i l e N a m e s и м е е т д л и н у 3).

Окно Watch использует ­ W a tch


ся для воспроизведения^
Füaäi' Value T ype
ситуации, приведылей ''
к появлению исключения- a
М ы н а ч а л и с добавления random,Next(fileNames.Length) j2 int
массива fileNames.

П р и с в о и м перем енной file N a m e s пустой строковый массив


О к н о W a tc h п о з в о л я е т менять значения о то б р а ж а е м ы х п е р е м е н н ы х и п о л е й . В ы даж е м о­
ж е т е выполнять методы и создавать новы е объекты , в э т и м о м е н т ы п о я в л я е тс я з н а ч о к
( ® ) , ш ;елчок н а к о т о р о м п р и в о д и т к п о в т о р н о м у в ы п о л н е н и ю с т р о к и . В едь м е то д R a n d o m
к а ж д ы й раз дает д р у г о й результат.

Д важ д ы щ е л к н и те н а с т р о ч к е f i l e N a m e s , ч т о б ы в ы д е л и ть т е к с т { s t r i n g [3 ] } . З а м ените
е го н а n e w s t r i n g [ 0 ] . Т ут ж е и сч е з н е т к в а д р а ти к со зн а ко м «плю с» ря д о м с п е р е м е н н о й
f i l e N a m e s , т а к ка к т е п е р ь э т о т м ассив пуст. А ря д о м со с т р о ч к о й r a n d o m . N e x t {) п о я в и тс я
значок {Щ. Щ е л к н и т е н а нем , ч т о б ы ещ е раз в ы п о л н и т ь м етод. О н д о л ж е н в е р н у ть 0.

Так как ист оч­ Эт от значок


ником проблемы W a tch инициирует
является пустой N am e Value повторное
массив fileNames, выполне-
leN am es {string[0]} b ! ^ in § D ние метода
мы смоделируем
эт у ситуацию Ф rand o m .N e xt{file M am e s.Le n gth ) int NextQ.
в окне Watch.

^ Воссоздание пробледж ой си туац и и


Д об авьте в отл адчик строку с оператором , ко торы й ста л причиной исклю чения:
f i l e N a m e s [ r a n d o m . N e x t ( f i l e N a m e s . L e n g t h ) ] . Э т о в ы р а ж е н и е будет о б с ч и т ы в а т ь с я
в о к н е W a tc h ... и в ы у в и д и т е с о о б щ е н и е о б и с к л ю ч е н и и . С лева п о я в и т с я з н а ч о к с в о с к л и ­
ц а те л ь н ы м з н а к о м , а в с то л б ц е V a lu e — т е к с т и с к л ю ч е н и я .

’Г □ X
W atch
Э т о т значок Value Type
Name
в окне Watch
сообш,ает об *: fiieName$ {sfringfOl} stringD
обнаружении

# random .N extPeN am es.Lengti) 0 Ф tnt
исключения. ^"^lieNames[randQm.Next{f»eNames.Lengtti)] Out ofbounds array index sfring

В ы в с егд а м о ж е те в о с п р о и зв е с ти с п о м о щ ь ю о тл а д ч и ка с и ту а ц и ю , с т а в ш у ю п р и ч и н о й
появления исклю чения.
дальше > 461
сделаем перерыв
Часщо

задаваем ы е
B o IlJ > o C b i

Почему сообщение о необрабо­ Как выбрать место для точки у» То есть я могу запустить в окне
танном исключении, которое получил останова? I /а!сЬ что-то, меняющее способ рабо­
Брайан, отличается от моего? ты программы?
Q ; Это хороший вопрос, который, к со­
Q ; Запуская программу в ИСР, вы жалению, не имеет однозначного ответа. 0 ; Да! Пусть не всегда, но здесь вы
работаете из отладчика, который при При появлении исключений, имеет смысл можете влиять на конечный результат
обнаружении исключения прерывает начинать с операторов, которые стали их работы программы. Более того, даже на­
выполнение (как при нажатии кнопки причиной. Но обычно проблема гнездится ведение указателя мыши на поля вну­
Break АН или при вставке точки останова). в предьщущих строках кода, а исключение три отладчика может поменять поведение
При этом появляется окно с сообще­ является не более чем последствием, программы, так как при этом выполня­
нием. Это позволяет исследовать объект к примеру, оператор, ставший причиной ется метод чтения свойства. И если
E x c e p t i o n , а также поля и переменные появления исключения «Деление на этот метод задает какое-то значение, оно
вашей программы, и понять, в чем именно ноль», может использовать значения, вы­ перейдет в программу. Такое поведение
состоит проблема. численные десятком строк ранее. Поэтому делает результаты отладки непредсказу­
ответ на вопрос о месте точки останова емыми. Программисты в шутку называют
Программа, с которой работал Брайан, в каждом конкретном случае будет такие результаты гейзенбергскими (эта
была установлена на его компьютер. отличаться. Но если вы представляете шутка понятна только физикам и котам
Помните, вы делали это в главе 1 для принцип работы своего кода, проблемы с в ящике).
приложения со списком контактов? Для выбором места не будет.
запуска программы вне ИСР достаточно
построить решение, то есть получить
Любой метод можно запускать
исполняемый файл, который находится в
в окне Watch?
папке b i n / , вложенной в папку проекта.
После этого вы будете видеть такие же
окна с сообщениями об исключениях, как И ; Да, любые корректные операторы
Зацущенная в ИСР
и Брайан. с д у т работать в окне Watch. Даже те,
которые не имеет смысла запускать. Запу­ профамма при
стите программу, прервите ее выполнение
То есть если программа работает
вне ИСР, с появлением сообщения об
и добавьте в окно Watch строку: S y s t e m . появлении необ­
T h r e a d i n g . T h r e a d . S l e e p ( 2 0 0 0 ).
исключении ее выполнение просто
останавливается, и пользователь ниче­
(Как вы помните, этот метод вызывает
двухсекундную задержку работы про­
работанных исклю­
го не может сделать?
граммы.) Использовать его нет смысла, но
чений прерывается,
Ql Да, программа останавливается,
столкнувшись с необработанным исклю­
интересно посмотреть, что произойдет: на
две секунды, которые занимает выполне­
как при достиже­
ние этого метода, появится изображение
чением. Но ведь нигде не сказано, что все
исключения относятся к необработанным!
песочных часов. Так как метод s l e e p ()
не возвращает значения, в окне Watch нии точки останова.
О способах обработки исключений и о том, появится сообщение. E x p r e s s i o n h a s
каким образом создать программу без со­ b een e v a lu a te d and h as no v a lu e,
общений о необработанных исключениях, информирующее об отсутствии возвраща­
мы поговорим чуть позднее. емых значений.

462 глава 10
обработка исключений

А код Все раВно не работает...


Б р а й а н у с п е ш н о п о л ь зо в а л ся E xcuse M a n a g e r п р и н а л и ч и и п а п ­
к и , н а п о л н е н н о й ф а йл а м и с о п р а в д а н и я м и , к о т о р у ю о н создал
во вр е м я н а п и с а н и я п р о гр а м м ы , н о о н заб ы л п р о э т у п а п к у ие-
с е р и а л и з а ц и е й п р о гр а м м ы . И в о т ч т о с л у ч и л о сь ....

П р и л о ж е н и е Б л о к н о т п о з в о л я е т в о ссо зд а ть ф а й л ы с о п р а в д а н и я м и . П е р ­
вая с т р о к а — о п р а в д а н и е , в т о р а я — р е зульта т е го п р и м е н е н и я , т р е т ь я —
дата е го п о с л е д н е го и с п о л ь з о в а н и я ( « 1 0 / 4 / 2 0 0 7 12:08:13 Р М » ).

© В ы з о в и т е E xcuse M a n a g e r и о т к р о й т е о п р а в д а н и е . П р и п о я в л е н и и и с к л ю ч е н и я ш ;елкните
на к н о п к е D e ta ils . О б р а т и т е в н и м а н и е н а стек вы зова - и м е н н о т а к н а з ы в а е тс я м е то д , в ы ­
з ы в а ю щ и й д р у г о й м е то д , к о т о р ы й , в с в о ю о ч е р е д ь , т о ж е в ы з ы в а е т м е то д и т. д.

Программа сообщает, что исключение появляется при


сериализации. Можно ли из подробного описания поняты.
Excuse M anager " какая строка кода является причиной его появления?
IMimidted
Cortinue, the ************** **************
ушсЛЛОіЛ.Ле Exception Text

Index was outside System.Runtime.Serialization.SerializationException: End of Stream encountered before parsing


was completed.

atSystem.Runtime.Serialization.Formatters.Binary._BinaryParser.Run()

atSystem.Runtime.Serialization.Fon7iatters.Binary.ObjectReader.Deserialize(HeaderHandler
проблема с классом handler, _BinaryParserserParser, Boolean fCheck, Boolean isCrossAppDomain,
BinaryFormatter. IMethodCallMessage methodCallMessage)
Это предположе­
ние имеет смысл, at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream
так как приложение serializationStream, HeaderHandler handler, Boolean fCheck, Boolean IsCrossAppDomain,
пыталось десериа­ IMethodCallMessage methodCallMessage)
лизовать текстовый
файл. atSystem.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream
serializationStream)
Многое можно узнат ь из
г.тека вызова, ведь т ам 'a t Chapter10.Excuse.OpenFile(String ExcusePath) in C:\Documents and Settings\Administrator\
показывается список ^My DocumentsWisua! Studio 2005\Projects\Chapter10\Chapter10\Excuse.cs:line 40
запущенных А/івтодов.
Вы видите, что м е ­ at Chapter10.Excuse..ctor(Random random , String Folder) in C:\Documents and Settings\
тод OpenFileQ класса ^dm inistrator\My DocumentsWisual Studio 2005\Projects\Chapter10\Chapter10\Excuse.cs:line 30
Excuse вызывается его
конструктором (.ctor), ^C hapter10.Form 1.ran dom E xcuse_C lick(O bject sender, EventArgs e) in C:\Documents and
который, в свою очередь, Settings\AdministratortMy DocumentsWisual Studio 2005\Projects\Chapter10\Chapter10\Form1.
вызывается обработ­
csiline 146
чиком событии кнопки
Random Excuse.

© Щ е л ч о к н а к н о п к е D e ta ils п о з в о л я е т м н о го е у з н а ть о п р и ч и н а х в о з н и к ш е й п р о б л е м ы . Есть
идеи, что со всем этим делать?

дальше ► 463
непредсказуемость пользователей

Разумеется, программа должна


была прекратить работу, ведь я дала ей не
тот сршл. Пользователи все время делаю т
что-то не то, и с этим невозможно бороться

На сам ом д е л е б о р оть ся с этим можно.


Разум еется, п о л ь з о в а те л и п о с т о я н н о д е л а ю т ч т о -т о н е
та к . Т а к о в а ж и з н ь . Н о с у щ е с тв у ю т п р о гр а м м ы , с п о с о б н ы е
р а б о та ть с н е в е р н ы м и д а н н ы м и , о ш и б к а м и п р и и х вводе
и д р у г и м и н е о ж и д а н н ы м и с и т у а ц и я м и ; и х н а з ы в а ю т р о­
бастными (robust). И н с т р у м е н т ы о б р а б о т к и и с к л ю ч е н и й
C # д а ю т вам в о з м о ж н о с т ь создавать и м е н н о т а к и е п р о ­
гр а м м ы . Р азум еется, в ы не можете к о н т р о л и р о в а т ь д е й ­
с т в и я п о л ь з о в а те л е й , н о в ы можете га р а н т и р о в а т ь , ч т о
п р о гр а м м а н е п р е к р а т и т работу, ч т о б ы о н и н е делали.

ро-баст-ный, прил. Класс BinaryFormatter со о б щ а ет


прочный; способный руды не I об искл ю чении в сл учае
противостоять неблаго­ o a i l o j = > o ^ H b i ! проблем с сер и а л и зо в ан ­
ны м ф айлом .
приятным условиям.
Получить такое исключение для про­
граммы Excuse Manager проще просто­
го — дайте ей файл, который не является
сериализованным объектом Excuse. Класс
B in a ry F o rm a tte r ожидает, что файл
будет содержать сериазированный обьект
нужного типа. Если же файл содержит что-
то другое, метод D e s e r ia li z e () отобра­
зит исключение S e r ia liz a tio z iE x c e p tio n .

464 глава 10
обработка исключений

Ключевые сАоВа try и catch


П р о и с х о д я щ е е в C # м о ж н о о п и с а т ь ф р а з о й «П ротестируйте (try) э т о т ко д и п р и п о я в л е н и и и с к л ю ч е ­
н и я прервите (catch) е го другим ко д о м » . Т е сти р уе м а я ч а с т ь ко д а н а з ы в а е тс я блоком t r y , а ч а с т ь , о б р а ­
б а ты в а ю щ а я и с к л ю ч е н и я , - блоком c a t c h . В б л о к е c a t c h м о ж н о д о б а в и т ь с о о б щ е н и е о б о ш и б к е , не
давая п р о гр а м м е а в а р и й н о з а в е р ш и ть работу.

private void randomExcuse_Click(object sender, EventArgs e)

__ здесь код, добавленш т несколько страниц назад.

s t r y {

if (CheckChangedО == true) {
currentExcuse = new Excuse(random, selectedFolder);
UpdateForm (false) ;
Ключевое слово catch означаетj что еле-
дующий за ним блок операторов содержит
оораоотчик исключения.

<gjtch ^SerializTtl^nExce'gagig, {

Исключе­ 4essageBox.Show(
ние вызы­
вает не­ "Your excuse file was invalid.",
медленный
переход к
операт о­ "Unable to open a random excuse")
рам блока
catch. J It
"I Это прост ейилий способ
обработки исключения,
остановить программу, ШТУРМ
написать сообщение
и зат ем продолжить Если исключение немедленно передает управление
выполнение программы. операторам блока c a t c h , что происходит с объектами
и данными, с которыми вы работали до этого?

дальше > 465


сомнительные делишки

Вызоб сомнительного метода


П о л ь з о в а т е л и н е п р е д ска зуе м ы . О н и в в о д я т в п р о гр а м м у с т р а н н ы е д а н н ы е , щ е л к а ю т н а к н о п к а х . И э т о
п р е к р а с н о , ведь в ы м о ж е т е с п р а в и т ь с я с п о с л е д с т в и я м и вво да т а к и х д а н н ы х , о б р а б а ты в а я и с к л ю ч е н и я .

( D П редполож им , п о л ь ­
зо в ател ь в в одит не те
данны е.

Вводимые Написанный
Пользователь данные вами класс

М етод д е л а е т что-то p u b lic v o id


P rocess(In p u t i )
странное, что м ож ет и {
if (i.I sB a d O ) {
не сработать во врем я
e x p lo d e ();
прогона.
Написанный }
Временем прогона (Runtime) называется вами класс }
время работы вашей программы. И с­
ключения иногда еще называют «ош иб­
ками при исполнении» (runtim e e rro rs ). Метод ProcessQ
A что будет,
не работает с Л
если щелкнуть
некорректными у
здесь...
Вы д ол ж ны знать,
что данными! ,—
вы зы ваем ы й м етод с о ­
м нителен.

Лучше всего, если вы предусмо­


т рит е обходной пут ь на случай
возникновения исключений! Впрочем Н аП И кл пп ы и
полностью уст ранит ь риск не Пользователь вами класс
получится, поэт ому следует по­ Программа действительно
ст упат ь так. \
стабильна!

( 5 ) в этом сл учае вы см ож ете


V ваша программа

нап и сать код, о б р аб а ты в а­


ю щ ий искл ю чение. Если оно
появится, вы будете готовы .

Ваш класс теперь


Пользователь
умеет обрабатывать
исключения
466 глава 10
обработка исключений

Часто
за д а в а ем ы е
с каждым следующим шагом. Это по­
Б оЦ Р о СЬ!
^ J Так когда используются ключевые зволяет отслеживать, что именно с ними
слова t r y и c a t c h ? происходит после выполнения каждого
Аварийное завершение работы про­ оператора.
Q ; в случаях, когда вы пишете сомни­ граммы при вводе некорректных данных
бесполезно. Нет таюке никакой пользы В окно Watch можно ввести любой
тельный код, который может привести
от попыток читать все вводимые данные. оператор и увидеть его значение. Если
к исключениям. Сложнее всего понять,
А вот появление окна с сообщением о не­ оператор влияет на значения полей
что код является сомнительным.
возможности прочитать файл позволяет и переменных, значит, он будет выпол­
принять верное решение. нять еще и эту функцию. Это позволяет
Вы уже видели, что сомнительную
менять параметры, не прерывая работу
работу кода может инициировать ввод
То есть отладчик нужен для поиска программы, что дает вам еще один
неверных данных. Пользователи могут
инструмент для отслеживания дефектов
выбирать не те файлы, вводить буквы причины исключений?
и исключений.
вместо цифр и имена вместо дат, а еще
они нажимают все кнопки, до которых г ; Нет. Вы уже не раз видели, что от­
могут дотянуться. Хорошая программа ладчик работает с любым кодом. Иногда
должна работать предсказуемо вне
Редактирование данных в окне
имеет смысл пошагово просмотреть VJatck влияет на их состоя­
зависимости от вводимых данных. Поль­ значения определенных полей и перемен­ ние в памяти на время рабо­
зователь может не получить нужного ных — именно так можно гарантировать ты программы. Д л я возвра­
результата, но по крайней мере он узна­ корректную работу сложных методов. щения переменным исходных
ет о наличии проблемы и ознакомится значений достаточно переза­
с предложенным решением. Но основным назначением отладчика грузит ь программу.
является поиск и устранение дефектов
А как можно предложить решение программы. Исключения также относятся
проблемы, о существовании которой к дефектам. Но отладчик решает пробле­
заранее неизвестно? мы и другого рода, например, поиск кода,

Q ; Для этого вам потребуется блок


дающего непредсказуемый результат.
Блок catch выпол­
c a t c h , который выполняется только
после того, как блок t r y выдаст ис­
Я не совсем понимаю принцип
работы с окном Watch.
няется только при
ключение. Вы даете пользователю сигнал:
что-то идет не так и ситуацию можно Q ; в процессе отладки обращается вни­
обнаружении ис­
и нужно исправить. мание на значения определенных полей
и переменных. Именно для этого нужно
ключения в блоке
окно Watch. Переменные, к которым
были добавлены контрольные значения,
try. Это дает воз­
обновляют свои значения в окне Watch
можность снабдить
пользователя ин­
формацией о путях
решения проблемы.

дальше > 467


плывем по течению

Результаты применения ключебых слоб try/catch


С ледует п о м н и т ь , ч т о п р и о б н а р у ж е н и и и с к л ю ч е н и я в б л о к е t r y
о с та л ь н а я ч а с т ь ко д а и г н о р и р у е т с я . П р о гр а м м а н е м е д л е н н о пере-
О ш Л а Д ь ш е эщ о !
х о д и т н а п е р в у ю с т р о ч к у б л о к а c a t c h . Впрочем, вы можете не ее-
рить нам на слово... ^

В е сь п р е д о с т а в л е н н ы й в э т о й главе к о д н у ж н о в с т р о и т ь в о б р а б о т ч и к с о б ы т и й к н о п к и
R a n d o m E xcuse. Т о ч к у о с т а н о в а следует п о м е с т и т ь в п е р в у ю с тр о ч к у . И з а п у с ти ть п р о гр а м ­
му. Щ е л к н и т е н а к н о п к е F o ld e r и в ы б е р и т е п а п к у с е д и н с т в е н н ы м ф а й л о м в н е й . У б е д и те сь ,
ч т о э т о некорректны й файл с оправданием (н е с м о т р я н а р а с ш и р е н и е .e xcuse). Щ е л к ­
н и т е н а к н о п к е R a n d o m E xcuse, а затем ш е с т ь раз щ е л к н и т е н а к н о п к е Step O v e r (и л и н а ­
ж м и т е F 10) для п е р е х о д а к о п е р а то р у , в ы з ы в а ю щ е м у к о н с т р у к т о р E x c u s e . В о т к а к д о л ж е н
в ы гл я д е ть ва ш э к р а н н а э т о м этапе:

p riv a te v o id ra n d c n B E x c u s e _ C lic k (o b je c t s e n d e r^ E v e n tA rg s e )

точка
Э т о {
останова, кот о­ s trin g [j file n a m e s = P ir e c to r y .6 e tF ile 5 (s e le c te d F o Irfe r. " * .e x c o s e " ) j
рую МЫ поме - if (file N a is e s .L e n g th == 0 )

стили в первую
строчку одра - ~~ M e s s a g e B o x .S h o w C ’P l e a s e s p e c ify a f o l d e r w ith ex cu se f i l e s in i t ”,
ботчика событий. "H o e x c u s e file s fo u n d ");
}
try

{
[if (CheckChanged{) =« tr u e )
{
c u rre n tE x c u s e = new E x c u s e (r a n d a ® , s e le c te d F o ld e r)j
Нажимайте Рй-О, U p d a te F o rre (fa ls e );
пока не дойдете } Используйте команду Step
до строки, пред­ } Over (F io) для обхода м е -
шествующей ^ c a tc h (s e ria iiz a tio n e x c e p tio n ) moda Ch.eckCkanged().
созданию объек­ {
та Excuse. M e s s a g e B o x . Show(
"Y our e x c u se f i l e w as i n v a l i d . ” ,
" U n a b le t o open a ran d cM e x c u s e " ) ;
}

© Н а ж м и т е F I 1 для п р о с м о т р а о п е р а то р а new . ОтлаДник. п о м е с т и т ж е л ту ю п о л о с к у над с т р о к о й


о б ъ я в л е н и я в ко де к о н с т р у к т о р а E x c u s e . Н а ж м и т е F11, ч т о б ы п о п а с ть в м етод O p e n F ile ()
и п о с м о тр е ть , ч т о с л уч и тся н а с т р о ч к е D e s e r i a l i z e { ) .

^jublic Excuse(Random random, string folder)


После оператора {
new отладчик на­ string[] fileNames = D i r e c t o r y . GetFiles(folder, " » .e x c u s e " ) ;
чинает просм ат ри­
OpenFile(fileNanies [random,Next(fileNaraes.Length) ] );
вать код констрик­
тора. >

468 глава 10
обработка исключений

О П о с л е в ы п о л н е н и я о п е р а т о р а D e s e r i a l i z e () п о я в и т с я и с к л ю ч е н и е , и п р о г р а м м а , п р о ­
и гн ори р ов ав вы зов м етод а U p d a te F o r m ( ) , перей дет непосредствен но к блоку c a tc h .

private void randOTiExcuse_Click(object sender, EventArgs e)


{
Stringt] fileWaiaes = Directory.6etPiles(selecteelFolder, "•.excuse");
if (fileNames.Length == 0)
{
>5essageBox.Show(“PIease specify a folder with excuse files in it",
Отладчик вы­ "No excuse files found");
делит желтым }
строчку с клю че­ try
вым словом catcl^J {
в то время как if (CheckChanged0 == true)
остальной код {
currentExcuse = new £xcuse(randoiB, selectedFolder);
блока будет по -'~ '
мечен серым, то UpdateFormCfalse);
есть готовым
к выполнению. ’
c«tch (SerializationException)
{
MessageSox.Sho«(
"Your excuse file was invalid.”,
"Unable to open a randoai excuse”);

Н а ж м и т е клавиигу F 5 д л я за п у с к а п р о г р а м м ы . О н а н а ч н е т в ы п о л н я т ь с я с о с т р о ч к и , п о м е ­
ч ен н о й ж елты м , в наш ем случае с блока c a t c h .

Совет на буду­
щее: Интервью
с соискателями
на должность
программиста А кку о атн е е с и скл ю чени я м и в конструкторе!
часто включа­ Рр у |Дыпе
ды пе
ю т в себя вопрос
о т ом , как сле­
осш^роЖНьїІ Думаю, выуже заметили, чтоу конструктора от­
дует пост упат ь сутствуетвозвращаемое значение. Дело втом, что его назначением
с исключениями является инициализация объекта, и именно поэтомутак сложно об­
в конструкторе. рабатывать исключения, возникшие внутриконструктора. Наличие
исключения означает, что оператор, создающийэкземпляр класса, не
смог его получить. Блок t r y / c a t c h вэтомслучае имеетсмысл по­
местить в обработчик событий кнопки, чтобыкод не ожидал найти
вклассе C u r r e n tE x c u s e корректный объектExcuse.

дальше > 469


убери за собой

Ключебое слоВо finally^


в случае исключения возможны два варианта развития событий. Не обработанное исключение ведет
к аварийному завершению программы. В противном случае управление переходит к блоку c a tc h . Но
что происходит с остальным кодом блока t r y ? Представьте, что в этой части закрывался поток. Тогда
этот код необходимо выполнить, несмотря на исключение. На помощь в этой ситуации приходит блок
f i n a l l y . Он з а п у с к а е т с я в с е г д а , вне зависимости от появления или отсутствия исключения. Вот как
нужно закончить обработчик событий кнопки Random Excuse:

p r i v a t e v o id ra n d o m E x c u s e _ C lic k (o b je c t s e n d e r , E v e n tA rg s e) {
s t r i n g [ ] file N a m e s = D i r e c t o r y . G e t F i l e s ( s e l e c t e d F o l d e r , " * .e x c u se ");
i f ( file N a m e s .L e n g th = = 0 ) {
M e ssa g e B o x .S h o w ("P le ase s p e c i f y a f o l d e r w ith e x c u s e f i l e s i n i t " ,
"No e x c u s e f i l e s f o u n d " ) ;
} e ls e {
tr y {
if (C heckC hanged0 == t r u e ) {
c u r r e n tE x c u s e = new E x c u se (ra n d o m , s e l e c t e d F o l d e r ) ;

блок finally гаран­


}
т ирует , что Me- } •При появлении исключения
‘- 'P/ateFormQ c a t c h ( S e r i a l i z a t i o n E x c e p t i o n ) {
будет выполнен вне в конструкторе Excuse у з ­
заоисимости от c u r r e n tE x c u s e = new E x c u s e ( ) ; нать значение переменной
исключения. Соот- CurrentExcuse невозможно,
c u r r e n t E x c u s e .D e s c r i p t i o n = " " ;
Ьет^ственно, метод т ак как экземпляр txcuse о т ­
UpdateFormO будет c u r r e n t E x c u s e .R e s u l t s = " " ; сут ст вует . Поэтому в блоке
Ьызван не только если c u r r e n tE x c u s e .L a s tU s e d = D ateTim e.N ow ; catch создается эт от обьект
конструктор Excuse и очищаются все его поля.
успешно прочитал M essageB ox. Show(
оправдание, но и 6
"лучае, когда п о ­ "Your e x c u s e f i l e was i n v a l i d . " ,
явилось исключение и "U nable t o op en a random e x c u s e " ) ;
раил с оправданием
>ыл очищен. }
fin a lly {
U p d a te F o r m ( f a ls e ) ;
SerializationException находится в простран­
} стве имен System.Runtime.Serialization, поэтому
в верхнюю часть файла формы нужно добавить
строчку u s i n g S y s t e m .R u n t im e . S e r i a l i z a t i o n ;

Р я д о м с к л ю ч ев ы м с л о в о м c a tc h у к а за н о , ч т о о т с л е ж и в а е т с я и с к л ю ч е н и е S e r i a l i z a t i o n ­
E x c e p t i o n . Это принятый в C# код c a t c h ( E x c e p t i o n ) , хотя тип исключения можно и опустить, оста­
вив только слово c a t c h . В этом случае б у д у т о т с л е ж и в а т ь с я в с е в о з м о ж н ы е и с к л ю ч е н и я . Хотя это
«е очень хорошо. Лучше указывать, какое исключение вы хотите отследить и указывать, как можно более
подробно.

470 глава 10
обработка исключений

ДА т е ^п е р
отладим !
. %
Обновите обработчик событий кнопки Random Excuse, вставив туда код с предыдущей
страницы. Поместите точку останова на первую строку метода и отладьте программу.

© Запустите программу и убедитесь, что когда программа указывает на папку с файлами


оправданий, кнопка Random Excuse работает корректно. Программа прервется на задан­
ной вами точке останова:
-Г7Г
private void randoraEKcuse_Cllck(object sender, EventArgs e)
{
■О string[] -fileNames = Directory,Ge1:Files(selectedFolder, "*.excuse")j
if (fileNames,Length == в)
Когда жел­
тая строка MessageBox.Show("Please specify a folder with excuse files in it”
достига­ " N o excuse files found")J
ет точки }
останова, else
над крас - {
ной точкой try
на поляк
появляет ­ if (CheckChanged() == true)
ся желтая {
стрелка. currentExcuse = new Excuse(randoiBj selectedFolder);
}
>
catch (SerializationException)
{
currentExcuse - new E x c u s e O ;
currentExcuse.Description = ""j;
currentExcuse.Results =
currentExcuse.LastUsed = OateTirae.Now;
Hessage0cfx.5hciw(
"Your excuse file was invalid.",
"Unable to open a random excuse");
}
finally
{
UpdateForm(false);
}

© Пошагово просмотрите обработчик событий кнопки Random Excuse и убедитесь, что


она работает корректно. После завершения блока t r y должен произойти переход к бло­
ку f i n a l l y , так как исключений не обнаружено.

О Теперь укажите папку, содержащую всего один дефектный файл, и снова щелкните на
кнопке Random Excuse. Из блока t r y при обнаружении исключения управление перей­
дет к блоку c a tc h . После того как будут просмотрены все его операторы, начнет выпол­
няться блок f i n a l l y .

дальше ► 471
исключения как причина нестабильности

Часвдо
Задаваем ы е
При отсутствии блока catch моя Б оцрось! Но если блок c a t c h может об­
программа, столкнувшись с исключе­ рабатывать исключения любых типов,
нием, будет просто остановлена. Что Зачем указывать в блоке catch тип зачем мне указывать конкретный тип?
же в этом хорошего? обрабатываемого исключения? Разве
не безопаснее использовать универ­
^ S К сожалению, не существует
^ I Основным преимуществом исключе­ сальный код?
универсального способа обработки всех
ний явлется то, что они не дают пройти исключений. Для устранения проблемы
мимо проблемы, в больших приложениях ^ ! Безопасней всего вообще избегать с делением на ноль в блоке catch нужно
сложно следить за всеми объектами, ситуаций, в которых возникают объ­ изменить значения некоторых пере­
с которыми ведется работа. Исключения екты E x c e p tio n . Лучше провести про­ менных и сохранить некоторые данные.
привлекают внимание к проблемам и по­ филактику, чем употреблять лекарство. А чтобы убрать ссылку на значение null
могают понять причины их возникновения. Это правило работает и с исключениями. может потребоваться создание нового
Попытка обработать все исключения экземпляра.
Появление исключения в вашей про­
сразу указывает на плохое программиро­
грамме предупреждает — что что-то идет
вание. Например, лучше воспользоваться
не так, как было запланировано. Может Обработка ошибок происходит
методом File .Exists () для проверки
быть, ссылка указывает не на тот объект, только в последовательности t r y /
наличия файла, а не обрабатывать ис­
на который нужно, или пользователь ввел c a tc h /fin a lly ?
ключение FileNotFoundException.
совсем не то значение, которое требова­
Хотя бывают исключения, которых просто
лось, или даже файл, с которым ведется ! Нет. Можно воспользоваться набором

й
не избежать, но большинство из них не
работа, вдруг стал недоступен. Подобные жов c a t c h , если вы хотите учесть
имеет приоритета.
вещи меняют результат работы вашей различные виды ошибок. Этого блока может
программы. и не быть вовсе. В этом случае исключения
Иногда имеет смысл оставлять исключе­
А теперь представьте, что вы даже не ния необработанными. Логика реальных обрабатывать не будут, просто код блока
знаете обо всех этих ошибках. Пользо­ программ зачастую крайне сложна и кор­ fina 11у начнет выполняться даже при
ватель же вводит некорректные данные ректно обойти ошибку не всегда удается, остановке из-за появления исключения
и начинает жаловаться, что приложение особенно если проблема возникает где-то в блоке try.
нестабильно. Нарушающие работу вашей там в нижней части кода. Обрабатывая
программы исключения позволяют узнать конкретные исключения, избегая слишком
о проблеме на том этапе, когда ее реше­ общих подходов и попыток решить все
ние еще можно провести относительно проблемы сразу, позволяя исключени­
легко и безболезненно. ям всплывать, чтобы обработать их на

Чем обработанное исключение


верхнем уровне, вы сделаете свой код
намного более стабильным.
Необработанное
отличается от необработанного?
исключение озна­
Ql При появлении исключений про­ Что произойдет, если не указать
чает непредсказу­
грамма начинает искать блок catch, тип исключения рядом с ключевым
словом c a t c h ?
занимающийся обработкой. При наличии
такого блока будут выполнены все полага­
емую работу про­
ющиеся в случае конкретного исключения ! Блок catch будет работать с лю-
граммы. Именно
действия. Фактически, написанием блока
catch вы проводите предварительную
работу над ошибками. Если же такой блок
отсутствует, программа просто прекраща­
О и исключением, которое появится
в блоке try.
поэтому программа
ет работу, выбрасывая окно с сообщени­ перестает работать
ем. В этом случае речь идет о необрабо­
танном исключении. при их появлении.
472 глава 10
обработка исключений
class Kangaroo {
______________ fs;
int croc;
Поместите фрагменты кода из
int d i n g o = 0;
бассейна на пустые строчки
программы. Каждый фраг­ public int W o m b a t ( i n t wallaby) {
мент может использоваться
несколько раз. Имеются и
try {
лишние фрагменты. Нужно
получить следующую строку. if { > 0) {
_.O p e n W r i t e {" w o b b i e g o n g " ) ;
croc = о;
Результат: G'day Mate! } else if (_ _________ < 0) {

croc = з;
} else {
using System.lO;
public static void M a i n O { .O p e n R e a d (" w o b b i e g o n g " );

Kangaroo j o e y = n e w K a n g a r o o (); croc = 1;

in t k o a l a = joey.Wombat( }
j o e y .W o m b a t {j o e y .W o m b a t (1))), catch (lOException) {
try { croc = -3 ;
C o n s o l e . W r i t e L i n e ((15 / koala)
}
+ " e g g s p e r p o u n d " ); catch {
} croc = 4 ;
catch (________________________) {
}
C o n s o l e .W r i t e L i n e (" G ' D a y M a t e !" ! finally {
. } if (_______ > 2) {
c r o c ___ dingo;

} J
К аж д ы й ф р а гм е н т
м о ж е т б ы ть и с п о л ь ­
зо в ан н е ско л ь ко
раз!

Ребусы становятся все более слож ны м и, а им ена перем енны х все менее очевидны м и. Н ад реш ением при­
ходится много д ум ать! Но ребусы реш ать не обязател ьно, вы м ож ете просто перевернуть страницу и про­
д ол ж и ть ч тен и е... это д л я л ю бител ей головоломок!

дальше > 473


что мусор д л я одного — сокровище для другого
Метод иоеу.\А1отЬа±0 вызывал­
ся три раза и на т рет ий раз
оернул ноль. Поэтому метод
е Ш ен и е ]= » е ^ с а Б б а с с е й н е WпteL/neQ вызвал исключение
Р/У/авВугегоЕхсерИоп.
public static void MainO {
Kangaroo joey = new Kangaroo{);
int koala = joey.Wombat(joey.Wombat(joey.Wombat(1))); ■^
try {
Console.WriteLine((15 / koala) + " eggs per pound")
}
catch (DivideByZeroException) {
Console.WriteLine("G'Day Mate!");

Объект FiieStiream } }
имеет метод
OpenReadQ и с т а ­ - Этот catch, работает
новится причиной
class Kangaroo { только с исключениями,
появления исключе­ FileStream fs; возникшими в результ ат е
ния lOException. int croc;
деления на ноль.
int dingo = 0 ;

public int Wombat(int wallaby) {


dingo ++;
try {
Этот код открывает if (wallaby > 0) {
файл wobbiegong. Зат ем он fs = File. OpenWrite ("wobbiegong") ;
открывает его повторно. croc = 0 ;
Но файл не закрывается
что приводит к исключе­ } else if (wallaby < 0) {
нию lOException. croc = 3 ;
else {
fs = File .OpenRead ("wobbiegong" ) ;
Помните, что жела­ croc = 1;
тельно обрабатывать
исключения конкретного
типа. Впрочем, в ребусах catch (lOException) {
мы делаем и другие веш,и, croc = -3
которых нужно избегать ^ Вы уже знаете, что после за -
при написании реальных \ вершения работы с файлом
программ. Например, мы \ его требуется закрыть, иначе
выбираем для перемен­ ^ файл окажется заблокированным.
ных незначимые имена. Попытка снова его открыть
станет причиной исключения
lOException.
2) {
croc -= dingo;

} }
return CrOC;

474 глава 10
обработка исключений

Получения информации о проблеме


Мы уже несколько раз повторили, что при появлении исключения .NET
создает объект E x c e p tio n . Доступ к нему вы получаете через код блока
c a tc h . Вот как это работает;

Некий объект выполняет некие функции, и вдруг нештатная


ситуация приводит к появлению исключения.

Что за ерунда
происходит?

О К счастью, срабатывает блок t r y / c a t c h . Внутри блока c a t c h


исключению присваивается имя ex.
г Вели рядом с объявлением
i типа исключения в блоке
DoSomethingRisky () ; __ catch указат ь имя пере-
------ ------------MCHHOUj эт у переменную
} можно будет использо-
f вать для доступа к объ -
catch (RiskyThingException(«^l ект у Exception.
string message = ex.Message;
MessageBox.Show(message, "Я сильно рисковал!");

e После завершения работы блока c a t c h ссылка e x исчезает,


и объект отправляется в мусорную корзину.

message = ex.Message;

дальше > 475


наборы блоков catch

Обработка исключений разных типоб


Вы уже знаете, как работать с исключением определенного типа... но что де­
Мемод ToStrii^O
лать, если код имеет несколько проблемных мест? Вам может потребоваться исключения
обработать набор различных исключений. В этом случае не обойтись без на­ позволяет вывести
бора блоков c a tc h . Вот пример кода для завода по переработке нектара, в ко­ в окно MessageBox
тором обрабатываются исключения различных типов. В некоторых случаях относящиеся к делу
используются свойства объекта E x c e p tio n , например, свойство M essage, со­ данные.
держащее информацию об исключении. Можно также прибегнуть к оператору
th ro w для сообщений об аномальных ситуациях.

public void ProcessNectar(NectarVat vat. Bee worker, HiveLog log) {


Иногда {
NectarUnit[] units - worker .EmptyVat (vat) ;
HO вызвать for (int count = 0; count < w o r k e r . UnitsExpected, count -ь-н) {
перехваченное , . ^
исключение. stream hiveLogFile = l o g .OpenLogFile () ;
u cn olT slfm - w o r k e r .AddLogEntry (hiveLogFile) ;
С Я оператор } Если вы не собираетесь исполь -
throw. , зовать объект Exception, объяв- Набор блоков catch обрабатывается по
} лят ь его не нужно. ^ ^ очереди. .............
г, „В наш ем случае сначала рассм а
т ри вает ся V a tE m p tyE x cep tio n , а м т о М
catch (VatEmptyException)^{ H iveLoqException. Последний олок eaten оо
vat.Emptied = true; р абат ы вает lOException. Это базовый класс
для различных исключений, в т о м числе
} для FileNotFoundException (ф айл не найден)
^ и E n dO fS tream E xception (К о н е ц пот ока).
catch (HiveLogException ex)
throw; В блоке catch объекту exception сопоставляют пере­
менную е х , которая зат ем может использоваться для
получения информации об объекте.
catch (lOException ex) {
Р^^ЛЯ объекта worker .AlertQueen ("Ап unspecified file error happened-
Exception 6 р а з-
личных блоках +■ "Message: " -i- ex.Message -i- "\r\n"
можно использо­ + "Stack trace: " -ь ex.StackTrace -н "\r\n"
вать одно и то же
имя (ех). + "Data: " 4- ex.Data -i- "\r\n");
}
finally {
v a t .S e a l ();
w o r k e r .Finis h e d J o b ()
}

476 глава 10
обработка исключений

- Разумеется, дывает и
такое, что один метод
Один класс создает исключение, другой его обрабатывает может формировать ис­
ключение, которое ликви­
в момент создания класса неизвестно, как с ним будут работать. Иногда дируется другим методом
действия пользователей становятся источником проблемы. Именно в этих этого же класса.
сиутациях возникают исключения.
Вам нужно заранее понять, что может пойти не так, и предусмотреть план
перехвата. Вы обычно не видите, какой метод создает исключение, а какой
устраняет его. Это, как правило, различные методы, принадлежащие раз­
личным объектам.
К онст рукт ор обьекта BeeProfile
ожидает имя содержащего
профиль пчелы, чтобы открыть его
Вместо того, чтобы... методом File.OpenQ. Если файл не
Без обработки исключений программа пере­ открывается, программа перест ает
стает работать. Вот что происходит в програм­ работать.
ме управления профилями пчел. stream = File.Open(profile);

FilelMotFoundException was unhandled


Unable to find the specified Яе.
^ HW® О бьект BeeProfile пы т ает - ^
С Я чит ат ь от сут ст вую и^иеы .^ "У \
файл, поэтому мет од File.
OpenQ информирует об ис­ внимание, как обьект
ключении. В программе о т ­
сут ст вует блок catch, п о ­
эт ому исключение остается
необработанным.
Y / 1^ередачи в улей.
try {
...мы можем поступить так. stream = File.Open(profile);
Объект В ееРго£11е может перехватить ис­ } catch (FileNotFoundException ex) {
ключение и добавить запись об этом в журнал. WriteLogEntry("unable to find " +
Затем исключение вызывается повторно и пе­ profile + ": " + ex.Message0;
редается в улей для обработки. throw;
}
■ ^ ^ n file ( " p r o f .d a t" 4 ^
Теперь при попытке улья

О
создать обьект 8ееРгоР((е,
передав неверное имя ф ай­
ла, бцдет сделана запись об
ошибке и появится моби^ение
об исключении. Улей сможет
try { обработать исключение, на
prof = new BeeProfile("prof.dat") пример, пут ем
. создания файла с профилем
} catch (FileNotFoundException) {
пчелы.
Hive.RecreateBeeProfile("prof.dat
}
дальше > 477
ваше собственное исключение
Методы показывают таков
исключение при некорректных
Exception
исключение OutOfHoney для пчел значениях параметров.
Message
Ваши классы могут формировать собственные исключения. Например, при получе­ StackTrace
GetBaseExceptionO
нии внутри метода параметра null вместо ожидаемого значения имеет смысл исполь­
ToStringO
зовать исключение, которое вызывает в такой ситуации .NET:
throw new ArgumentException(); ^

Ho иногда исключение требуется из-за особых обстоятельств, возникающих в про­


цессе работы программы. Например, количество меда, потребляемого пчелой, за­
висит от ее веса. Но для ситуации, когда меда в улье не осталось, имеет смысл соз­ your Exception
дать собственное исключение. Для этого потребуется класс, наследующий от класса
Exception. StackTrace
GetBaseExceptionO
ToStringO
c l a s s O u tO fH o n e y E x c e p tio n : S y s t e m .E x c e p tio n {

p i i b l i c O u t O f H o n e y E x c e p t io n ( s t r i n g m e s s a g e ) : b a se(m essa g e) { }

} Для вашего исключения потребуется класс,


c l a s s H o n e y D e liv e r y S y s t e m { который должен быть производным по о т -
^ ношению к классу System.Exception. О брат и­
те внимание, каким образом перегружается
, конструкт ор, чтобы передать соотцение об
p u b l i c v o i d F ee d H o n e y T o E g g s О { исключении.
if (h o n ey L ev el = = 0 ) {

th r o w n ew O u t O fH o n e y E x c e p tio n ( "T he h i v e i s o u t o f h o n e y ." );

) .1 .. ( ,— Вызывается новый экземпляр обь-


fo r e a c h (E gg e g g i n E g g s ) { “ ) При наличии в у л ь Т '^ " ' except/on.
С ‘и сключение не
J ‘п оявляется и уп р а в­
} ление передается
p u b l i c p a r t i a l c l a s s F o rm l : Form {
эт ому коду.

p r i v a t e v o i d c o n s u m e H o n e y _ C lic k ( o b j e c t s e n d e r , E v e n tA r g s e ) {
H o n e y D e liv e r y S y s t e m d e l i v e r y = n ew H o n e y D e li v e r y S y s t e m ( ) ;
tr y {
d e l i v e r y . F e ed H o n e y T o E g g s () илл я пользоват & льского <^сключетя ука зы ^^ ^
У к» «»полня-
} iC - ются все операции для его обработки.
c a tc h (O u tO fH o n e y E x c e p tio n e x ) {

M e s s a g e B o x .S h o w ( e x .M e s s a g e , " W a rn in g : R e s e t t i n g H i v e " ) ;
Wamir^j Resetting Hive
H iv e .R e s e t 0 ;
Если в улье от сут ст вует мед, деят ель-
} ность пчел останавлиоается, и симулят ор T he h'fve is o u t of h o n ey

} прекраш,ает работать. Для возвраш,ения


к нормальному функционированию т ребу­ OK
ется перезагрузка. Именно эт от код пом е­
щен в блок catch.
478 глава 10
обработка исключений

I Ц сЮ П оЧ иш еЛ ьН ы е М а Г н и т ь !
public static void MainO { Расположите магниты с кодом таким об­
Console.Write("when it разом, чтобы на консоль был выведен
ExTestDrive.Zero{"yes") следующий результат.
Console.Write(" it ");
ExTestDrive.Zero("no"); Результат:
Console.WriteLine("."); ► when it thaws it throws.

class MyException : Exception { }

P u b lj ^ ^ ^ a t ic v o id Z ero ( s t r i n g t est) {

s t a t i c v o id D o R isk y (S tr in g t) {
C o n s o le .W r it e ( " h " ) ;

дальше ► 479
небольшой обзор

Задачи с МаГнищаМи
public static void MainO { Расположите магниты с кодом таким образом,
Console.Write{"when it "); чтобы на консоль был выведен следующий ре­
ExTestDrive.Zero("yes"); зультат.
Console.Write(" it ");
ExTestDrive.Zero("no"); Результат:
Console.WriteLine("."); > — ► when it thaws it throws.
}
class MyException : Exception { }

c l a s s E x T e s t D r iv e {
p u b lic s t a t i c v o id Z e r o (s t r in g t e s t ) {
Эта строчка опреде­
ляет пользовательское
исключение МцЕнсерНоп,
которое обрабатывается 8 зависимости от. mozoj
C o n s o le .W r it e ("t ");
в блоке catch. какой парам ет р test
D o R is k y (te s t);
был передан методу
ZeroQ (строка yes или
чт о-т о другое), выво­
C o n so le .W rite ( " о " ) ; дится строка thaws или
throws.
} c a tc h (M y E x c e p tio n ) {

} fin a lly {
1
C o n s o le .W r it e (" W " ) ;
[
1 I
/ 5лок finally гарант ирцет ,
что мет од всегда выоо-
дит символ W . А так как
ф f символ S выводится вне
1 обработчика исключений,
\ он так же всегда попадает
C o n s o le .W r it e ("s "); в список вывода.

G J
s t a t i c v o id D o R is k y (S tr in g t ) {
Эта строчка выполня­ C o n s o le .W r it e ( "h") ;
ется только в случае,
]
когда метод doRiskyQ
не становится причи­
ной появления исклю ­
if (t = " y es" ) {

th r o w n ew M y E x c e p t io n ( ) ;
I
чения. I
} Метод doRiskyO вы-
to n s o le .W r it e ( " r " ) ;
передаче ему строки'^

480 глава 10
обработка исключений
КЛЮЧЕВЫЕ
МОМЕНТЫ
Причиной исключения может стать любой оператор. Каждому оператору try может соответствовать
несколько операторов catch:
Для обработки исключений пользуйтесь блоком
try/catch. Необработанные исключения приво­ try { ... }
дят к прекращению работы программы. catch (NullReferenceException ex) {
// эти операторы срабатывают при
Обнаружение исключения в блоке try приводит // NullReferenceException
к немедленной передаче управления первому опе­ }
ратору блока catch. catch (OverflowException ex) { ... }
catch (FileNotFoundException) { ... }
Объект Exception содержит информацию об catch (ArgumentException) { ... }
исключении. Объявив переменную Exception
в операторе catch, вы получаете доступ к инфор­ Для сообщения об анамальных ситуациях использу­
мации об исключении, появившемся в блоке try: ется оператор throw:

try { throw new Exception ("Сообщение");


// операторы, которые могут
// вызвать исключение Оператор throw позволяет повторно вызвать пере­
} catch (lOException ex) { хваченное искпючение, но только внутри блока catch.
// информация об исключении
Наследованием от класса Exception можно
// содержится в переменной ех
создать пользовательское исключение.
}
class CustomException : Exception;
Существуют различные типы исключений. Каждому
соответствует объект, унаследованный от класса В большинстве случаев достаточно встроенных
Exception. Старайтесь избегать обнаружения исключений .NET. Прибегая к различным типам
исключений «вообще», работайте с исключениями исключений, вы предоставляете больше инфор­
определенного типа. мации пользователю.

Оператор using как комбинация Объявляя ссылку внутри оператора


usin^j вы автоматически вызываете
в конце блока операторов метод
операторов try и finally DisposeQ.

Вы уже знаете, что оператор u s in g гарантирует


закрытие ваших файлов. 1^1ногда он может ис­
пользоваться и как б ы с т р ы й в ы з о в для операто­
Y o u r C la s s с = new Y o u r C l a s s О ;
ров try и finally!
tr y {
При вызове м ет о -
// // T>.,-NTT S . ^. .
(j/2 Г)/СЬолглА ий илоке
а н а л о ги ч н о ^ rinaliy можно ис-
u s in g (Y o u r C la s s с } fin a lly { j пользовать сокра-
= new Y o u r C la s s 0 ) { '/ц ен н ую запись с
с .D is p o s e 0 ; ^операт ором using.
// код }

дальше > 481


небольшое предупреждение .И нт ерф ейс IDisposable позволяе т
избежать распространенных ис­ ис-
ключении. Используйте оператор
using при работе с реализующими
11збегаем исключений при помощи интерфейса эт от интерфейс классами.

IDisposable
Потоки снабжены кодом для их закрытия после удаления объекта. Но что делать с пользовательским
объектом, после удаления которого требуется произвести некие действия? Имеет смысл написать свой
код для случая, когда объект используется внутри оператора u s in g .
В C# это можно сделать при помощи интерфей­
са I D is p o s a b le . Реализуйте его и вставьте ос­
вобождающий ресурсы код в метод D is p o s e (), ® операторе using
— можно только при условии реализации
как показано ниже: им интерфейса IDisposable^ Ъ противном
SyT eZ компилироваться не
c la s s N e c ta r : I D is p o s a b le { Объект, который предполагается использовать вместе
p r iv a te d o u b le a m o u n t;
^D isposablT^^ должен реализовывать интерфейс
p r iv a te B e e H iv e h iv e ;
p r iv a te S tr e a m h iv e L o g ;
p u b lic N e c ta r (d o u b le a m o u n t, B e e H iv e h iv e . S tr e a m h iv e L o g ) {
th is .a m o u n t = a m o u n t;
th is .h iv e = h iv e ;
th is .h iv e L o g = h iv e L o g ; Sudem выполнено в конце оператора using.
} sL- Метод
p iib lic v o id D is p o s e О { DisposeQ
if (a m o x m t > 0 ) {
уже написан,
т ак что он
h iv e .A d d ( a m o u n t ) ; может быть
h iv e .W r it e L o g ( h iv e L o g , am ount + " mg n e c t a r added to th e h iv e " ); вызван произ-
am ount = 0 ; / вольное коли-
^ чество раз.
}
У В руководстве по реализации и нтерф ейса
i D i s p o s e сказано, что метод D i s p o s e О
м ож но вы зы вать много раз. Вы поним аете
важность этого обстоятельства?

Теперь можно несколько раз воспользоваться оператором Вложенные операторы using исполь­
u s in g . Возьмем встроенный объект S tream , реализующий зуют ся при необходимости объявить
дбе ссылки на интерфейс IDisposable
I D is p o s a b le , как и наш обновленный объект N e c ta r: о одном блоке кода.
u s in g (S tre a m lo g = new F i l e . W r i t e ( " l o g . t x t " ))
u s in g (N e c ta r n e c t a r = new N e c t a r ( 1 6 .3 , h i v e , lo g ) ) {
B e e .F ly T o ( f lo w e r ) ;
Объект Nectar использует поток
B e e . H a r v e s t ( n e c ta r ) ; j автоматически закрываюи^иися
B ee. FlyT o (h iv e ) ; у S внеилнего оператора using.
к о н ц е

482 глава 10
обработка исключений
часацо
^аД аБ аеМ ы е
Боцрось!
try {
я правильно понимаю, что Может ли метод быть вызван DoSomethingRiskyО ;
объекты, реализующие интерфейс D i s p o s e О вне оператора u s i n g ? SomethingElseRisky();
I D i s p o s a b l e , используются только }
внутри оператора u s i n g ? Q ; Конечно. На самом деле в этом finally {
случае оператор using вообще не нужен. AlwaysExecuteThis{);
^ ! Да. Интерфейс ID is p o s a b le Вызывайте метод Dispose (), завер­ }
предназначен для работы с оператором шив работу с объектом. Он высвободит
При обнаружении исключения в методе
u s in g , и добавление этого оператора указанные ресурсы, как и при вызове
DoSomethingRisky О немедленно
эквивалентно созданию экземпляра клас­ вручную метода Close {) для потока.
будет запущен блок final 1у.
са, только тут всегда вызывается метод Оператор using всего лишь облетает
D i s p o s e {). чтение и понимание кода и предотвращает
проблемы, которые могуг возникнуть, если Правда ли, что метод D i s p o s e О
Любой ли оператор может быть не удалить объект. работает только с файлами и потока­
Iомещен в блок u s in g ? ми?
)• Вы упомянули блок t r y / f i n a l l y ,
^ ! Разумеется. Оператор u s in g всего
лишь гарантирует уничтожение любого
созданного вами объекга. Но использо­
вать эти объекгы вы можете на свое усмо­
!значает ли это, что операторы t r y
и f i n a l l y могут фигурировать без
оператора c a t c h ?
О ; Нет, многие классы реализуют
интерфейс IDisposable, и при
работе с ними всегда нужно использовать
оператор using. (С некоторыми из этих
трение. Можно даже создать объект при ! Да! Вы можете скомбинировать классов вы познакомитесь в следующей
помощи оператора u s in g и не упоминать
его внутри блока. Впрочем, это не имеет
практического смысла.
О ж try непосредственно с блоком
finally, как показано здесь:
главе.) Если вы пишете класс, который
нужно утилизировать определенным
способом, таюке реализуйте интерфейс
IDisposable.

Если блоки try/catch — это такой


полезный инструмент, почему он не
используется по умолчанию, а нам
приходится вводить код вручную?

Сначала нужно определить тип появляющегося


исключения, чтобы правильно его обработать.
Ведь обработка исключений не сводится к выводу стан­
дартного сообщения об ошибке. Скажем, в программе
для поиска оправданий, зная о появлении исключения Именно поэт ому так
FileNotFoundException, можно вывести сообщение много классов наследуют
с советом, где искать нужные файлы. А в случае исклю­ от^класса Exception. По­
чений, связанных с базами данных, можно по электрон­ рой вам даже приходит ­
ся писать такие классы
ной почте отправить сообщение администратору. Так самостоятельно.
что все ваши действия зависят от типа обнаруженного
исключения.

дальше * 483
упущенные возможности

Hauxyguiuu бариант блока catch


Блок c a t c h позволяет программе продолжить работу.
Появившееся исключение обрабатывается и вместо ава­
рийной остановки и сообщения об ошибке вы двигаетесь
дальше. Но это не всегда хорошо.
Рассмотрим странно работающий класс C a l c u la to r .
Что же происходит?

c l a s s C a lc u la to r {

p iib lic v o id D iv id e ( i n t d iv id e n d , in t d iv is o r ) { Если делитель


равен нулю, появ­
ляется исключение^
tr y { - pivdeB yZ eroE xception .

t h i s .q u o t i e n t = d iv id e n d / d iv i s o r ;
Почему н есм от ря на наличие
} c a tc h { — "■ олока catch мы получаем с о -
оощение об ошибке?
I I П рим ечание Джима: нуж но п о н я т ь , как п р е д о т в р а т и т ь в в о д

// п о л ь зо в а т е л я о ш н у л е в о г о з н а ч е н и я в д е л и т е л ь .

П рограм м и ст подум ал,


чт о смож ет скры т ь
исключения при помощ и
п уст о го блока catch ,
но всего лиш ь создал
проблем у пользоват елям
11сЬючения дод)кны обрабатываться, п рограм м ы .
а не скрываться

Тот факт, что программа продолжает работу, не означает обработки ис­


ключений. Написанный выше код не будет аварийно остановлен... по
крайней мере, не в методе D iv id e (). Но что если этот метод вызыва­ Помните, что если
ется другим методом, который пытается вывести результат? При равен­ исключение не обраба­
стве делителя нулю, метод, скорее всего, вернет неправильное (и не­ тывается,, оно подни­
ожиданное) значение. мается вверк в стеке
вызовов. Это тоже
Нужно не просто добавить комментарий, а обработать исключение. своего рода обработка.
Даже если вы не знаете, что делать, не остаеляйте блок catch пустым или
закомментированным! Это лишь усложняет пользование программой.
Лучше пусть появится сообщение об исключении, это хотя бы позволя­
ет понять, что именно работает не так.

484 глава 10
обработка исключений

Временные решения -К сожалению, в реальной жизни «временные»


решения зачастую становятся постоянными.
Иногда, столкнувшись с проблемой, вы не знаете, что де­
лать. В этом случае имеет смысл внести запись в журнал,
снабдив ее примечанием. Это не так хорошо, как обработ­
ка исключения, но лучше, чем ничего.
Вот временное решение для калькулятора:

c l a s s C a lc u la to r {

p x ib l lc v o i d D i v i d e ( i n t d i v i d e n d , in t d iv is o r ) {

tr y {

t h i s .q u o t i e n t = d iv id e n d / d iv is o r ;

} c a tc h (E x c e p tio n e x ) {

u s in g ( S t r e a m W r it e r sw = n ew S t r e a m W r i t e r ( @ " C : \ L o g s \ e r r o r s . t x t " ) ;

s w .W r it e L in e ( e x .g e t M e s s a g e ( ) ) ;

} Проблема никуда не исчезла,


но по крайней мере, стал о
Т си о где она возникла. Лучше
всего разобраться, почему
ваш метод р Ш е вызывает
-----------^
ся при нулевом знйМ енйтем
Я понял! М ы используем м устранит ь эт у возмож
обработку исключений, чтобы ность.
пометить проблемную область.

Обработка исключения далеко не всегда


означает УСТРАНЕНИЕ исключения.
Возможность аварийной остановки програм­
мы —это плохо. Но непонимание причин такого
поведения намного хуже. Поэтому всегда нужно
обрабатывать ошибки, которые вы можете пред­
сказать, и записывать в журнал информацию
об ошибках, с которыми вы не умеете бороться.

дальше у 485
несколько предложений

краткие принципы обработки исключений

Красиво оформляйте коВ обработки ошибок.

Предоставляйте ИНФОРМАТИВНЫЕ сообщения


об ошибках.

Старайтесь прибегать к встроенным исключе­


ниям .НЕТ^ а не создавать собственные.

Думайт е о том, как можно сократить код


блоке ігу.

...и самое главное...

Избегайте ошибок, связанных с файлами... всегда I другими


элеуиентйл1к,
I польЩйтесь блоком работая с потоками! I
Ш Ж 1 тА .

486 глава 10
обработка исключений

В о с п о л ь з у й т е с ь б ло ко м try/catch/finally д л я о б р а б о тк и искл ю чен ий


^ п^рпаржа ж
н (н ш е в п р о гр а м м е E x c u s e M a n a g e r Б р а й а н а .

О Добавьте к обработчику события Click кнопки O pen обработку исключения. Достаточно


блока try/catch, вызывающего окно с сообщением. Оно должно появляться при попытке
открыть файл неверного формата:

О Но это еще не все. Создадим отдельный некорректный файл с объяснением. Поместите


точку останова в первую строчку метода E x c u s e . S a v e (), запустите программу и сохрани­
те оправдание. Когда программа прервет работу, добавьте контрольное значение к свой­
ству L a s t U s e d . В окне Watch присвойте ему значение D a t e T i m e . P a r s e ( " O c t o b e r 14,
1 0 6 6 "). Нажмите клавишу F5, чтобы продолжить отладку. Должно появиться исключение
A r g u m e n tO u tO fR a n g e :

t Argumi^otOutOtRrtii^ctKceptk)« was unhandled


Valued '10Я4ДШ 12Ш 00 AM' Ъ not veftd for ’Value'. ‘Value' shoufd be between ‘Mm0ate‘ and 'М^Ойе'.
ParameSer name Vatue
f r o u t > t € s t o o t k i g 1^ : ___________

Maice sure t h e arQu m ents to this have vaftd values, j


If you are working wfth a coHectbf>, m ake sure th e index is less th an th e size of th e coHection.
W hen using th e overloaded tw a -a rg u m e n t FmdStnng or FbdExactString m eth o d s with a ComboBox or UstBox, check th e rtsrtlndex param eter.
Get general help fo r thfs exceptsor^,

O ho появилось потому, что свойство Value элемента DateTimePicker получило значение


меньшее, чем MinDate. Но перед этим класс Excuse записал файл. Это о ч е н ь п о л е з н а я
т е х н и к а : генерация файлов с заранее известными неверными данными. Потом эти файлы
можно использовать для тестирования программы.

О Загрузите только что созданый файл, и вы получите то же самое исключение. Для получе­
ния другого исключения нужно попытаться открыть файл, который не содержит оправда­
ния. Добавьте блок обработки исключения, вложенный внутрь добавленного на шаге 2 , чтобы
загрузить файл, не содержащий оправдания:
1. Объявите булеву переменную clearForm перед блоком try/catch. Она должна иметь
значение true при наличии исключения и использоваться при проверке необходимо­
сти очистки формы.
2. Добавьте еще один блок try/catch внутрь блока кнопки Open.
3. Добавьте блок finally к внешней конструкции try/catch, чтобы вернуть форму
в исходное, пустое состояние. Если переменная clearForm имеет значение true,
присвойте LastUsed.Value свойство DateTime.N ow (оно возвращает текущую дату).

дальше ► 487
решение упражнения

Вот каким образом ваши знания о конструкции try / c a tc h / fin a lly помогли улуч-
_ шить программу Брайана.
аж нш е
реш ение

private void open_Click(object sen d e r , EventArgs e) {


if (CheckChanged0) {
o p e n F i l e D i a l o g l .I n i t i a l D i r e c t o r y = s e l e c t e d F o l d e r ;
o p e n F i l e D i a l o g l .F i l t e r =
"Excuse files ( * .excuse) I* . e x c u s e IA l l files ( * .*)|*.*";
o p e n F i l e D i a l o g l .F i l e N a m e = description.Text + ".excuse";
DialogResult result = o p e n F i l e D i a l o g l .S h o w D i a l o g ();
if (result == D i a l o g R e s u l t .OK) { ^ В этом блоке try/catch block создается окно
bool clearForm = fals e - i ^ сообщением об оилибке, на сличай возник-
г ' ^ новения проблемы при вызове формой кон-
i структора Excuse для загрузки оправдания.
currentExcuse = n e w E x c u s e ( o p e n F i l e D i a l o g l .F i l e N a m e ) ;

8 данном случае U p d a t e F o r m (false) ;


не используется } еслй данные в загрцжаемплА
объект, exce p tio n j (ArgumentOutOfRangeException) { ‘^Р^делы заданно-
поэтому в опера- , , ^ го диапазона.
mojpe c a tc h у к а - M e s s a g e B o x . s h o w ("The e x c u s e file '"
зывается только + openFileDialogl.FileName + "' h a d a i n v a l i d data",
т ип исключения,^ "Unable to o pen the excuse");
ClearForm = true; с сообщением, созданное внеш-
ОКНО
опускается. ^ блоком try/ca tch block. Оно содержит
I ^ — информацию об исключении.
catch ( S e r i a l i z a t i o n E x c e p t i o n ex) {
MessageBox.Show("An error occurred while o p e n i n g the excuse '"
+ o p e n F i l e D i a l o g l .F i l e N a m e + ” '\n" + ex.Message,
"Unable to o p e n the excuse", M e s s a g e B o x B u t t o n s .OK,
MessageBoxIcon.Error);
C l e a r F o r m = true; npuc6au8aH^m
}

if ( C learForm) { finally 4>opMa nepesaepyTi^


description.Text = "";
results.Text =
lastUsed.Value = DateTime.Now;

488 глава 10
обработка исключений

Наконец Брайан получил


сбой отдых...
Теперь, после того как Брайан
позаботился об исключениях,
он получил свой заслуженный (и
одобренный шефом!) выходной.

...и полозкение дел улучшилось!


Ваше умение работать с исключениями
не просто предотвратило проблему. Оно
прежде всего гарантировало, что шеф
Брайана не узнает о его прогулах!

Старина Брайан никогда


Правильная обра­
не уходит с работы без
уважительной причины.
ботка исключений
незаметна пользова­
телям. Программа
не останавливает
работу, а проблемы
обрабатываются акку­
ратно, без сообщений
об ошибках.

дальше ► 489
11 с о б ь Ш 1и я и д е Л е Г а ш ы

Чтоделает ваш код, когда вы ^


на него не смотрите

Невозможно все вредля контролировать созданные объекты.


Иногда что-то... происходит. И хотелось бы, чтобы объекты умели р е ­
а г и р о в а т ь н а п р о и с х о д я щ е е . Здесь вам на помощь приходят собы­
тия. Один объект их публикует, другие объекты на них подписываются,
и система работает. А для контроля подписчиков вам пригодится метод
о б р а тн о го вы зова.
издатель, встречай подписчика

Х оти те, что объекты научились думать сами?


Предположим, вы пишите симулятор игры в бейсбол. Вы собираетесь про- повсеместно^
гт ^ ^ ^ юаспространенныи
дать это приложение команде «Янки» и заработать миллион долларов. Вы способ именования
создали объекты B a l l (Мяч), P i t c h e r (Подающий), U m pire (Судья), Fan методов. Мы
(Фанат) и многие другие. Вы даже написали код, моделирующий перебра­ обсудим его чут ь
сывание мяча объектом P i t c h e r . позже.
Осталось собрать все вместе. Вы добавляете мячу метод O n B a llln P la y ()
(Мяч в игре), и теперь нужно, чтобы объект P i t c h e r ответил методом сво­
его обработчика событий. Методы уже написаны, их требуется только свя­
зать друг с другом:

посль э—т Мяч лет и т по 70-градцсиой


вы зы вает ^ ^^раектории и y n a d e m Z
расстоянии 8Z ф у ^ а от базы. Подаю1ций должен
бросить эт от
^ B a l l . O n B a l l l n P l a y (70, 82 мяч.

Подающий может
мяч под
'^^ к и м углом и на
^ а к о е расстояние
больше, чем 82 )

P i t c h e r . C a t c h B a l l (7 0 9 0)

Как объекты узнают, что произошло?


Сформулируем проблему. Вы хотите, чтобы объект B a l l беспо­
коился об отправляющем его в полет ударе, а объект P i t c h e r —
о поимке летящих на него мячей. При этом не нужно, чтобы объ­ Объекты должны
ект B a l l сообщал объекту P i t c h e r , «Я лечу».
заботиты;я о себе,
а не о других.
Мы за разделение
Scooting и очень быстро бегающий. сферы влияния
Г 0№Ье1!КТОВ. ^
м ы говорам определяет
что кидать. Это не
о « « » * « “ “'
492 глава 11
события и делегаты

События
После удара по мячу вам потребуется с о б ы т и е ( e v e n t ) . Этим тер­
мином называется что-то происходящее в вашей программе. На
событие могут прореагировать объекты, например. Pitcher. С О -б Ы “Т И -е , с у щ .
Разумеется, это могут быть и объекты Catcher, ThirdBaseman, то, что случается.
Umpire и даже Fan. При этом реакция для каждого объекта будет Солнечное затмение -
своя.
это событие, которое
То есть объект Ball должен в ы з ы в а т ь с о б ы т и е . Остальные же
объекты будут п о д п и с ы в а т ь с я н а с о б ы т и е э т о г о т и п а ... и реагиро­ нельзя пропустить.
вать на его возникновение. На это .
- ° событие, может
пО >^ри эт ом объек -
Тпй подписчи-
ииной ^ ков неизвестен.
13д|[|1л.р!йУ-
Вызвано событие BalllnPlay

Ва\'

В ИСР события
помечаются знач­ Фанаты подписыва­
ком 6 виде м о л­ ются на случай, если
нии. Вы уже могли мяч попадет на три -
его видеть р я ­ НапаЭдкзьцмй Судья проверяет, по пра- буны.
дом с событиями м други е и гр о ­ бмлйм ли обрабатывается
в окнах IntelliSense ки ст а р а ю т ся каждый мяч, и от сле­
и Properties. noAy4wt^f мям. живает происходящее
на поле.

Обработчик событий
Логичным результатом оповещения объектов о событии должен
быть запуск некоего кода. Этот код называют о б р а б о т ч и к о м с о б ы ­
3(71
т и й ( e v e n t h a n d le r ) . одного р uwon^A
илелиок н« к
Все это происходит во время работы программы без вашего вмеша­ с1ллаиоби ^om ofo^
тельства. Вы пишете код, вызывающий событие, затем код для его образом
обработки и запускаете приложение. П ри возникновении события,
обработчик начинает свою деятельность... вы при этом не делаете ни­
чего. И лучше всего то, что объекты при этом заботятся только о себе,
а не о других объектах.

дальше > 493


если в лесу упадет дерево

Один объект инициирует событие, другой реагирует на него


Посмотрим, как в С# функционируют события, их обра­
ботчики и подписки:

Сначала объекты подписываются на событие


До момента, когда объект B a l l сможет вызвать событие
B a l ll n P l a y , на него должны подписаться другие объекты. Этим
они как бы сообщают о своем желании знать, что это событие
наступило. ^
1<аждый объект соз-
daem свой собствен-
ныц оорабоилчик со-
бытия. Помните, как
о качестве реакции на
событие Click к кнопке
(Добавлялся метод
buttoni_ciick().

Событие запускается
По мячу наносится удар. Именно в этот момент объект B a l l вы­
зывает новое событие.

6 и гр у-

Мяч вызывает событие


Появилось новое событие (о том, как именно это происходит, мы поговорим
через минуту). Его аргументами являются скорость и траектория движения
мяча. Эти аргументы присоединены к событию, как экземпляр объекта
E v en tA rg s. Затем информация о событии отправляется подписчикам.

Событие BalllnPlau
вызывается обьек-
>^ом Ball.

494 глава 11
события и делегаты

Обработка события
После возникновения события все подписанные на него объекты
получают уведомления и могут совершать различные действия.

П одписчики получаю т ув ед ом л ение


Так как объекты Pitcher, Umpire и Fan подписаны на событие BalllnPlay
объекта Ball, они получают уведомление, и их предназначенные для обработ­
ки событий методы вызываются один за другим.

Событие
BalllnPlay

Вызванное мячом событие


V . - объект BaiІEventArqs,
содержащий информацию
событий называется ,0 траектории и дальности
метод запускаемый подписчиком полета мяча. Эта инф орма- Обр аботчики
после возникновения события. ция. передается обработчикам собь
г ' -------'^'^и'^чипгхчикам оидытий запи-
событии. ^ ^ скаются в по-
рядке очереди.
® Камады й о б ъ е к т о б р а б а т ы в а е т с о б ы т и е
Теперь объекты Pitcher, Umpire и Fan начинают каждый по-своему реагировать
на событие BalllnPlay. Их обработчики событий вызываются в порядке очереди
путем передачи им в качестве параметра ссылки на объект Ball EventArgs.
Вот с чем им ею т дело все обраба­
тывающие событие объекты. Кроме
того, они получают ссылку на объ­
ект , ставший причиной события.

об ъект Рап с помощь


аргумент а Ва11Еуеп±Агд$
проверяет, достаточно
ли близко подлетел мяч,
чтобы его поймать.

Объект Umpire наблюдает. Он м о ж е . т быть подпи­


сан и на другие события, скажем, BallFietded (Передача
мяча) или BallThrown (бросок мяча), чтобы реагиро­
вать на происходящее.
дальше ► 495
мне нужны аргументы

Соединим Все бместе


Теперь, когда вы имеет общее представление о том, что проис­
ходит, рассмотрим более подробно процесс соединения раз- EventA rgs

Это означает воз­


можность восхо­
класса EventArgs. дящего приведения
объекта EventArgs
^ Нам нужен объект для аргументов события в случае, когда его
нужно переслать
Помните, что событие BalllnPlay имеет несколько ар­ событию, не у м е -
гументов? Для них нам потребуется объект. В .NET для ющему его обраба­
этой цели существует стандартный класс E v e n t A r g s , но тывать. B allEventA rgs
он н е и м е е т ч л е н о в . Он предназначен исключительно Trajectory
для передачи аргументов объекта обработчикам собы­ Distance
тия. Вот объявление этого класса:
Эти свойства позво-
class BallEventArgs : EventArgs мячу передать
обработчикам событий
информацию о мест е
вброса в игру.

Нужно объявить событие внутри вызвавшего его класса


В классе Ball присутствует строчка с к л ю ч е в ы м с л о в о м e v e n t . Она может располагаться в произ­
вольном месте класса, обычно ее помещают рядом с объявлением свойств. Благодаря этому другие
объекты могут подписываться на событие. Вот как это выглядит:

p u b l i c e v e n t E v e n t H a n d le r B a l l l n P l a y ;

Д ост уп к событиям обыч- \


но открыт. Наше событие \
определено в классе Ball, но
нужно, чтобы на него могли
ссылаться объекты Pitcher, после ключевого слова event к лю -
Umpire и т. п. Если вы хо ­
т ит е ограничить доступ
к событию экземплярами
из его класса, событие мож­
но закрыть.

fw T r ссылка на объект, вызвав-


E v e n tfr g r " ^

496 глава 11
события и делегаты

Классам-подписчикам нужны обработчики событий


© Вы уже знаете, как функционируют обработчики событий. Каждый раз, когда
вы создавали, к примеру, метод для обработки события Click, ИСР добавля­
ла его в класс. Ровно то же самое произойдет с событием BalllnPlay объек­
та Ball. Его обработчик событий будет иметь уже знакомый вам вид;
void ball_BallInPlay(object sender, EventArgs e)

В C=^ н е т правила именования


обработчиков событий, но обычно В объявлении события
имена формируют ся следующим BalllnPlay его т ип у к а ­
образом: имя обьекта, нижнее зан как EventHandler, что
подчеркивание, имя события. означает присутствие
двух параметров: объекта
sender и ссылки EventArgs
с именем е, а также то,
Клйсс^ которому принадлежит что это событие не воз­
данный обработчик события, им еет вращает значение.
ссылочную переменную bail на объект
Ball поэтому имя обработчика со­
бытия BalllnPlay начинается со слаба
balL за которым следует имя обра­
батываемого события BalllnPlay.

На событие подписываются объекты


® Теперь, когда обработчик события задан, объектам Pitcher, Umpire,
ThirdBaseman и Fan нужно подключить свои обработчики событий. Каждый из
них будет иметь собственный метод Ьа11_В а11 InPlay. Так что при наличии у вас
ссылочной переменной на объект Ball или поля ball, оператор -*-=привяжет к ним
обработчик события:
ball.BalllnPlay += new EventHandler(ballBalllnPlay);
^ -------У
\ Эта часть определяет,
Мы связываем обработ­ \ какой метод обработчика
5уЭет подписан на событие.
чик с событием BalllnPlay
объекта, на который
указывает ссылка ball.

програм м а не будет компилироваться.

IJepeBepHuine с т р а н и ц у и л|=оДоЛжиМ

дальше ► 497
Объект B a ll оповещает подписчиков, что он в игре, с помощью события
Теперь, когда все настроено, объект B a l l может в ы з в а т ь с в о е с о б ы т и е в ответ на
определенные действия симулятора. Вызвать событие легко.

EventHandler balllnPlay . BalllnPlay,


if (balllnPlay != null) e — ЭИЛО
новый объект
balllnPlay(this, e); BallEventArgs.

По мячу наносится
удар, tA обьект
Bal начинает
действовать... Событие, не имеющее
обработчика, становит-
,Ы передавая 5 и д ы п е I ся причиной
его возиикме- « с щ о р о ^ к н ы ! исключения.
длу c o o b i m u h o . : J
Если другие объекты не
Г Событие акт ив­ добавят событию свои
но. К т о на него обработчики, оно полу­
подписан?
чает значение null, и
появляется исключение
N u llR e f e r e n c e E x c e p tio n .
О бьект Pitcher связы­
Именно поэтому нужно
вает свой обработчик
с событием BalllnPlay. копировать событие в
переменную перед про­
веркой на равенство null.
Поэтому метод
обьекта Pitcher В крайне редких случаях
вызывается с п р а ­ событие успевает приоб­
вильными данными рести это значение уже
и может рабо­ после проверки.
Prtc т ат ь с событием.

Стандартные имена методоВ, бызыВаюи^их событие


Откройте код любой ф о р м ы и введите слово o v e r r i d e в строку объявления метода. После нажатия
пробела появится окно IntelliSense;
override Обратили внимание, что каж­
дый из методов использует в
4* OnCursorChanged(EventArgs е)
качестве парамет ра EventArgs?
OnPeactivate (EventArgs е)
Вызывая событие, все методы
OnDockChanged(EventArgs е) передают ему данный параметр.
’ OnDoubleClick(EventArgs е)
4^ O nDragProppragEvent^rgs drgevent)
Объект Form вызывает множество событий, а каждое событие имеет набор вызывающих его методов.
Метод ф о р м ы OnDoubleClick () вызывает событие Doubleclick, и это выглядит логично. Поэтому
объект Ball и м е е т м е т о д O n B a l l l n P l a y , использующий в качестве параметра объект BallEventArgs.
Симулятор вызывает этот метод каждый раз, когда для мяча требуется событие BalllnPlay, так что
когда симулятор обнаруживает удар биты по мячу, он создает экземпляр BallEventArgs, содержащий
информацию о траектории и дальности полета, который и передается методу OnBalllnPlay ().

498 глава 11
события и делегаты
Ч асто
Задаваем ы е
В опросы

Зачем в объявлении события ^ I Да! Ваши события вместо объ­ И поэтому для добавления об­
писать слово E v e n tH a n d le r ? Я екта и ссылки E v e n t A r g s могуг работчика события я использовал опе­
думал, что обработчиками называют отправлять что угодно или вообще ничего! ратор +=? Как будто добавляя новый
способ других объектов подписаться Посмотрите на последнюю строчку, пред­ обработчик к уже существующим.
на событие. лагаемую функцией IntelliSense. Обратили
внимание, как метод o n D r a g O r o p ^ : Именно так! Благодаря оператору +=
принимает ссылку D r a g E v e n t A r g s ваш обработчик событий не замещает
; Это верно, для подписки на со-
вместо E v e n t A r g s ? D r a g E v e n t A r g s предыдущий. Он становится еще одним
jbiTMe пишется метод, называемый
наследует от E v e n t A r g s точно так в цепочке обработчиков одного и того же
обработчиком событий. Но вы заметили,
же, как и B a l l E v e n t A r g s . Со­ события.
каким именно образом E v e n t H a n d l e r
бытие формы D r a g D r o p не ИС-
использовался в объявлении события
пользует E v e n t H a n d l e r . Он берет
(на шаге #2) и в строчке подписки (на Почему при вызове события
D r a g E v e n t A r g s , И ДЛЯ его обработки
шаге #4)? E v e n t H a n d l e r определяет о использовалось
B a llln P la y
ваш метод должен брать объекг и ссылку
сигнатуру события, он сообщает объ­ слово this?
DragEventArgs.
ектам, подписывающимся на событие,
каким именно образом они должны
Параметры события определяются I 1; Это первый параметр стандартного
определить методы-обработчики. А для о6|эаботчика событий. Вы заметили, что
подписки метода на события он должен
делегатами. Примеры делегатов —
E v entHandler и DragEventArgs. любой обработчик события C l i c k имеет
иметь два параметра ( obj e c t и ссылку параметр object sender? Это ссылка
О том, ЧТО это такое, мы поговорим чуть
E v e n t A r g s ) И не возвращать значений. на вызывающий событие объект. То
позже,
есть когда вы обрабатываете щелчок
Что произойдет при попытке на кнопке, объект s e n d e r указывает
) • Можно ли сделать так, чтобы обра-

:
воспользоваться методом, не совпа­ на эту кнопку. При обработке события
отчик событий возвращал значение? B a l l l n P l a y параметр s e n d e r будет
дающим с определенным при помощи
E v e n tH a n d le r ? указывать на объект Ball, а мяч при
П ; Можно, но делать этого не следует. вызове события обозначит этот параметр
Ведь именно отсутствие возвращаемых ключевым словом this.
Q ; Программа не будет компилировать­
значений позволяет соединять обработчи­
ся. Компилятор следит за тем, чтобы вы
ки событий в цепочки, присоединяя к од­
случайно не подписались на метод-об-
ному событию несколько обработчиков.
работчик, несовместимый с событием.
Стандартный обработчик событий
E v e n t H a n d l e r показывает, как именно Б ! Зачем нужны цепочки? ОДНО событие
должны выглядеть ваши методы.
; Они позволяют подписать на одно со- всегда вызывается

!
)'• Что значит «стандартный» обра-
отчик событий? Разве есть и другие? 0 гие несколько обработчиков. Эта про­
цедура будет рассмотрена чуть позднее. ОДНИМ объектом.
Но отвечать
на него M O iy r
МНОГИЕ объекты.

дальше ► 499
это сэкономит вам время

Абтоматическое создание обработчиков событий


Большинство программистов присваивают обработчикам событий имена по одному и тому же прин­
ципу. Скажем, если объект Ball вызывает событие BalllnPlay, а переменная, ссылающаяся на этот
объект, называется ball, обработчику события присваивается имя ball_BallInPlay (). Соблюдать
это правило не обязательно, но следование ему облегчает чтение кода другими пользователями.

К счастью ИСР позволяет без проблем следовать этому написанному


правилу. Ведь она умеет автоматически добавлять обработчики собы­
тий. Вы уже сталкивались с этой функцией, но давайте рассмотрим ее
еще раз.
^ У п р а ж н е н и е !

Создайте приложение Windows Form и добавьте Ball и BallEventArgs


Вот код для класса Ball:
class Ball {
public event EventHandler BalllnPlay;
public void OnBalllnPlay(BallEventArgs e) {
EventHandler balllnPlay = BalllnPlay;
if (balllnPlay != null)
balllnPlay(this, e ) ;
}
}
A это класс BallEventArgs:

class BallEventArgs : EventArgs {


public int Trajectory { get; private set; }
public int Distance { get; private set; }
public BallEventArgs(int trajectory, int distance)
this.Trajectory = trajectory;
t h i s .Distance = distance;
}

О Добавим конструктор для класса Pitcher


Конструктор класса Pitcher будет содержать одну строчку, добавляющую обработчик
событий к b a l l .BalllnPlay. Начните вводить оператор, но пока не набирайте +=.
public Pitcher(Ball ball) {
ball.BalllnPlay

500 глава 11
события и делегаты

^ ИСР поможет вам сэкономить время


Как только вы введете оператор +=, появится окно:
public P i t c h e r ( B a l l ball) {
b a l l . B a l l l n P l a y += ___________
} new EventHandler(ball_BallInPlay); (Press TAB to insert)

Нажмите tab для завершения ввода оператора. Вот как он выглядит:


public P i t c h e r ( B a l l ball) {
b a l l . B a l l l n P l a y += n e w E v e n t H a n d l e r ( b a l l _ B a l l I n P l a y ) ;

^ Двойной щелчок на кнопке в конструкторе форм приводит к авт ом а­


тическому добавлению обработчика события, но в данном случае код
добавляется к методу InitializeComponentO в файле F orm l.Pesigner.cs,
а не в конец файла класса.

Обработчик событий тоже будет добавлен автоматически


Нужно добавить еще один метод в цепочку события, к счастью, это тоже можно сде­
лать средствами ИСР.
new EventHandler
Press TAB to generate handler ■ball_BallInPlay* in this class

Нажмите клавишу tab, чтобы добавить этот обработчик событий в класс Pitcher. Вы­
бор имени будет проведен по схеме objectName_HandlerName ():
void ball_BallInPlay{object sen d e r , EventArgs e) {
t h r o w n e w N o t l m p l e m e n t e d E x c e p t i o n ();

^ / " i/icp no цмолчанию вставляет исключение


^ —/ MotlmplementedExceptionQ в качестве
t i не h e d e m e сюда нужный код и
появится сообщение о т ом , что метод нереализобан.

Завершение обработчика событий для класса pitcher


Вы добавили скелет, теперь нужно нарастить на него мышцы. Нападающий подает низко
летящие мячи или защищает первую базу.
void ball_BallInPlay(object se n d e r , EventArgs e) {
if (e is B a l l E v e n t A r g s ) {
BallEventArgs ballEventArgs = e a s B a llE v e n tA r g s ;
if { (ballEventArgs.Distance < 95) & & f ^ ( b a l l E v e n t A r g s . T r a j e c t o r y < 60))
C a t c h B a l l ();

„звоЗииО »“ З е г и е м к нисхо-
EventArgs, ^pu помощи

добавлены через минут у. доступа к его сбоистб

дальше > 501


соединим все вместе

П р и ш л о вр е м я п р и м е н и ть п о л у ч е н н ы е зн а н и я на практике. В а м нуж но
пражнение за к о н ч и ть к л а сс ы Ball и Pitcher, д о б а в и т ь к л а сс Fan и уб е д и ть ся, что
они р а б о т а ю т п р ави л ьн о .

Завершение класса Pitcher.


Методы CatchBall () и CoverFirstBase () должны выводить информацию
о том, что защитник или ловит мяч или бежит на первую базу.

class Pitcher {
public Pitcher(Ball ball) {
ball.BalllnPlay += new EventHandler(ball_BallInPlay);

void ball_BallInPlay(object sender, EventArgs e) {


if (e is BallEventArgs){
BallEventArgs ballEventArgs = e as BallEventArgs;
if ((ballEventArgs.Distance < 95) && (ballEventArgs.Trajectory < 60))
CatchBall{);
else
CoverFirstBase0 ; ^
Эти два метода должны
1 выводить результ ат на
консоль.

Хоум -ран — удар, при


О Создание класса Fan. котором мяч пролет а­
ет все поле и вылетает
Конструктор класса Fan должен быть подписан на событие
BalllnPlay. Его обработчик событий должен проверять, не за его пределы.
превышает ли расстояние 400 футов, а траектория 30 (хоум-ран),
и при превышении значений пытаться поймать мяч. В противном
случае болельщик кричит. Выведите результат на консоль.

Точный список вывода вы увидите


на рисунке на следующей странице.

Fan

502 глава 11
события и делегаты

Q Построение простого симулятора.


Создайте новую форму с двумя элементами NumericUpDown: один
для расстояния, другой для траектории. Добавьте кнопку Play ball!
Щ елчок на ней соответствует удару по мячу, после чего мяч летит
с указанными в счетчиках параметрами. Форма должна выглядеть
примерно вот так;
Не забудьте осуще
плхвить приведе­
ние свойства V a l u e
Baseball к типу int.
диапазон эшого значе
т и я м о ж е т меняться
о т О д о Х О О .поэт о- ' Trajeciofy 40
М9 свойст ву M inim um
(л-рисбоииле Зн<яченм6 Oj
Distance 435
свойст ву M axim um —
значение 1 0 0 , а свой­ Расстояние может
ст ву Value —
значение ZO.
O ia M J меняться от о до
500. В качестве
значения по ум олча­
нию выберите 100.

Список вывода.
Вот какой результат должен выдать симулятор после трех мячей:

Output - П X

Show output from; Debug_____


P i tc h e r : I covered f i r s t base
Fan: Woo-hoo* Yeahf
P itc h e r ; I caught th e b a l l
Fan: woo-hoof Yeahl
P itc h e r : I covered f i r s t base
Fan: ttoroe run! I^ a going f o r t h e b a l l !

Мяч 1: Мяч 2: Мяч 3:


Траектория: Траектория: Траектория:
Расстояние: Расстояние: Расстояние:

дальше > 503


решение упражнения

la’w u e u M o в ы гл я д и т код с н а п и с а н н ы м и к л а сс ам и Ball и Pitcher и д о б а в л е н н ы м


1с1Ж Неппе к л а сс о м Fan.

ешение
c la s s B a ll
{
public event EventHandler BalllnPlay;
public void OnBalllnPlay(BallEventArgs e) {
EventHandler balllnPlay = BalllnPlay;
if (balllnPlay 1= null)
balllnPlay(this, e) Метод ОпВаШпР1ау() вызы­
вает событие ВаШпР(аи. Не
} забудьте проверить, не равно
ли его значение пиЦ иначе он
ст анет причиной исключения
В качестве ар гу- c l a s s B a ll E v e n t A r g s : E v e n tA r g s
ментов события (
прекрасно подхо­ public int Trajectory { get; private set; }
дят авт ом ат и­
чески добавляемые public int Distance { get; private set; }
предназначенные public BallEventArgs(int trajectory, int distance)
только для чт е­
ния свойства. this.Trajectory = trajectory;
Ведь обработчики
событий только this.Distance = distance;
чит аю т переда­ К онструктор объекта
ваемый им данные. } Fan привязывает свой
обработчик события
c l a s s F an { к событию BalllnPlay.
public Fan(Ball ball!

ball.BalllnPlay += new EventHandler(ball_BallInPlay);

Обработчик собы- void ball_BallInPlay(object sender, EventArgs e)


тия BaJllnPlay клас- {
ca fan высматрива if (e is BallEventArgs) {
em высоко летящие
мячи, брошенные BallEventArgs ballEventArgs = e as BallEventArgs;
на слишком длинную if (ballEventArgs.Distance > 400 && ballEventArgs.Trajectory > 30)
дистанцию. Console.WriteLine("Fan: Home run! I'm going for the ball!");
else
Console.WriteLine("Fan: Woo-hoo! Yeah!");
}
}

504 глава 11
события и делегаты

c la s s P itc h e r {
public Pitcher(Ball ball) {
ball.BalllnPlay += n e w E v e n t H a n d l e r ( b a l l _ B a l l I n P l a y ) ;
В классе pitcher уже им е-
} —s е т с я обработчик события
void ball_BallInPlay(object sender, EventArgs e) dC BalllnPlay. Он проверяет
низко летящ,ие мячи.
if (e is B a l l E v e n t A r g s ) {
BallEventArgs ballEventArgs = e as B a l l E v e n t A r g s ;
if ((ballEventArgs.Distance < 95) S=& ( b a l l E v e n t A r g s . T r a j e c t o r y < 60))
C a t c h B a l l ();
else
C o v e r F i r s t B a s e ();

}
}
private void C a tc h B a ll0 {
Console.WriteLine("Pitcher: I caught the ball");

}
p r i v a t e v o i d C o v e r F i r s t B a s e () {
Console.WriteLine("Pitcher: I covered first base");

}
}

p u b l i c p a r t i a l c l a s s F o r m l : Form { Форме требуется один мяч,


Ball ball = new B a l l O ; один болельщик и один на-
падающий. В своем кон­
Pitcher pitcher;
ст рукт оре она связывает
Fan fan; болельщика и нападающего
с мячом.
public Forml0 {
I n i t i a l i z e C o m p o n e n t (); Щелчок Нй кнопке заставляет нападающе­
го выполнить подачу, в результ ат е чего
pitcher = new Pitcher(ball
мяч вызывает свое событие BalllnPlay. Это,
fan = n e w Fan(ball); в свою очередь, вызывает обработчики со­
} бытий объектов Pitcher и Fan.

private void playBallButton_Click(object sen d e r , EventArgs e) {


BallEventArgs ballEventArgs = new BallEventArgs(
(int)trajectory.Value, (int)distance.Value)
b a l l .O n B a l l l n P l a y ( b a l l E v e n t A r g s ) ;

Мяч 1: Мяч 2: МячЗ:


Траектория:......7.5^........ Траектория:....... 4-.S.. Траектория:......
Расстояние:...... %OS- ■
■■■ Расстояние:....... 2>0- Расстояние;..........

дальше *■ 505
представление страницы событий

Обобщенный EventHandler
Посмотрим на объявление события в классе Ball:

public e v e n t E v e n t H a n d le r B a l l l n P l a y ;

A теперь ра с с м о т р и м объявление с о б ы т и я C l i c k для к н о п к и ф о р м ы :

public event E v e n t H a n d le r C l i c k ;
Обобщенный
Они называются по-разному, но объявляются одним и тем же способом. А так аргумент
как все это прекрасно работает, постронние могут и не знать, что обработчик EventHandler
должен быть
BallEventHandler передает BallEventArgs при возникновении события. К сча­ производным
стью, .NET имеется инструмент, позволяющий легко сообщить эту информацию, — от EventArgs.
обобщенный EventHandler. Измените обработчик события BalllnPlay вот таким
образом:

public event EventHandler<BallEventArgs> B^lInPlay;

Вам понадобиться отредактировать и метод OnBalllnPlay, заменив EventHandler на Event-


Handler<BallEventArgs>. Но при попытке построить код в окне Error List появится сообщение
о двух ошибках:

Error List П X "


O 2 Errors i A 0 Warnings 1 0 Messages

Description File Line Colum n ■


Q 1 Cannot implicit^ convert type ‘System.EventHandler' to Pitcher.cs 12 32 1
' System.EventHandlef < Baseball. Bat[Ever^tArgs>'
O 2 Cannot implicitly convert type 'System.EventHandler' to
'System.EventHandler< Baseball,BsllEventArgs>'
Fan.cs 12 32 I

Дело в том, что после изменений в объявлении события нужно обновить классы Pitcher и Fan, заста­
вив их передавать обработчику обобщенный аргумент:
b a l l .B a l l l n P l a y += n e w E v e n t H a n d l e r < B a ll E v e n t A r g s > ( b a l l _ B a l l I n P l a y ) ;

НеяВное преобразование
Автоматически созданный обработчик событий будет обязательно содержать ключевое слово new, за
которым следует его тип. Если же убрать это ключевое слово и тип обработчика, C# осуществит н е я в ­
н о е п р е о б р а з о в а н и е и определит тип за вас:

b a l l . B a l l l n P l a y += b a l 1 _ В а 1 1 I n P l a y ;

Замените код в конструкторах классов Pitcher и Fan указанным выше способом. Вы увидите, что на
работе программы это не отразится.

506 глава 11
события и делегаты

Все формы используют события


Создавая кнопку, дважды щелкая на ней в конструкторе ф ор­
мы и прописывая код для метода b u tto n l_ C lic k ; (), вы рабо­
У г 1 |^ а ж н е н и е
таете с событиями.

Q Создайте проект Windows Application. Щ елкните на кнопке Events (она помечена а^


значком молнии) в окне Properties формы. Откроется список событий:

просмотра событий,
язаннш с элементом
управления, выделите Properties П X
эт от элемент и щ елк­ F o n n l'''5 y ^ m .W in d o w s .F o r m s .F o r m
ните на кнопке со значком Найдите строчку Click
молнии. и дважды щелкните на
_________ І ней. ^удет добавлен но­
C h a n q eU IC u es вый обработчик события,
Чтобы создать со­
бытие, возникающее FormI.Ctick который акт ивизиру­
при щелчке на фор ClientSizeChanged ется п р и любом щ елч­
м е, нужно выбрать СontextMenuStripC ке на форме. В файле
в раскрывающемся formX.Designer.cs по­
списке в строчке Ctick явится новая строка,
Click вариант связывающая обработчик
Occurs when the component is dkkerf.
Forml^Click. и событие.

О После двойного щелчка на строчке Click к форме будет автоматически добавлен обработ­
чик события Forml_Click. Введите для него код:
private void Forml_Click(object sen d e r , EventArgs e) {
M e ss a g e B o x .S h o w (" Y o u j u s t c l i c k e d o n t h e f o r m " ) ;
}

© Visual Studio не только пишет за вас объявление метода, но и связывает обработчик с со­
бытием Click формы. Откройте файл Forml .Designer .cs и воспользуйтесь функцией
Quick Find (Edit » Find and Replace » Quick Find) для поиска текста Forml_Click. Вы
найдете строчку:
this.Click += n e w S y s t e m . E v e n t H a n d l e r ( t h i s . F o r m l _ C l i c k ) ;

Запустите программу и убедитесь, что код работает!

страниїзу u продолжим

дальше > 507


представление страницы событий
_ 'lacsno
Несколько обработчиков одного события ^адаБаеМ ы е
БоЛ|Эо<;ь1
События можно соединять в цепочки, чтобы одно событие вызывало
один за другим несколько методов. Добавим к вашей форме несколько Почему после добав­
кнопок, чтобы посмотреть, как это работает. ления нового обработчика
событий к объекту Pitcher
появилось сообщение об
Добавьте к форме два метода:
исключении?
private void SaySomething(object sender, EventArgs e) {
MessageBox.Show("Something"); Q ; ИСР добавила код,
вызывающий исключение
} NotlmplementedException, что­
бы напомнить вам, что метод
private void SaySomethingElse(object sender, EventArgs e ) {
пока не реализован. Данное
MessageBox.Show("Something else"); исключение обычно исполь­
зуется как местозаполнитель,
}
когда вы создаете скелет клас­
са, но пока не готовы писать
Добавьте к форме две кнопки и двойным ш;елчком на каждой из них код целиком. Появление этого
добавьте обработчики событий: исключения говорит, что вам
private void buttonl_Click(object sender, EventArgs e) { просто нужно закончить работу
над кодом.
this.Click += new EventHandler(SaySomething);
}
private void button2_Click(object sender, EventArgs i {
this.Click += new EventHandler(SaySomethingElse)
}
Вспомните о том, что каждая из кнопок связывает новый обработчик с событием Click фор­
мы. В первых трех шагах вы использовали ИСР, чтобы добавить обработчик событий, вызыва-
юш;ий окно диалога при ш;елчке на форме, - код с оператором +=, осуществляющим привязку
к обработчику, вставлялся в файл Forml.Designer.cs.
Теперь вы добавили кнопки, которые используют аналогичный синтаксис, чтобы сформировать
цепочку дополнительных обработчиков события Click. Подумайте, что будет, если запустить про­
грамму и по очереди щелкнуть на первой, потом на второй кнопке, а потом на самой форме.

Обработчики событий должны быть «связаны».


Если перетащить кнопку на форму, добавить
У ыпе I метод b u t to n l_ C lic k () с правильными пара­
о с щ о р о ж н ь ! ; метрами, но не связанный с кнопкой, он не
будет вызываться. Дважды щелкните на кнопке
в конструкторе, ИСР возьмет заданный по умолчанию обра­
ботчик ЬиЬЬоп1_сИск_1 () и добавит его к кнопке.

508 глава 11
события и делегаты

Запустите программу и выполните следующие действия:


★ Щелкните на форме. Появится окно с текстом «You just clicked on the form».

Обработчик события
^ ___ Click формы вызвал

— окно с сообщени­
ем «Вы только что
щелкнули на форме».

★ Теперь щелкните на кнопке buttonl, а затем снова на форме. Появятся два окна
с текстом: «Youjust clicked on the form» и «Something». 4:— '

Каждый щ ел­
чок на кнопке
приводит к
появлении:) еще
одного окна
диалога.
★ Дважды щелкните на кнопке button2, а затем снова на форме. Появятся четыре
окна: «Youjust clicked on the form», «Something», «Something else» и «Something else».

Так что же происходит?


При щелчке на кноп­
Каждый щелчок на одной из кнопок по цепочке вызывает другой ме­ ках по цепочке вы­
тод — Something () или SomethingElse () в ответ на событие Click ф ор­ зываются другие
мы. Если продолжить щелчки на кнопках, продолжится вызов тех же мето- ^ обработчики события
дов. Событию безразлично количество методов в цепочке, вы даже можете Click формы.
несколько раз подсоединить один и тот же метод. Все они будут вызываться
Т
в том порядке, в котором были добавлены, при каждом появлении события.
Э то означает, что щелчок
Form1_Click()
I на кнопках не даст ника­
кого результат а! Сначала
нужно щелкнут ь на форме,
так как кнопки меняют ее
► SaySomethingO U
поведение, внося изменений
в событие Click.
SaySomethingElseO В цепочку можно
несколько раз
добавить один и
SaySomethingElseO т от же метод.

% дальше > 509


дающие и принимающие

Сбязь ме)кду издателями и подписчиками


Одним из самых сложных для понимания при изучении событий является обстоятельство, что и з д а ­
т е л ь ( s e n d e r ) должен знать, какое событие он инициирует, в том числе, какие аргументы он ему пере­
дает. И п о д п и с ч и к ( r e c e i v e r ) должен знать, какой тип возвращаемого значения и аргументы следует
использовать для методов обработчика событий.
Но связи между издателем и подписчиком нет. Издатель инициирует событие и ему все равно, кто на него
подписан. А подписчику важно, что это за событие, но все равно, какой объект стал его причиной. Получает­
ся, что как издатель, так и подписчик сфокусированы на событии, но не друг на друге.

до/

“*Г н .й
4 У
Собы тие BalllnPlay

Св prtcV'®’
О б ьект Ball не должен и м ет ь
связи с объект ом P itcher Ему
все равно, объект ы какого т ипа
работ аю т с событием: Fan,
Pitcher, U m pire и т. п.

«Мой народ хочет Вступить 6 контакт с вашим народом».

Вы знаете, что делает этот код:


B a ll c u r r e n tB a ll;

Он создает с с ы л о ч н у ю п е р е м е н н у ю на любой объект B a ll.


Эта переменная может указывать даже на значение null. Де-ле-гат, сущ.
Событиям нужна такая же ссылка, но указывающая не на объ­ человек, имеющий
ект, а н а м е т о д . Событиям нужно отслеживать список подпи­ полномочия пред­
санных на них методов. Как вы уже видели, эти методы могут ставлять других, пре­
принадлежать другим классам и даже быть закрытыми. Как же
отследить все обработчики событий, которые будут вызывать­
зидент отправил деле­
ся? Для этого используются д е л е г а т ы ( d e l e g a t e ) . гата на саммит.

510 глава 11
события и делегаты

Делегат замещает методы


нужно только указать
Возникающее событие н е з н а е т , методы-обработчики событий каких объектов сигнатуру методов,
будут вызваны. Каким же образом событие может управлять этим процессом? на которые он может
ссылаться.
Для этого в C# существуют д е л е г а т ы ( d e le g a t e ) . Это ссылочный тип, позволяю- !
щий с с ы л а т ь с я н а м е т о д ы в н у т р и к л а с с а .. . также делегаты являются основой А
для событий.
Данный делегат может ссы­
В этой главе вам уже приходилось с ними сталкиваться! При созда­ латься нд любой метод, ис­
нии события BalllnPlay вы работали с делегатом EventHandler. пользующий в качестве п а ­
Щ елкните на нем правой кнопкой мыщи и выберите команду «Go to рамет ров оьект и EventArgs
definition», и вот что вы увидите. и не возвращающий значения.

\
public delegate void EventHandler(object sender, EventArgs e)
Эт от элемент сигнатуры делегата Имя делегата
показывает, что EventHandler может EventHandler.
ссылаться только на методы, не
возвращающие значения. Vу п ] ^ а ж^ н е н и е
Добавляем к проекту ноВый т и п данных
Добавляя к проекту делегаты, вы добавляете данные нового типа, Используя их для создания поля или
переменной, вы создаете э к з е м п л я р этого типа. О т к р о й т е н о в ы й п р о е к т C o n s o le A p p l i c a t i o n и добавьте
к нему новый файл классов ConvertsIntToString.cs. Введите одну строчку:
delegate string ConvertsIntToString(int i); Еще одним добавленным в про­
ект делегатом является
Добавьте в класс Program метод HiThere (): ConvertsIntToString. Его мож­
но использовать для объявления
private static string HiThere(int i) Сигнатура этого переменных точно так же, как
{ 'F - метода совпадает вы делали бы это для класса или
return "Hi t h e r e ! #" + (i * 100)
с ReturnsAString. интерфейса.
)
someM etkod — это переменная типа
Заполните метод M ain (): ConvertsIntToString. О т обычной ссылочной
s t a t i c v o i d M a i n ( s t r i n g [] args)
переменной она отличается т ем , что цка-
^ зывает не на объект в куче, а на м ет о1
{
ConvertsIntToString someMethod = new ConvertsIntToString(HiThere);
string message = s o m e M e t h o d (5);
console.writeLine(message) ; д П е р е м е н н о й

}
Переменная someMethod указывает на метод HiThere (). Записью someMethod (5) вызывается метод
HiThere (), которому передается аргумент 5. В итоге возвращается строкаН1 there! #500. Просмотрите
программу в режиме отладки, чтобы понять, что именно происходит.

дальше ► 511
делегируйте свои полномочия

Делегаты В geiicmBuu , ,
Работать с делегатами легко, они не требуют большого объема кода. По- \Г '
пробуем с их помош;ью помочь владельцу ресторана рассортировать се- J T lj^& yK H 0H U 0,
кретные ингредиенты с кухни шеф-повара. ^

О Добавьте делегат в новый проект


Делегаты обычно располагаются вне классов, поэтому добавьте к проекту новый файл
классов G e tS e c r e t I n g r e d i e n t . c s и введите в него строку:
delegate string GetSecretlngredient(int amount);

(Полностью удалите объявление класса.) Этот делегат станет основой переменной, указы-
ваюш;ей на метод, который, взяв параметр типа i n t , возвраш;ает строку.

О Добавьте класс для первого шеф-повара, Сюзанны


Suzanne.cs содержит класс, следяш;ий за секретными ингредиентами, которые исполь­
зует Сюзанна. Он снабжен закрытым методом SuzannesSecretlngredient () с сигнату­
рой, совпадающей с GetSecretlngredient. Имеется и предназначенное только для чте­
ния свойство, возвращающее GetSecretlngredient. С его помощью остальные объекты
могут получить ссылку на метод SuzannesIngredientList ().
c la s s Suzanne {
Взяв перем ен- p u b l i c G e t S e c r e t l n g r e d ie n t M y S e c r e tln g r e d ie n tM e th o d {
ную am ount get {
т од% т вр^ает = fe tu m new G e t S e c r e t ln g r e d ie n t ( S u z a n n e s S e c r e t ln g r e d ie n t ) ;
строку, onucbi- ^
бающую ce- J
кретный ингре- p r i v a t e s t r i n g S u z a n n e s S e c r e t l n g r e d i e n t ( i n t a m o u n t) {
диент. — aj, r e t u r n a m o u n t . T o S t r i n g 0 + " o u n c e s o f c l o v e s " ;
}
} Свойство GetSecretlngredient
возвращает новый экземпляр
п < _ делегата GetSecretlngredient,
Добавьте класс для второго шеф-повара. Эми ^ указывающего на метод п о лу-
Методы Эми работают так же, как методы Сюзанны: / цтия секретных ингредиентов.
д c la s s Amy { М
м ет од получе- p u b l i c G e t S e c r e t l n g r e d i e n t A m y s S e c r e t l n g r e d i e n t M e t h o d (
ния секретных get {
ингредиентов, r e t u r n new G e t S e c r e t ln g r e d ie n t ( A m y s S e c r e t ln g r e d ie n t ) ;
c которыми }
работает Эми, }
т ак же берет ( p r i v a t e s t r i n g A m y s S e c r e t l n g r e d i e n t ( i n t a m o u n t ) {
переменную if (a m o u n t < Ю ) .
am ount типа < r e t u r n a m o u n t .T o S t r in g O
int и возвраща- ) ^ Ise " s a r d in e s - - y o u n e e d m o re!" ;
em строку. Ho / r e t u r n a m o u n t .T o S t r in g O + " c a n s o f s a r d in e s " ;
это уже другая ч }
строка. }

512 глава 11
события и делегаты

Secret Ingredients
Добавьте к проекту делегаты
Постройте форму. — Gei the ingredient

Это ее код: Get Suzannes delegate


GetSecretlngredient i n g r e d i e n t M e t h o d = null;
Suzanne Suzanne = n e w S u z a n n e (); Get Amy's delegate
A m y a m y = n e w A m y ();

p r i v a t e v o i d u s e I n g r e d i e n t _ C l i c k (object sen d e r , EventArgs e) {


if (ingredientMethod != null)
C o n s o l e . W r i t e L i n e ( " I ' 11 a d d " + i n g r e d i e n t M e t h o d ( (int) a m o u n t . V a l u e ) );
else
Console.WriteLine("I don't have a secret ingredient!");
}
p r i v a t e v o i d g e t S u z a i m e _ C l i c k (object s ender, EventArgs e) {
ingredientMethod = new GetSecretlngredient(suzanne.MySecretlngredientMethod);
}

private void getAmy_Click(object sender, EventArgs e) {


ingredientMethod = new GetSecretlngredient(amy.AmysSecretlngredientMethod);
}

О Посмотрите на работу делегатов при помощи отладчика


Воспользуйтесь отладчиком, чтобы понять принцип работы делегатов:
★ Запустите программу. Щ елкните на кнопке «Get the ingredient», на консоль будет выведе­
на строка «I d o n ’t have а secret ingredient!»
★ Щелкните на кнопке «Get Suzanne’s delegate», которая присваивает полю i n g r e d i e n t - ,
M ethod формы (являющемуся делегатом G e t S e c r e t l n g r e d i e n t ) значение, возвращаемое
свойством G e t S e c r e t l n g r e d i e n t . Это новый экземпляр типа G e t S e c r e tl n g r e d i e n t,
указывающий на метод S u z a n n e s S e c r e tl n g r e d i e n t ().
★ Снова щелкните на кнопке «Get the ingredient». Теперь, когда поле in g re d ie n tM e th o d
указывает на S u z a n n e s S e c r e tl n g r e d i e n t (), произойдет вызов этого метода, и его зна­
чение будет передано numericUpDown (убедитесь, что он называется am ount), а затем
выведено на консоль.
★ Щ елкните на кнопке «Get Amy’s delegate». Она использует свойство А т у .G e t­
S e c r e t l n g r e d i e n t , чтобы присвоить полю in g r e d ie n tM e th o d значение A m y sS e c re t­
l n g r e d i e n t ().
★ Снова щелкните на кнопке «Get the ingredient». Будет вызван метод Эми.
★ Поместите точки останова в первые строчки каждого из трех методов формы. П ерезапус­
тите программу (чтобы метод in g re d ie n tM e th o d стал равен null) и снова выполните
все вышеуказанные шаги. Используйте функцию Step Into ( F ll). Отслеживайте, что про­
исходит при щелчке на кнопке «Get the ingredient».

%
дальше > 513
чересчур открытые события

Б бассейне

public FormlО {

InitializeComponentO ;

t h i s . ________ += n e w E v e n t H a n d l e r (Mini van) ;

t h i s . ________ += n e w E v e n t H a n d l e r {___________

}
void Towtruck(object sender, EventArgs e) {

Возьмите фрагменты кода из бас­ Console.Write("is coming ");


сейна и заполните пропуски. Каждый
фрагмент может быть использован не­
}
сколько раз. В бассейне есть лишние void Motorcycle(object sen d e r , EventArgs e) {
фрагменты. Вам нужно, чтобы при b u t t o n l . ________ += n e w E v e n t H a n d l e r (________________ ) ;
щелчке на кнопке b u t t o n l на консоль
выводился следующий результат: }
void Bicycle(object sen d e r , EventArgs e) {

Результат: C o n s o l e . W r i t e L i n e ("to g e t y o u ! " ) ;


F in g e r s is c o in in g to g e t you!
}
v o i d ________________( object se n d e r , EventArgs e) {

b u t t o n l .________ += n e w E v e n t H a n d l e r (Dumptruck) ;

b u t t o n l . ________ += n e w E v e n t H a n d l e r (________________);

}
v o i d ________________( object se n d e r , EventArgs e) {

Каждый фрагмент C o n s o l e . W r i t e (" F i n g e r s ");

может быть ис­


пользован не­
сколько раз.

514 глава 11
события и делегаты

Объект мо)кет подписаться на событие...


Предположим, для симулятора был создан класс Bat (Бита), который добавляет событие HitTheBall.
Обнаружив, что игрок ударил по мячу, симулятор вызывает метод OnHitTheBall {) объекта Bat, кото­
рый вызывает событие HitTheBall.
Поэтому к классу Ball можно добавить метод bat_HitTheBall, который подпишется на событие
HitTheBall. При ударе по мячу уже его собственный обработчик вызовет метод OnBalllnPlay () для
уже его собственного события BalllnPlay, и цепочка начнет работу. Игроки играют, болельщики виз­
жат, судьи кричат... всё как на реальном матче.
Обработчик получает
информацию о силе удара,
С им улят ор обнаружил О б ш кт Ball подписан определяет расстояние
ст олкновение м я ч а на событие HitTheBall. и траекторию и вызыва­
с Зит ой, поэтому ет событие BalllnPlay.
был бызбан м ет од
ОпНИТкеВаЮ биты.

в& ^
Событие HitTheBall
Вал
вит
О й ' Э т о ж е были резер
м ячи, добавленные на вс ЯКий
'1С
случаи.

...НО э т о не Всегда хорош о! i


Н о беззаботный п р о грам м ист
в игре может присутствовать только один мяч. Но если объект Тодписал и х бее н а событие
Bat при помощи события передаст информацию об ударе по H itTheB all- поэт ом у после
мячу, подписаться на него сможет любой объект Ball. Пред­ идаюа битой по МЯчу,
ставьте, что программист случайно добавил еще три объекта
Ball. Что произойдет в этом случае? После удара битой по полю
fr r a c Ä — -
мяча! ^
разлетятся четы ре мяча!

дальше > 515


обратный вызов

Обратный Вызов
События в системе работают корректно, если объекты Ball и Bat имеются в единственном числе. В си­
туации, когда мячей больше одного, все они оказываются подписаны на событие HitTheBall и при его
возникновении вбрасываются в игру, что не имеет никакого смысла. Другими словами, нам нужно свя­
зать с битой всего один мяч, исключив возможность привязки других мячей.
в этом нам поможет о б р а т н ы й в ы з о в ( c a llb a c k ) . Так называется техника работы с делегатами, при
которой вместо события, доступного для подписки любым объектам, используется метод (часто кон­
структор), с хранящимся в закрытом поле делегатом в качестве аргумента. Обратный вызов позволит
нам гарантировать, что объект Bat оповещает всего один объект Ball:

О Объект B at сохраняет поле делегата закрытым


Предотвратить возможность создания цепочек из объектов Ball поможет закрытие хра­
нящего эту информацию поля. В этом случае объект Bat управляет вызовом нужного ме­
тода объекта Ball.

О Конструктор объекта B at
Когда мяч оказывается в игре, конструктор создает экземпляр биты и передает указатель
на него методу OnBalllnPlay ( ). Это м е т о д о б р а т н о г о в ы з о в а , так как объект Bat ис­
пользует его для вызовы объекта, который его создал.
Объект Ball передает
сш лк у на делегат свое-
_ м у собственномц методи
ОпВаІІІпРІауО в конст рук-
1 \^ ^ торе объекта Bat. По­
следний сохраняет данный
делегат в закрытом поле
hitriaeBallCallback.
О Когдо Bat ударяет по мячу, он использует метод обратного вызова
Но пока Bat скрывает делегат, можно быть полностью уверенным в том, что ни
один другой мяч в игру не попадет. Вот решение проблемі.]!

-Г hitBalICalIback
Теперь объект
t Bat может вы­
звать делегат
hitBallCallhackj ко­
Д ругие мячи не м огут торый, в свою оче­
подсоединиться к эт о ­ редь, вызовет м е ­
м у делегату, так как тод OnBallInPiayQ
данное поле объекта объекта Ball.
Bat закрыто.

516 глава 11
событ ия и делегаты

Случай с золотым крабом


Іенри «Простак» Ходкингс — охотник за сокровищами (ТгеазигеН ипЪег).
Сейчас он выслеживает одно из самых желанных и редких ювелирных изделий на
морскую тему —инструктированного нефритами золотого краба. Но охотников
за сокровищами много. И в их конструкторах так же присутствует ссылка на этого
краба. Іенри же хочет найти сокровище первым.
IJ яго и М и ш ^ щ н а я Из украденных диаграмм классов Генри узнал, что как только кто-то
тайна приближается к крабу, класс Со1(іепСгаЬ вызывает событие Кип-
F o rC o v er (Побег в укрытие). Более того, событие включает Неід^Ьоса-
tio n A r g s (Новое место), указывающее, куда переместился краб. Но так
как остальные охотники не знают о событии, Іенри считает, что сможет
получить сокровище первым.
Генри добавляет в конструктор код, превращающий метод tr e a s u r e _ R u n -
F o rC o v er () в обработчик события R unForC over. Затем кто-то посылается за
крабом, чтобы тот спрятался и вызвал событие R unF orC over —это даст методу
tre a s u re _ R u n F o rC o v e r () всю нужную информацию.
Все шло по плану, пока Генри не прибыл на нужное место и не обнаружил, что там
за краба уже дерутся три его конкурента.
К аким же образом они т ам оказались? ----------------- ► 0 ш Б е т На с. ^ 2

К о н с т р у к то р public Forml0 { і ^ і п е н и е p * e ^ c :a
последователь­ I n i t i a l i z e C o m p o n e n t ();
но добавляет t h i s . Load += n e w E v e n t H a n d l e r ( M i n i v a n ) ; Б бассейне у
два обработчи­
ка к событи!^М
this. Load += n e w E v e n t H a n d l e r (Motorcycle) ;

Load. В итоге }
они вызываются void Towtruck(object s ender, EventArgs e) {
сразу после за ­ C o n s o l e . W r i t e ("is c o m i n g ");
грузки формы.
}
void Motorcycle(object se n d e r , EventArgs e) {

buttonl .Click += n e w E v e n t H a n d l e r (Bicycle) ; ■'f'^^f^P^iom4UKa события


Load привязывают к об­
работчику события Click
void Bicycle(object sen d e r , EventArgs e) { кнопки три других, от
Щелчок на кнопке - — C o n s o l e . W r i t e L i n e ("to g e t y o u ! " ) ; дельных обработчика.
вызывает цепочку
из т рех связанных void Minivan ( object se n d e r , E v e n t A r g s e) {
с ней обработчи­ buttonl .Click -1-= n e w E v e n t H a n d l e r (Dumptruck)
ков событий. b u t t o n l .Click += n e w E v e n t H a n d l e r (Towtruck) ;

}
void Dumptruck (obj e c t sen d e r , EventArgs e) {
C o n s o l e . W r i t e (" F i n g e r s ");

дальше > 517


оставьте сообщение, я вам перезвоню

Обратный Вызов как способ работы с делегатами


Обратный вызов является е щ е о д н и м с п о с о б о м р а б о т ы с д е л е г а т а м и .
Он описывает ш а б л о н —способ, при котором вы используете делегатов
в своих классах таким образом, что один объект может сообщить другому 1 ГУ^ра^ение!
«Сообщи мне, когда это случится!»

Добавим в проект еще один делегат


® Так как объект Bat хранит делегата в закрытом поле, указывающем на метод, новый делегат должен
иметь совпадающую сигнатуру: Обратный вызов обьекта Bat будет
d e l e g a t e v o i d B a t C a l l b a c k ( B a l l E v e n t A r g s e) ; Указывать на мет од OnBalllnPlayQ
г. -у а о этого Объекта, поэт ому делегат об-
Созааоать для делегатов отдельные ф ай- /у ратного вызова должен им ет ь ана-
лы необязательно. Этот делегат пом е- \ логичную сигнатуру — использовать
ст ит е в файл с классом Bat. V___ в качестве парамет ра BallEventArgs
_ _ не возвращать значения.
Добавим к проекту класс B at
Это очень простой класс. Он содержит метод HitTheBall (), запускающийся при каждом ударе
по мячу, который с помощью делегата hitBallCallback () вызывает метод OnBalllnPlay ()
(передаваемый в конструктор).
class Bat {
private BatCallback hitBallCallback;
Д ля предот - public Bat (BatCallback callbackDelegate) {
вращения UC- this.hitBallCallback = new BatCallback(callbackDelegate);
ключения нужно }
public void HitTheBall(BallEventArgs e)
ссылается ли
какой делегат -----=> i f (hitBallCallback != null)
на значение null. h i t B a l l C a l l b a c k (е)

}
Мы воспользовались оператором =, так как в данном случае нужно, чтобы объект bat
получал сообщения только от одного объекта ball, соответственно, данный делегат
настраивается всего один раз. Но ничто не запрещает написать обратный вызов с
оператором +=, который будет адресован нескольким методам. Основной смысл
обратного вызова — в отслеживании вызывающим объектом адресатов. В случае
события, объекты требуют оповещения, добавляя обработчики, в то время как при
обратном вызове объекты перебирают делегатов и просят об оповещении.
Свяжем биту с мячом
© ...........
Каким гг__________
же образом конструктор объекта Bat получает ссылку на метод OnBalllnPlay () определен­
ного мяча? Он вызывает метод GetNewBat {) (Получить новую биту) этого объекта, который мы
сейчас добавим:
Метод QetNewBatÇ) создает
p u b l i c Bat G e t N e w B a t О объект Bat и использует де­
легат BatCallBack для передачи
{ ссылки на эт от новый объ­
return new Bat(new BatCallback(OnBalllnPlay)) ект собственному методу
) / OnBallInPiayQ. Именно эт от
мет од обратного вызова будет
применен битой в м омент уда­
ра по мячу.
его 8 m L oS „ л » м З “ /„ Г “
518 глава 11
события и делегаты
Инкапсулируем класс B a l l
Методы, вызывающие события, названия которых начинаются с On... не бывают открытыми.
Попробуйте в форме вызвать событие OnClick () кнопки playBall, вы не сможете это сделать,
так как оно защищено (в итоге производный класс может перекрыть его). Сделаем такой уровень
доступа и для метода OnBalllnPlay ():
protected void OnBalllnPlay(BallEventArgs e) {
EventHandler<BallEventArgs> balllnPlay = BalllnPlay; й

H (b a llln P la y „ u lll ^
b a llI n P la y (th is , e ) ,■ N E T

О стал ось с в я за т ь м етод с ф орм ой ется с «Оп».


Форма больше не может вызывать метод OnBalllnPlay () объекта Ball, как мы
и хотели. Для этого и был вставлен метод B a l l . GetNewBat {). Теперь форма
должна попросить у объекта Ball новую биту для удара по мячу. При этом метод
OnBalllnPlay () нужно связать с обратным вызовом биты.
private void playBallButton_Click(object se n d e r , EventArgs e)

{ Если форма (или симулятор) ко-


Bat bat = ball.GetNewBat0 ; ~~ ’ чеил ударить по мячу, ей пот ре­
буется новый объект Bat. Мяч
BallEventArgs ballEventArgs = new BallEventArgs(
гарант ирует связь обратного
(int)trajectory.Value, (int)distance.Value); вызова с битой. При вызове м ет о ­
bat.HitTheBall(ballEventArgs); да HitTheBall() биты он вызывает
мет од OnBallinPlayO мяча, иници-
} ируюи^ий событие BalllnPlay.
Запустите программу, она должна работать, как и раньше. Но теперь в ней невоз- ^
можно появление набора мячей, реагирующих на одно и то же событие.
Не верьте нам на слово, посмот рите
на работу программы в режиме
отладки!
КЛЮЧЕВЫЕ
МОМЕНТЫ
Добавляя к проекту делегата, вы создаете новый Все элементы с панели toolbox используют события.
тип, хранящий ссылки на методы. Когда один объект передает другому ссылку на
С помощью делегатов события оповещают объекты метод таким образом, что этот второй объект может
о произведенных действиях. вернуть информацию, это называется обратным
вызовом.
Объекгы подписываются на события, если им нужно
реагировать на происходящее с другими объектами. Методы могут подписываться на события анонимно,
EventHandler — это вид делегата, часто работа­ в то время как обратные вызовы позволяют объектам
ющий с событиями. контролировать делегаты.

С одним событием можно связать несколько обработ­ Обратные вызовы и события используют делегатов
чиков. Именно поэтому для присвоения обработчика для ссылки и вызова методов других объектов.
событию используется оператор +=. Чтобы понять, как работают делегаты, используйте
Всегда проверяйте события и делегаты на равенство null. отладчик.

дальше > 519


паттерны проектирования

_ Часто
даД аБ аеМ ы е
Bollj=»oCbi

Чем обратный вызов отличается от события? Получается, что обратные вызовы — это всего лишь
закрытые события?
! События и делегаты — это часть .NET. Это способ, которым
одни объекты оповещают других о произведенных действиях. На Q ; Не совсем. Проще всего представлять их таким образом,
событие может подписаться произвольное количество объектов, но закрытые события имеют свою специфику. Помните, что на
при этом издатель лишен возможность узнать о них. При запуске самом деле означает модификатор доступа p r i v a t e ? Доступ
события все подписанные на него объекты запускают обработ­ к помеченному им члену класса имеют только экземпляры этого
чики. класса. Поэтому, пометив событие как p r i v a t e , вы запре­
щаете подписываться на него из других классов. В то время как
Обратные вызовы не принадлежат .NET — это всего лишь на­ обратные вызовы допускают анонимную подписку.
звание способа использования делегатов (или событий, ничто
не мешает вам создать обратный вызов из закрытого события).
Но обратный вызов выглядит как событие, не снабжен­
Этим термином всего лишь называются отношения между двумя
ное ключевым словом event.
классами, при которых объекг запрашивает оповещение. В слу­
чае же событий объекты требуют оповещений.
Q l Обратный вызов похож на событие, потому что они оба ис­
пользуют делегатов. Это имеет смысл, так как обратный вызов
То есть обратные вызовы не принадлежат .NET?
является инструментом, позволяющим одному объекту передать
другому ссылку на свой метод.
Q ; Нет. Обратный вызов — это шаблон, способ использования
существующих типов, ключевых слов и инструментов. Рас­ Но событие — это способ, которым класс оповещает мир о неких
смотрите внимательно код обратного вызова, написанный для действиях, в случае же обратных вызовов оповещения отсут­
биты и мяча. Присутствуют ли там неизвестные вам ключевые ствуют. Они являются закрытыми, и метод, осуществляющий
слова? Нет! Но при этом там не используются делегаты, которые вызов, отслеживает, кто именно вызывается.
являются типом .NET

Шаблонов существует множество. Есть даже отдельная область


программирования — паттерны проектирования. Ведь мно­
гие проблемы, с которыми вы можете столкнуться, уже решены,
и вам достаточно воспользоваться подходящим паттерном.

520 глава 11
событ ия и делегаты

Случай с золотым крабом IJffmuMuHijrtiHaff гоайна


Почему другие охотники опередили Генри? р’асК І^ьш іа
Разгадка в том, как именно наш охотник искал каменоломню.
Впрочем, начнем с изучения украденных Генри диаграмм.
Генри обнаружил, что при попытке приблизишься к крабу класс
GoldenCrab вызывает событие RunForCover. Событие включает
NewLocationArgs, с информацией о новом месте укрытия. Так как
конкуренты о событии не знают, Генри решил, что победа будет за ним.
class GoldenCrab {
public delegate void Escape(NewLocationArgs e);
public event Escape RunForCover;
p u b l i c v o i d S o m e o n e s N e a r b y () {
NewLocationArgs e = n e w N e w L o c a t i o n A r g s (" П о д к а м н е м " ) ;
RunForCover(e); Как только к крабц
} кт о-т о приближ айся
} мет од ВотеопевЫеагЬуО
class NewLocationArgs { инициирует событие
public NewLocationArgs(HidingPlace newLocation) { ^ипРогСоуег, и краб п р я ­
чется в другом м е с т [
this.newLocation = newLocation;
}
private HidingPlace newLocation;
public HidingPlace NewLocation { get { return newLocation; } }

Как же Генри воспользовался полученной информацией?


Благодаря полученной ссылке на краба Генри добавил к своему конструктору метод
treasure_RunForCover() в качестве обработчика события RunForCover. Затем оп послал за
крабом помощника, зная, что это станет причиной побега краба и появления события
RunForCover, которое даст методу treasure_RunForCover() всю нужную информацию.
class Tr easureH unter {
public TreasureHunter(GoldenCrab treasure) {
treasure.RunForCover += n e w G o l d e n C r a b .E s c a p e ( t r e a s u r e _ R u n F o r C o v e r ) ;

void treasure RunForCover (NewLocationArgs e) { Генри ЗуМал, ч т о добавление К KOHC^yK

’ бытия RunForCover у краба, — это хит


v o i d M o v e H e r e (HidingPlace Location) { O H забыл, что все охотники

и код перемещения в новое место зд %кровиш,ами наследуют от f


того L класса поэтому ^^o fd J o 6 a 6 u
в цепочку и их обработчики событии
}

Вот почему Генри потерпел поражение. Добавив обработчик событий конструктору ТгеазигеН ипЪ ег,
он нечаянно сделал это для всех охотников] Обработчики событий всех охотников оказались связанными
с одним и тем же событием RunForC over. и сообщение о перемещении краба в новое укрытие пришло
всем. Можно было бы обернуть дела в свою пользу, если бы Генри получил сообщение первым. Но Генри
не мог узнать, в каком порядке проводится оповещение. В итоге все подписавшиеся до него получили
новость о местоположении краба раньше.

дальше > 521


убей моль!

ОЗЬМИ в руку карандаш


Заполните пробелы, чтобы игра «Убей моль» заработала. Вам нужен код, поддерживаю­
щий обратные вызовы. Когда программа будет готова, проверьте ее работу в ИСР!

pu b l i c p a r t i a l class Forml : F o r m {
M o l e mole;
R a n d o m r a n d o m = n e w R a n d o m {);
public F o r m l 0 {
I n i t i a l i z e C o m p o n e n t ();

m o l e = n e w M o l e ( r a n d o m , n e w M o l e . ________ J) ;
t i m e r l .I n t e r v a l = r a n d o m . N e x t (500, 1000)
timerl.Start 0 ; форма передает делегата
} указыван>и1,ий на метод
private void timerl_Tick(object sen d e r , EventArgs e) обратного вызова в кон­
timerl.Stop 0 ; ст рукт оре моли.
T o g g l e M o l e () ;

Э т о т /""^private void ToggleMole 0 {


if ( m o l e . H i d d e n == true)
метод
m o l e .S h o w ();
управляет ю т СО­
else
появлением mole.HideAgainO ; МЫ поговорим
и исчезнове­ t i m e r l .I n t e r v a l = r a n d o m . N e x t (500, 1 0 0 0 )
нием моли t i m e r l .S t a r t ();
по р езуль-j
т ат ам p r i v a t e v o i d M o l e C a l l B a c k ( i n t m o l e N u m b e r , b o o l show) {
работы if ( m o l e N u m b e r < 0) {
таймера. t i m e r l .s t o p () ; F o rm lx s [Design] -г X
return;
} Щ W h a c k -a -m o le
B u t t o n button;
s w i t c h (moleNumber) {
6
Ьлок^switch case button = buttonl; break
^арантиру - case button = button2; break
ет измене­ case button = buttons; break
ние текста case button = button4; break
и цвета у default: b u t t o n = button5; break;
^^равильной Перетащите элемент
Кнопки. (show == true) {
b u t t o n . T e x t = "HIT ME!"; , imeri ' Tim er из окна toolbox
b u t t o n . B a c k C o l o r = C o lor. Red; '* л и дважды щелкните
} else { ^ на нем.
b u t t o n . T e x t = ""; IB!
b u t t o n . B a c k C o l o r = S y s t e m C o l o r s .C o n t r o l ;
Добавьте пять обработ­
t i m e r l .I n t e r v a l = r a n d o m . N e x t (500, 1000)
чиков событий для кнопок.
t i m e r l . S t a r t (); buttonZ_click() вызывает
} m ole.Sm acked(l) и заст ав­
private void buttonl_Click(object sen d e r , EventArgs е) { ляет button3 вызвать
m o l e .S m a c k e d (0) mole.Smacked(Z)>что за -
} Добавьте эти обработчики со- ^ ст авляет button4 вызвать
бытий обычным способом, то mole.Smacked(3) и b u tto n s
есть двойным щелчком на кноп- вызвать mole.Smacked(4).
ках в конструкт оре формы.

522 глава 11
события и делегаты

using System.Windows.Forms;

class Mole {
Впиш им е делегаш, и поле, в к о ­
public void PopUp(int hole, bool show) т о р о м он будет хранит ься,
,ода п а р а м е т р а должны нахо­
p r i v a t e _____________ p o p U p C a l l b a c k ; дит ься в верхней част и класса
p r i v a t e bool hidden;
Mole.
p u b l i c bool H i d d e n { get { re t u r n hidden; } }
З десь м ы п роверя ем н е ­
p r i v a t e in t t i m e s H i t = 0;
равен ст во обрат ного
p r i v a t e in t t i m e s S h o w n = 0;
p r i v a t e i n t h o l e = 0;
вызова null, в п р о т и в ­
R a n d o m random; ном случае объ ект Mole
/в ы з о в е т исключение
p u b l i c M o l e ( R a n d o m random, Po p U p p o p UpCallback) { у / A rgu m en tE xception .
if ( p o p U p C a l l b a c k == null)
t h r o w n e w A r g u m e n t E x c e p t i o n (" p o p U p C a l l b a c k c a n ' t b e null")
t h i s . r a n d o m = random;
При создании нового о б ъ ­
t h i s .___________
ект а Mole ф о р м а п е р е ­
h i d d e n = true; дает ссылки на его м ет од
} обрат ного вызова. П осмо­
т р и т е , как именно вы ­
public void S h o w O { зы вает ся к о н ст рукт ор
timesShown++; ф орм ы , и заполнит е эт о
h i d d e n = false;
поле.
h o l e = r a n d o m .N e x t (5)

(hole, true)

т ек ст «H IT ME!» ^ ^<^ображает
public void Hi d e A g a i n O
h i d d e n = true;
М етоды Н1с(еАда1пО и 5таскес1()
(hole. false); также п ользую т ся дел егат ом
C h e c k F o r G a m e O v e r () обрат ного вы зова, чтобы вы зват ь
м ет о д ф орм ы .
public void Smacked(int holeSmacked) {
if ( h o l e S m a c k e d == hole) { В игре использует ся т а й м ер со
timesHit++; случайной задерж кой о т 0 . 5 до Л..5
h i d d e n = true; секунд. После т ого как врем я выилло,
C h e c k F o r G a m e O v e r ();
он вы зывает м оль. О брат ны й вызов
объект а Mole заст авля ет ф о р м у
(hole, false) п оказат ь или скры т ь м оль в одном
из пят и полей, ф орм а и сп ользует
т а й м ер , чтобы через п ром еж у­
т ок врем ени (о т 0 .5 до i-.S секунд)
private void CheckForGameOver( { уб р а т ь моль.
if ( t i m e s S h o w n > = 1 0 ) {
p o p U p C a l l b a c k (-1, fal s e ) ;
MessageBox.Show("You scored + timesHit, " G a m e over"
Application.ExitO ;
^ И гра заканчивает ся после т ого, как м оль
'У появит ся ХО раз. Баши очки п оказы ваю т ,
сколько р а з вам удалось ее прихлопнут ь.

дальше ¥ 523
решение упражнения

возьми в руку карандаш


Вот как должен выглядеть код программы «Убей моль».
'ешение
public partial class Forml : Form {
private void Forml_Load(object sen d e r , EventArgs e) {

mole = new Mole(random, n e w Mole,


Popup MoleCallBack
t i m e r l .I n t e r v a l = r a n d o m . N e x t (500, 1000);
t i m e r l .S t a r t ();
} Форма передает ссылку на
} мет од MokCallBackQ о Ь е к т у
Mole, что позволяет мол« вы­
class Mole {
зывать данный метод.
М О Л & о п р е д е ля е т свой
public _ void P o p U p d n t hole, bool show) д е л е га т и и с п о л ь з у е т его
для задания закры т ого
private
Popup popUpCallback; п о ля , в к о т о р о м б уд ет
х р а н и т ся ссы лка на м е - ^
mod в форме, меняющий
ц в е т кнопок.
p u b l i c M o l e ( R a n d o m ran d o m , PopUp popUpCallback) {
t h i s . r a n d o m = random;

popUpCallback
t h i s .___________
popUpCallback
h i d d e n = true;
} Создав новый экземпляр объекта Mole,
public void S h o w O {
\ форма передает конструкт ору в ка-
timesShown++;
\ честве парамет ра ссылку на метод
h i d d e n = f a lse;
MoleCallBackQ. Эта строка в кон­
hole = r a n d o m .N e x t (5);
ст рукт оре копирует ссылку в поле
popUpCal back. Методы конст рук­
popUpCallback тора при помощи этого поля м о ­
(hole, true) гут вызывать мет од MoleCallBackQ
} в форме.
public void Hid e A g a i n O {
h i d d e n = true;

popUpCallback (hole, false)


C h e c k F o r G a m e O v e r (); V — К огда м о л ь по ка зы ва ет ся, пря
} чется и ли оказы вает ся у д и -
т Т 1 о б ъ ек т ^о 1 е и % о л ^
public v o i d Smacked(int holeSmacked) { д е лега т рорирС аН Ьаск °
if ( h o l e S m a c k e d == hole) { зова м е т о д а , м ен я ю щ его цбе
timesHit++; и т е к с т кнопок ф о р м ы .
h i d d e n = true;
C h e c k F o r G a m e O v e r ();

popUpCallback (hole, false)

524 глава 11
12 обзор и преДБа]=пхіхіеЛьНьіЄ реЗуЛьніазпьі

^ Знания, сила и построение


классных приложений

Знания нужно применять на практике.


П ока вы не начнете пи сать р аб о таю щ и й код, вряд ли м ож но б ы ть уве р е н­
ным, что вы на сам ом д ел е усвоили слож ны е понятия С # . С е й ч а с мы за й ­
м ем ся прим енением на практике п ол уче н н ы х р анее знаний. Кром е того,
вы найдете пр ед вари тельны е сведения о понятиях, которы е будут р ас­
см атри ваться в с л ед ую щ и х главах. М ы начнем построение действитель­
но сложного приложения, что бы закрепить ваш и навыки.
так много информации

Вы прошли долгий путь


Много времени прошло с момента, когда вы взялись за свой первый код для
объектвильской фирмы по производству бумаги. Ниже перечислено многое
из того, чему вы научились за несколько сотен страниц.

Вы создавали формы, пользовались


средствами .NET Framework и даже
обменивались данными с базой.
.N ET Fram ew ork
Реш ение объект ы ,
4 ры... все эти повседневную Party
NumberOPeople
CosfôfDecorations

CalculateCostOfDecorationsO
CalculateCostO

SetLocationO
б а з а д анны х SetDestinationO DinnerParty BirthdayParty
M odifyRouteToAvoidO NumlwrOfPeopte NumljerOfPeople
Вы научились работать M odifyRouteTolncludeO CostODecorations CostODecorations

даже с такими сложны­ GetRouteO


GetTimeToDestinationO
CostOffieveragesPerPerson
HealthyOption
CakeSize
CakeWriting
ми типами данных как TotalDistanceO CalculateCostOfDecorationsO CalculateCostOfDecorationsO
массивы. CalculateCostO CalculateCostO
SetHealthyOptionO

eimii
7 переменных т ипа in t

в«

Вы боретесь с п р о - ' " ' ' ^ ^


олемами при по МО-
щи, отладки и и Т
хлючений. 'в п <te¥^
p r i v a t e v o id ra n d o M E x c u s e _ C iic k (o b je c t s e n d e r, EventArgs. в )
{
if ( file W a ie e s .L e n g th == 0)
{
ft"s.‘ia g € 3 o x .S h o w (” P ie a s e s p e c i f y a f o l d e r w i t h e x c u s e f i l e s in it"
" H o excu se f i l e s f o u n d " ) j
}______
526 глава 12
обзор и предварительные результаты

Вы стали пчелоВодом
Помните, как в главе 6 вы моделировали работу улья? Разные виды пчел выполняли
разную работу...

Но мо)кно сделать ßce намного лучше... Окно ст ат ист ики п о ­


зволяет отслеживать
Вы многому научились за это время. Поэтому начнем снача­ работу симулятора.
ла и построим аним ированны й симулятор улья. Создадим
пользовательский интерфейс, показывающий улей и поля,
iß Beehive Simylator
над которыми летают пчелы, а также статистику произве­
денных пчелами действий. Pause simulation Reset ^
# Bees 6
Sflowera 11
Totrfhoneyh thetwe 1.200
TtAdnetJarhftefield 34.^
Окно HiVe Framesfun 383
Ї Ї Ї с З т е » в ад«- Franersts 16 ^S2.5msj
kSe;1bee
FlywigToFtow«-:2bees
GMheringNectar: 1bee
R^iBrtngToKve: 2bees

Можно даже на­


блюдать за рабо­
той пчел d поле.

дальше > 527


не слишком сложно... да?

Архитектура симулятора улья


Вот как выглядит улей изнутри. Несмотря на необходимость кон­
тролировать множество пчел, общая модель довольно проста.

О бьекм \AJorid следит за


работой симулятора: за
оош,им состоянием улья
основное окно,
отображающее
ст ат ист ику
и сообщения.

Sysfem.V'fi'

fire элементы архитектуры


лТпТаю т пчелиный мир,

интерфе.йс.

О бьект World
представляет
общую кар­
тину.

'разумеется, мы
Для каждого не обойдемся без
цветка пот ре­ класса Вее.
буется обьект
Flower.

528 глава 12
обзор и предварит ельны е результ ат ы

Построение симулятора улья


М ы п о к а н е со зд авал и п р о е к т о в та к о го у р о в н я сл о ж н о сти , п о это м у н а под ­
готови тел ьн у ю р аб о ту п р и д е т с я за т р а т и т ь пару глав. Вам п р е д с т о и т н а ­
уч и ться р а б о т а т ь с та й м е р а м и , о с в о и т ь в с т р о е н н ы й я зы к за п р о с о в L IN Q
и при ем ы работы с граф икой.
В от ч ем вам п р е д с то и т за н я т ь с я в эт о й главе;

О С о з д а т ь к л а с с F lo w e r, в к о т о р о м ц в е т ы р а с п у с к а ю т с я ,
д а ю т н ектар, в я н у т и ум ир аю т.

© С о зд ать кл асс В ее с н аб ором р азл и чн ы х состояни й


(с б о р н е к т а р а , в о з в р а щ е н и е в у л е й ) и н а б о р о м з а д а н и й
в зав исим о сти от состояния.

О С о з д а т ь к л а с с H iv e , с о д е р ж а щ и й в х о д , в ы х о д , и н к у б а ­
то р пчел и ф а б р и ку д л я п ер ер аб о тки со б р ан н о го н екта­
ра в мед.

О С о з д а т ь к л а с с W o rld , у п р а в л я ю щ и й у л ь е м , ц в е т а м и
и пчел ам и в каж д ы й м о м ент врем ени.

О П о стр о ить ф о р м у д л я со б о р а стати сти ки о состоянии


классов и управл ения ульем .

дальш е ► 529
поню хаем цвет ы

'^пражнение П е р е й д е м к н а п и с а н и ю к о д а . Н а ч н е м с к л а с с а F lo w e r . О н с о ­
реш ение д е р ж и т и нф о р м ац и ю о пол ож ен ии ц ветка, во зр асте и про дол ж и­
т е л ь н о с т и ж и з н и . Н у ж н о с д е л а т ь т а к , ч т о б ы ц в е ты с о в р е м е н е м а .w vca
с т а р е л и и у м и р а л и . Э т и м вы с е й ч а с и з а й м е т е с ь . ^ с к е л е т о м класса
oSv^eACH f
Основа класса Flower ^о ка не н.ап\лсана реа
Н а о с н о в е д и агр ам м ы кл асса F l o w e r н ап и ш и те его скелет. L o c a t i o n (М есто п о л о ж ен и е),
А де (В о зр аст), A l i v e (Ж и в о й ), N e c t a r (Н ек т ар ) и N e c t a r H a r v e s t e d (С о б р ан н ы й н е к та р ) -
автоматические свойства. П о с л ед н ее сво й с тв о п е р е за п и с ы в ае м о , в то в р ем я как п е р в ы е ч е ­
т ы р е п р е д н а зн а ч е н ы только для чтения.

Все эти свойства, F lo w e r


кроме N ectarH arvested, Location; Point
^р&дназначеиы только Age: int П осле д в о е т о чи я
для чтения. ^
Alive: bool аказан
б е р е м е н н о м ...
Nectar: double
Эт о поле используется
только внутри класса, NectarHarvested: double
поэт ом у его нужно lifespan: int
сделать закрытым.
HarvestNectarQ: double 7 -м ли т ип возвраш,аемого
Go() Ь мет одом значения.

О Необходим ы е константы
Конст ант ы обычно
не показываются на
Д о б а в и м в класс F lo w e r ш е сть к о н с та н т: диаграмме классоО.

♦ Lif eSpanMin - м и н и м а л ь н а я п р о д о л ж и т е л ь н о с т ь ж и з н и ц в е т к а .

♦ Lif eSpanMax м акси м ал ь ная п р о д о л ж и тел ь н о сть ж и з н и ц в етка.

♦ InitialNectar — с к о л ь к о н е к т а р а и з н а ч а л ь н о с о д е р ж и т ц в е т о к .

♦ MaxNectar - м а к с и м а л ь н о е к о л и ч е с т в о н е к т а р а , к о т о р о е м о ж н о с о б р а т ь с ц в е т к а .

♦ NectarAddedPerTurn - к о л и ч е с т в о н е к т а р а , д о б а в л я е м о е п о м е р е р о с т а ц в е т к а .

♦ NectarGatheredPerTurn - к а к м н о г о н е к т а р а у д а е тс я с о б р а т ь за о д и н ц и к л .

о с н о в у з н а ч е н и е к а ж д о й к о н с т а н т ы , в ы б р а ть д л я н е е т и п . Ц в е т ы ж и в у т о т
15 ООО д о 3 0 ООО ц и к л о в , и в н а ч а л ь н ы й м о м е н т и м е ю т 1 .5 е д и н и ц ы н е к т а р а . К о л и ч е с т в о
н е к т а р а м о ж е т д о х о д и т ь д о 5 е д и н и ц . З а о д и н ц и к л п р и б а в л я е т с я 0 .0 1 е д и н и ц ы н е к т а р а
и 0 .3 е д и н и ц ы м о ж е т б ы ть с о б р а н о . 4 ^ ’

Анимированные симуляторы созда-


^ ^ Р м ш ы «ка др»,
Ха в наилем случае
являются взаимозаменяемыми

530 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

В верхней части класса, и спол ь зую щ его тип Point, д о л ж н а бы ть


стр о ка using System.Drawing;.

О П о стр о ен и е конструктора
К о н с т р у к т о р д о л ж е н п е р е д а в а т ь с т р у к т у р у Point, з а д а ю щ у ю п о л о ж е н и е ц в е т ­
к а , и э к з е м п л я р к л а с с а Random. Н у ж н о у к а з а т ь , ч т о в о з р а с т ц в е т к а р а в е н О, ч т о
о н ж и в о й , а т а к ж е п р и с в о и ть ему начал ь н о е ко л и ч е ств о н е кта р а . Н а к о н е ц , тр е ­
б у е т с я р а с с ч и т а т ь п р о д о л ж и т е л ь н о с т ь ж и з н и ц в е т к а . В о т с т р о ч к а , к о т о р а я в ам
пом ож ет:
lifeSpan = random.Next (LifeSpanMin, LifeSpanMax -н 1);

Х Г и Г » Г е р е Э .6 » .« «
конст рукт ору Flower.

М ето д HarvestNectar О
П р и к а ж д о м в ы зо в е э т о г о м е т о д а п р о в е р я е т с я , д о с т и гл о л и к о л и ч е с т в о с о б р а н ­
н о г о н е к т а р а м а к с и м у м а , к о т о р ы й м о г у т д а ть ц в е т ы . В э т о м с л у ч а е м е т о д в о з­
в р а щ а е т 0. В п р о т и в н о м случае в о зв р ащ ается к о л и ч е с тв о с о б р а н н о го н е к т а р а ,
ко то р о е добавляется к п е р е м е н н о й N e c ta r H a r v e s te d , х р а н я щ е й и н ф о р м а­
ц и ю об общ ем сборе с каж д о го ц ветка. ^

Подсказка: В эт ом мет оде использиются


только конст ант ы N ecta rG atheredP erTurn
N ecta r и N ectarHarvested.

О М ето д Go {)
К а ж д ы й в ы зо в э т о г о м е т о д а с о о т в е т с т в у е т п р о ж и в а н и ю ц в е т к о м о д н о г о ц и к л а ,
зн а ч и т, д о л ж е н о б н о в л ять ся и е го в озраст. Н у ж н о п р о в е р я ть , н е д о сти гл а л и
э та п е р е м е н н а я за д а н н о го для ц в е тк а в р е м е н и ж и з н и . В случае п о л о ж и те л ь н о ­
го резул ь тата ц в е т о к ум и р ает.
К ж и в ы м ц в етам н у ж н о д обав и ть ко л и ч е с тв о п р о и з в е д е н н о го за ц и к л н е кта р а .
П р о в е р я й те , н е п р ев ы ш ает л и о н о м акси м ал ь но в о зм о ж н о е зн ач е н и е .

Результ ат ом будет анимация с м а ­


ленькими лет ающ ими ф игуркам и пчел.
Мет од <5о() вызывается для каждого
кадра, при т ом ч т о показывается не­
]^ е Ш е н и е н а с Л е д у і° Ш е й а іїр а н и Ц е ..,
сколько кадров в секунду. Но с н а Ч а Л а т іо іїр о б у й т е н а п и с а т ь
U с К о М о и Л и р 'о Б а г о ь К о д

дальш е * 531
куда пропали цвет ы ?

1
не н ке В о т к а к в ы гл я д и т к л а с с Flower д л я с и м у л я т о р а у л ь я . F lo w e r
Location: Point
)^еш енке class Flower { Age: int
private const int LifeSpanMin = 15000; Alive: bool
private const int LifeSpanMax = 30000; Nectar: double
private const double InitialNectar = 1 .5 ; NectarHarvested: double
private const double MaxNectar = 5.0;
lifespan: int
private const double NectarAddedPerTurn = 0.01;
private const double NectarGatheredPerTurn = 0.3; HarvestNectar(): double
свой ст ва public Point Location { get; private set; } Go()
L o c a t io n .,

{
public int Age { get; private set; }
Aü v e U Nectar
к\реЭназнйчене>| public bool Alive { get; private set; }
Д ост уп к полю
т о л ь к о Эля public-----------------------
double Nectar { get; private Oset; ^}

чтени я.
^ ^ -t. V d u e C U ;
N ectarH arvested
public
ГМ 1 T -i ^ double
/-ЧЧ ЛV-4 1 Л NectarHarvested
^ 4- _ ^ T T _ _________ ______ □ {f get;
_ set; } <2^ ^ должны имет ь
private int lifeSpan; другие классы.

public Flower(Point location, Random random) {


Location = location;
Age = 0;
Цвет ы и м е ­ Alive = true;
ю т различную Nectar = InitialNectar;
>^родожитель-- NectarHarvested = 0 ;
ность жизни. lifeSpan = random.Next(LifeSpanMin, LifeSpanMax + 1);
} Пчелы вызывают мет од
H arvestN ectarQ для сбора нектара.
М ет од а о () вызы­ public double HarvestNectar0 { За один раз пчела забирает совсем
вается для каждо­ if (NectarGatheredPerTurn > Nectar) немного нект ара, поэт ом у мет од
го кадра. Возраст return 0; отслеживает состояние цветка
цветка за один цикл else { на протяжении ряда кадров, пока
увеличивается не­ Nectar -= NectarGatheredPerTurn; нект ар не закончится.
значительно,, но по NectarHarvested += NectarGatheredPerTurn;
м ере работы с и м у ­ return NectarGatheredPerTurn;
лятора эти значе­ }
ния сум м ирую т ся. }

public void Go О { Н ект ар следует


Age++; добавлять только
if (Age > lifeSpan) Живым цветам.
Alive = false;
else { 1/
Nectar += NectarAddedPerTurn;
if (Nectar > MaxNectar)
Nectar = MaxNectar;
, ^ П ерем енны е ти п а P o in t п р и н ад л еж ат про стр ан ств у им ен
System.Drawing, ПОЭТОМу не Забудьте ДОбаВИТЬ СТрОЧКу using
System.Drawing; В верхнюю часть ф зйла класса.

532 г л а в а 12
обзор и пред вар и т ельны е результ ат ы

Жизнь U смерть цВетка 4acni°


Задаваем ы е ___
К а ж д ы й ц в е т о к п р о х о д и т базовы й ц и к л , р а стет, н а ка п л и в а е т Б о І 1р>осГьі
н е кт а р , отд ает э то т н е кт а р пчел ам и н а ко н е ц ум ир ает:
^ 5 Переменная NectarHarvested
только увеличивает свое значение,
а больше нигде в классе не использу­
ется. Зачем она нужна?

^ ; Это заготовка на будущее. Симу­


лятор должен отслеживать состояние
цветов и количество собранного не­
ктара. Эта переменная была оставлена
в открытом доступе, именно потому что
она потребуется другим классам.

Чем старм е. Почему все автоматические


ст ановит е свойства предназначены только для
и ве т о к , чтения?
дет.
; Помните разговор в главе 5 об
О
уровнях доступа? В данном случае дру­
гие объекты симулятора, то есть улей
и пчелы, должны иметь возможность
чтения этих полей, но не записи в них.
собирают н е- При этом вы можете их редактировать
О & ь е у :^
его^п^ УЛ1ен&и.<йют внутри класса при помощи закрытого
его общее количество

I
У Каждого цветка. метода записи.

• Мой код отличается от вашего.


Я сделал какую-то ошибку?

аде - 30291 1
nectar = ,83
Г ; Вы могли написать код по-другому,
но если он функционирует так же,
s s r
alive = false как и наш, все в порядке. Особенность
і paem - < инкапсуляции в том, что внутренняя
структура классов не важна для других
/ классов, нужно только, чтобы класс вы­
O Q y S (S полнял свою функцию.

ШТУРМ
М етод Со () увеличивает возраст цветка на 1 при продолжительности ж изни от
15 ООО д о 30 ООО циклов. То есть м етод Со {) д ля одного цветка будет вызван
по м еньш ей м е р е 15 ООО раз. Как обработать та ко е количество вызовов?
А в случае 10 цветов? 100? 1000?

дальш е ► 533
т руд я га -пч ел а

Класс Вее
Д л я цветов, г о т о в ы х дать нектар, п о н а д о б и т с я класс Вее, в к о т о р о м с о д е р ж а т ­
ся свед е н и я о возрасте п ч е л ы , ее м е с т о п о л о ж е н и и и в о з м о ж н о м колич е с т в е со­
б и р а е м о г о нектара. В класс б ы л т а к ж е д о б а в л е н метод, п е р е м е щ а ю щ и й пчелу
в н у ж н у ю точку пространства.

class Bee {
private const double HoneyConsumed = 0.5;
private const int M o v e R a t e = 3;
P l o w e r ^ ^
private const double MinimumFlowerNectar = l
•только с эт им к л а ссо м ""'''''''’'''
private c onst int CareerSpan = 1000;

public int A g e { get; private se t ; }


public bool InsideHive { get; private set; }
ла определяй ^<^Nectar пче-
public double NectarCollected { get; private set; }

private Point location; Д л я задания местоположения


public Point Location { get { return location; (использовалось вспомога -
тельное поле. А в т о м а т и ­
^ — Каждой пчеле присваивается ческие свойства в данном
private int(^; уникальный номер ID- случае не пом огут , т ак как
p r iv a t e Flower~3estinationFlower; м ет од МоуеТош аЫ 5иосаНоп()
не о состоянии задать зна­
чения своих членов напрямую
public Bee{int id, Point location) { (иосаЬюп.Х -= МоуекаЬе).
this.ID = id; . Пчела должна знать
Age = 0; ID и свое
t h i s .l o c a t i o n = location; ^^ст о п о ло ж ен и е.
InsideHive = true;
d e stlMtio„Flo«er = null; ( “
NectarCollected = 0; > нет цветов, н Т к о т п Т '''^ бремени
}

public void Go(Random random) {


Age++;
}

534 г л а в а 12
обзор и предварит ельны е результ ат ы

М ет од M atkA bsO вычисляет модуль разно­


сти между пункт ом назначения и т екущ им
положением- ^

private bool MoveTowardsLocation(Point destination) { ~Ч хлчала мы


if (MatbC^s^tdestination.X - location.X) <= MoveRate && ^ х 'о д и т с я
не н а -
— w,; Au пчела
Math.Abs(destination.У - location.Y)
return true; J Ka МенЬиАвМ

M oveR ue paccm o-
ямии от пункт а
ЧРЛЛ

^ т ц ^ е т ^ ' (destination.X > location.X) назначения.


значение location.X += MoveRate;
tru e при e l s e if (destination.X < location.X) Если пчела находится недост а­
достиже-^ location.X -= MoveRate; точно близко к пункт у назначения,
HUU пчелой она смещ ает ся в его направлении
пункт а н а­ на величину, определяемую к о эф ­
значения. if (destination.Y > location.Y) фициент ом смещения.
location.Y += MoveRate;
else if (destination.Y < location.Y)
М ет од MoveTowardsLocationQ
location.Y -= MoveRate;
^ смещ ает положение пчелы,
меняя се коорЭ инл ил м X м Y.
return false; - М ет од возвращает К а к только пчела достигает
значение false, т ак как пункт а назначения, он
пункт назначения не ■ возвращает значение true.
дост игнут и нужно
‘продолжать движение.

Н и ж е п е р е ч и с л е н с п и с о к д е й с тв и й , котор ы е д о л ж н ы вы пол нять пчелы . С о з д а й ­


т е д л я о б ъ е к т а Вее п е р е ч и с л е н и е BeeState ( С о с т о я н и е п ч е л ы ). Д л я к а ж д о й и з
пчел п о тр еб уется п р е д н а зн а ч е н н о е то л ь ко д л я ч те н и я а в то м а ти ч е с ко е свойство
Currentstate ( Т е к у щ е е с о с т о я н и е ) . В н а ч а л ь н ы й м о м е н т в р е м е н и п ч е л а н а х о ­
д и т с я в с о с т о я н и и idle ( Н е з а н я т ы й ). Д о б а в ь т е к м е т о д у Go () о п е р а т о р switch
с п а р а м е т р а м и д л я ка ж д о го э л е м е н т а п е р е ч и с л е н и я .

Элемент функция
Idle Пчела ничем не занята
FlyingToFlow er Пчела лет ит на цветок
G atkerin gN ectar Пчела собирает нект ар
Retu rningToH ive Пчела возвращается в улей
M akingHoney Пчела производит мед
R etired Пчела прекращ ает работ у

дальш е > 535


п че л а , с п о к о й н о !

'а ж н е н и е о сно в е д ан н о го на п р ед ы д ущ ей с тр а н и ц е сп и с ка пчелины х за н я ти й


'р т о и к о к л а с с а ве е б ы л о с о з д а н о п е р е ч и с л е н и е Beestate. З а к р ы т о е п о л е
currentstate п р е д н а зн а ч е н о д л я о тс л еж и в а н и я состояни я каж д о й пчелы .

епш п B e e s t a t e {
I d le ,
F ly in g T o F lo w e r ,
Это перечисление
G a th e r in g N e c ta r , возможных состояний
R e tu r n in g T o H iv e , пчелы.
M a k in g H o n e y ,
R e tir e d
}

class Bee {
// объявление констант
// объявление переменных

p u b lic B e e s ta te C u r r e n tsta te { g et; p r iv a te se t; }

public Bee(int ID, Point initialLocation) {


this.ID = ID;
Age = 0;
location = initialLocation;
InsideHive = true;
C u r r e n tsta te = B e e s t a t e . I d l e ; ^
destinationFlower = null; Сначала пчелы ничего
NectarCollected = 0; не делаю т .
}

Вы не забы ли д о б ав и ть в верхню ю часть ф ай л а кл асса строку


us ing Sys tem.Drawing;?

536 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

М ы ввели код обработки не­


public v o i d G o ( R a n d o m random) О п ер а т о р sw itc k f) которых состояний. Ничего
Age++; обрабат ы вает страшного^ если вы не см ог­
сост ояние каждой ли написать его самостоя­
switch (Currentstate) {
1лчелы. тельно, просто воспользуй­
case B e e s t a t e .I d le : т есь нашим вариантом.
if (Age > CareertJpanj
CareerSpan) {
Currentstate = B e e s t a t e .Retired^^ ^ К огда переменная «5« .
сравнивает ся с переменной
else {
!1ре<тп, пчела уходит на
// Ч т о мы делаем в состоянии idle? Т н с ^ - Но перед э т и м она
должна закончит ь все данные
break; ей зйЭания.
case B e e s t a t e . F ly in g T o F lo w e r :
// Перемещение пчелы к выбранному цветку
break;
case B e e s ta t e .G a th e r in g N e c t a r : Мы собираем н е ­
^ double nectar = d e s t i n a t i o n F l o w e r . H a r v e s t N e c t a r () к т ар с вы бранно­
го цветка...
if (nectar > 0) ^ ------- если н ект ар еще
NectarCollected += nectar; ост ался, добавьт е его
Требует ся
обработ ат ь else ^ <^обранному...
g /e возможные Currentstate = BeeState.ReturningToHive;
состояний- break; ...а если нект ара не о с т а -^
лось, возвращайтесь в улей.
С case B e e s t a t e . R e tu r n in g T o H iv e :
if (!I n s i d e H i v e ) { ^ .
■ Возвращение в улей
// Перемещение в улей
зависит от т ого, где
} else { о данный м ом ент н а­
11 Ч т о п ч е л а д е л а е т в н у т р и у л ь я ? ходится пчела.
} break;
case B e e S t a t e .M a k i n g H o n e y :
Пчела от дает
if (NectarCollected <0.5) {
NectarCollected = 0;
Currentstate = B e e S t a t e .I d l e ; Г к^м йт ваЛ ^^ект ара
else {
ф абрика не
// Перерабатываем нектар в мед П оэт ом у ^иеля о т них
прост о избавляется.
break;
case B e e s t a t e .R e tir e d :
// Ничего не делаем, Работа закончена I
break;

дальш е k 537
внеш ний вид улья

Программисты против бездомных пчел


у нас есть п чел ы и п о л н ы е н е к т а р а ц веты . Н у ж н о н а п и с а ть ко д для с б о р а н е кт а р а ,
н о п е р е д э т и м за д у м а е м с я о т о м , о т к у д а п о я в и л и с ь п ч е л ы ? И куд а о н и п о н е с у т весь
э т о т н е к т а р ? О т в е т о м н а в о п р о с б у д е т кл а с с H i v e .

У л е й — н е п р о с т о м е с т о , куд а в о з в р а щ а ю т с я п ч е л ы . В н у т р и о н р а з д е л е н н а з о н ы
р а з л и ч н о го н а з н а ч е н и я . С ущ ес тв ую т вход и вы ход, п и т о м н и к для в ы р а щ и в а н и я
м о л о д ы х п ч е л и ф а б р и к а п о п е р е р а б о т к е н е к т а р а в м ед.

Зоны б н у т р м ул1?я
отделены друг от
друга, и пчела м о ­
жет переходить
из зоны в зону т ак
же, как она лет ит
из улья к цветку.

Нобы е іячел&і
їл о я б л я ю т с я из
lA.UWOMHWKfl-
Пчелы залет аю т в цлей
через вход, а покидают
е го через выход. Все
хорошо организовано.

Для )кизни улья ну)кен мед


С у щ е с т в о в а н и е уль я з а в и с и т о т к о л и ч е с т в а х р а н и м о г о Обдумайт е эт у информацию... мед
в н е м м ед а. М е д н у ж е н д л я ф у н к ц и о н и р о в а н и я с т а р ы х
нужен для функционирования улья и
создания новых пчел. Уже имеюш,ие£.я
и созд ания новы х пчел. С о о тв е тс тв е н н о , нам п о тр еб уется ^ пчелы собирают нект ар, который
ф аб р и ка, п ер ер аб аты ваю щ ая со б р ан н ы й п чел ам и н е кт а р ^"^— перерабат ывает ся в мед и позволяет
в м е д . И з е д и н и ц ы н е к т а р а п о л у ч а е т с я 0 .2 5 е д и н и ц ы м ед а. улью работ ат ь дальше.
Именно это (с небольшой помощ ью
с нашей стороны) вам предст оит
смоделировать.

538 г л а в а 12
о б з о р и предварительные р е з у л ь т а т ы

^ п р а ж нн е н и е В а ш а з а д а ч а — с о з д а т ь к л а с с H iv e .

Н а п и ш и те скел ет класса H iv e
К ак и в случае класса F lo w e r , нуж но н ач а ть со ске­ Honey; double
лета. Д и агр ам м а класса п о к а за н а сп рава. С д елай те locations: Dictionary<string, Point>
с в о й с тв о H o n e y п р е д н а зн а ч е н н ы м т о л ь к о д ля ч т е н и я , beeCount; int
п о л е l o c a t i o n s д о л ж н о б ы ть за к р ы т ы м , к ак и п о л е
b e e C o u n t. InitializeLocationsQ
AddHoney(Nectar; double); bool
ConsumeHoney(amount: double); bool
AddBee(random; Random)
О О пред ел и те константы
У к а ж и т е н а ч а л ь н о е к о л и ч е с т в о п ч е л ( 6 ) , м е д а (3 .2 ) ,
Go(random; Random)
GetLocation(location; string); Point
м а кс и м ал ь н о ко л и ч е с тв о м еда (1 5 ), к о э ф ф и ц и е н т п е р е ­
р а б о т к и н е к т а р а в м е д ( .2 5 ) , м а к с и м а л ь н о е к о л и ч е с т в о
п ч е л (8 ) и м и н и м а л ь н о е к о л и ч е с т в о м е д а , п р и к о т о р о м
п о я в л я ю т с я н о в ы е п ч е л ы ( 4 ). Присвойте констлн-
■т ам и м е н а

О Код , работаю щ ий с пол ем L o c a t i o n


Н а п и ш и т е м е т о д GetLocation ( ) . О н д о л ж е н б р а т ь
с т р о к у , с р а в н и в а т ь е е с о с л о в а р н ы м и з н а ч е н и я м и и в о з­
в р а щ а т ь с т р у к т у р у point. П р и о т с у т с т в и и с т р о к и д о л ж ­ Гх° н Г и о Т н а Т ен Т £ в ы р а ж е н и и .
н о п о я в л я т ь с я и с к л ю ч е н и е ArgumentException. в к о т о р ш эти константы
Sydym ф игурироват ь.
З а т е м н а п и ш и т е м е т о д InitializeLocations ( ) , за ­
д а ю щ и е к о о р д и н а т ы э л е м е н т о в улья:

В хо д (6 0 0 ,1 0 0 )
Все эт о привязано
П и т о м н и к (9 5 , 1 7 4 ) к т очкам в дву­
м ерном п ро ст ра н ­
Ф а б р и к а (1 5 7 , 9 8 ) стве улья.
В ы х о д (1 9 4 , 2 1 3 ) впользуеімся
к « - « « Г Г^ Г РсинТат“ ам
“ “ и.
-
р о в а и н ь їм и ^ ^ 0 -
Конструктор H i v e hpu м ож но
К о н с т р у к т о р д о л ж е н за д а в а т ь н а ч а л ь н о е к о л и ч е с т в о
м е д а в уль е и п о л о ж е н и е в н у т р е н н и х э л е м е н т о в , а т а к ­ ж е л а н и и зд _

ж е с о зд а в а т ь э к з е м п л я р R andom . З а т е м д л я к а ж д о й за­
р о ж д а ю щ е й с я п ч е л ы н у ж н о в ы зв а ть м е т о д A d dB ee {),
п е р е д а в а я е м у т о л ь к о ч т о с о з д а н н ы й э к з е м п л я р Random .

V о б ь е к т R andom добавляет случайное значение к положению объекта


^ Nursery. Эт о позволит избежать появления пчел в одной точке.

дальш е > 539


начнит е с модели

В а м н у ж н о б ы л о нач а т ь п о с т р о е н и е клас с а Hive.


ажнение Убедит есь в наличии ст рочки «using
S y ste m .D ra w in g ;» т ак как код
ешение и сп о льзует значения т ипа Point. Вы мож ет е вы драт ь для
конст ант и други е имена.
class Hive {
private const int InitialBees = 6;
К он ст ан т а
private const double InitialHoney =3.2; M axim um H oney м е ­
p r i v a t e c o n s t d o u b l e MaximutnHoney = 15.0; т е т с я в диапазоне
p r i v a t e c o n s t d o u b l e N e c t a r H o n e y R a t i o = .25; о т InitialHoney (3 .2 )
p r i v a t e c o n s t d o u b l e M i n i m u m H o n e y F o r C r e a t i n q B e e s = 4 0• эт о го значения,
p r iv a te c o n s t i n t M ,x i„ » E a e s = 8 ,- T Z T IZ A V
private Dictionary<string, Point> locations; именно э т о м у т и п у
private int beeCount = 0; f:r
принадлеж ит кон ­
ст а н т а InitialHoney.
овар,
public double Honey { get; private se t ; }

private void I n i t ii aa lliizzeeLLooccaattiioonnss ()


0 {
(
locations = new Dictionary<string, Point>()
locations.Add("Entrance", new P o i n t (600, 100))
l o c a t i o n s . A d d (" N u r s e r y " , n e w P o i n t ( 9 5 , 174))
l o c a t i o n s . A d d (" H o n e y F a c t o r y " , n e w P o i n t (157, 98) ) или эт о не будет
l o c a t i o n s . A d d (" E x i t " , n e w P o i n t (194, 213)); работ ат ь.
}
public Point GetLocation(string location) {
if ( l o c a t i o n s .K e y s .C o n t a i n s ( l o c a t i o n ) )
return locations[location];
else
t h r o w n e w A r g u m e n t E x c e p t i o n (" U n k n o w n location: + location)
} Э т о т м ет о д не позволяет
\ др уги м классам вносит ь
г V. изменения в словарь, т о
public Hive О
^ ест ь п еред нами при м ер
Honey = InitialHoney; инкапсиляции.
I n i t i a l i z e L o c a t i o n s {);
Random random = n e w R a n d o m ();
for (int i = 0; i < InitialBees; i++)
вм зй г«
AddBee(random);
} пчелы.
public bool AddHoney(double nectar) { return true; }
public bool ConsumeHoney(double amount) { return true; Э т о т код еще не
private void AddBee(Random random) { } написан, и сп оль-
зу й т е п уст ы е
public void Go(Random random) { } м ет оды в качест ве
м ест озаполнит еля.
М о ж н о добавт ь исключение N o tlm p le m en ted E x cep tio n ко всем
м ет о д а м , для кот оры х пока не написана реализация. Эт о п о ­
зволи т обнаруж ит ь м е с т а , в кот оры е сл едует дописат ь код.

540 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

Странны й способ
н а п и са н и я кода. П ч е л ы п о ка не знаю т
о ц ветах, а у л е й п о л о н п усты х объ явлений
м етод ов. И н и ч е го не работает, не так л и?

Код состав л яется из кусочков.


Б ы л о б ы п р е к р а с н о , е с л и бы к о д к л а с с а п и с а л с я з а о д и н р а з ,
к о м п и л и р о в а л с я , т е с т и р о в а л с я и о тк л а д ы в а л с я в с т о р о н у , ч т о ­
бы п е р е й т и к р а б о т е н а д с л е д у ю щ и м к л а с с о м . К с о ж а л е н и ю ,
это н ев о зм о ж н о .

В б о л ь ш и н с т в е с л у ч а е в к о д п и ш е т с я ф р а г м е н т за ф р а г м е н т о м .
М ы п р а к т и ч е с к и н а п и с а л и класс F lo w e r , а в о т для кл асса В е е
э т о с д е л а ть н е п о л у ч и л о с ь . В а м н у ж н о д о п и с а т ь , ч т о с л е д у е т
делать в к а ж д о м и з с о с т о я н и й .

И класс H iv e остал ся с м н о ж е с т в о м п усты х м етод ов . К р о м е


т о г о , п ч е л ы п о к а н и к а к н е с в я з а н ы с у л ь ем . П л ю с н е р е ш е н н а я
п р о б л е м а с м н о г о к р а т н ы м в ы зо в о м м е т о д а G o О ...

Н о м ы ещ е д аж е н е п р и с ту п и л и
к с о е д и н е н и ю классов д р уг с другом !
М ы разобрались с ар хи те ктур о й
и п р и с ту п и л и к п о с тр о ен и ю .

С начал а разраб аты в аем , затем строим


М ы начал и п р о е к т с п о н и м а н и я то го , ч то и м е н н о мы х о ти м полу­
ч и т ь в и т о г е : с и м у л я т о р ул ь я. М ы м н о г о е з н а е м о в з а и м о д е й с т в и и
т а к и х об ъ ектов к а к пчел ы , цветы , улей и о к р у ж а ю щ и й м и р . И м е н ­
н о п о это м у мы н а ч ал и с ар хитек туры , д аю щ ей пр ед с та в л е н и е о
т о м , к а к к л а с с ы р а б о т а ю т д р у г с д р у го м . Т о л ь к о п о с л е э т о г о м о ж н о
п е р е х о д и т ь к р а з р а б о тк е к а ж д о го и з классов.

Р а б о т а т ь н а д п р о е к т о м н а м н о г о п р о щ е , е с л и вы з а р а н е е п р е д с т а в ­
л я е т е к о н е ч н ы й р е зу л ь та т. Э т о в п о л н е о ч е в и д н о . И и м е н н о э т о п о ­
зволяет в н ести в ко н е ч н ы й п р о д укт зн ачи тел ь н ы е и зм е н ен и я .

д а л ь ш е *■ 541
добавьт е в улей м ет од Goß

Построение класса Hive


В е р н е м с я к к л а с с у H i v e и н а п и ш е м к о д д л я о т-
с у тс тв у ю ш :и х п о к а м е то д о в :

class Hive {
// объявление констант
// объявление переменных

// I n i t i a l i z e L o c a t i o n s ()
// G e t L o c a t i o n ()
// H i v e constructor

public bool AddHoney(double nectar) { V/


double honeyToAdd = nectar * NectarHoneyRatio; ■М п ровери м , ест ь ли
if (honeyToAdd + Honey > MaximumHoney) о у л ь е м ест о для т акого
количест ва меда.
return false;
Honey += h o n e y T o A d d ; ^ — При наличии м ест а
добавим мед. м ет од б ер е т перем енн ую
return true;
a m o u n t (количест во меда) и
} ^ ^ л я е т м ед из хранилищ а п от реби т е
public bool ConsumeHoney(double amount) { лям.
if (amount > Honey)
return false; Если количест во м еда м еньш е, чем т реб ует ся
else { пчелам , м ет о д возвращ ает значение False.
Honey -= amount; <-7
return true; Если м еда достаточно^
} из хранилища м ет о д возвращ ает
J значение tru e. Здесь создает ся
т очка, от ст оящ ая
^ p r i v a t e v o i d A d d B e e ( R a n d o m random)
от пит ом ника на 5 0
Создават ь b e e C o u n t + + ; единиц по осям X U Y.
пчел MO- i n t r l = r a n d o m . N e x t (100) - 50;
жет т о л ь -^ ^ ^ г 2 = r a n d o m .N e x t (10 О ) - 50;
ко э к з е м ­
пляр Hive. P o i n t S t a r t P o i n t = n e w P o i n t ( l o c a t i o n s [" N u r s e r y " ] .X + rl, Новая пчела п о ­
locations["Nursery"].Y + r2) являет ся в м ес т е
Bee newBee = n ew B e e ( b e e C o u n t , StartPoint)
с вычисленной
координатой.
// Новую пчелу следует добавить в существующую систему

public v o i d G o (Random random) { }


} напиш ем м ет од Qo()..

542 г л а в а 12
о б з о р и п р е д в а р и т е л ь н ы е результаты

Метод 6 о () для улья


М ы у ж е п и са л и м ето д Go () для классов F l o w e r и В ее 5^йнст^еннйлі
( х о т я .и н е з а к о н ч и л и э т у р а б о т у ). В о т м е т о д G o ( ) д ля
к л а с с а Hive:

public void Go(Random random) { ^<оличесЛ"медТ


if (Honey > MinimumHoneyForCreatingBees)

AddBee(random);
T o m ж е экзем пляр R an dom ,
■ к о т о р ы й передавалсям ет о д у PoQ,
п ередает ся м ет о д у AddBee[)-

К с о ж а л е н и ю , э т а м о д ел ь д а л е к а о т р е а л ь н о с т и . Ч а с т о п ч е л и н а я
м а тк а улья о казы в ается н а с то л ь ко з а н я т а , ч т о н е и м е е т в р е м е н и
н а со зд ан и е нов ы х п ч ел . У нас н е т класса QueenBee, н о п р е д п о л о ­
ж и м , ч т о п р и н а л и ч и и н е о б х о д и м о го ко л и ч е ств а м еда новы е п ч е ­
л ы п о я в л я ю т с я в 1 0 % с л у ч ае в . Э т о м о ж н о н а п и с а т ь т а к :

public void Go(Random random) {


if (Honey > MinimumHoneyForCreatingBees
пчелы
&& random.Next(10)
6 оЭ иом 'из l o
AddBee(random);
создает ся новая пчела.

8 эт о м случае вы получает е
Ш м ож н осм ь сохранит ь
случайным образом ген ери руем ое
Часщо п от о м ст во и в будуи^ем
п овт орно за п у ст и т ь к а к и ю -т о
г ,аДаБаеМые кон крет н ую с и м у л я ц и ю .е с л и
Б о Іїр о С Ь і оам эт о за ч е м -т о нужно!

Может ли улей создать бесконеч­ Можно ли назначить экземпляр


Random свойству класса вместо пере­
/ р вы
. я все еще не понимаю, как будут
вызываться все эти методы Со О .
ное количество пчел?

0 ; в настоящий момент да, но такая


ситуация далека от реальности. Позднее
мы добавим ограничение на количество
дачи его методу AddBee () ?

О
; Разумеется. После этого метод
; й а в е е будет использовать это свойство
о! Мы как раз собираемся рассмотреть
этот вопрос. Для начала нам потребуется
класс W o r l d , отслеживающий происхо­
пчел, которые могут одновременно суще­ вместо передавамого ему параметра, Но дящее в улье, состояние всех пчел и даже
ствовать в улье. вряд ли имеет смысл так поступать. всех цветов.

дальш е > 543


пост роим мир

Все готобо для класса IVorM


И м е я п р а к т и ч е с к и г о т о в ы е кл а с с ы Hive, В е е и F l o w e r , м о ж н о п р и с т у п и т ь к п о с т р о е н и ю к л а с с а Worl d ,
п р е д н а з н а ч е н н о го для к о о р д и н а ц и и с о став ны х ч а с те й н а ш е го си м ул ятора. И м е н н о о н будет п о д д е р ж и ­
в а ть ж и з н е д е я т е л ь н о с т ь п ч е л , о п р е д е л я т ь н а л и ч и е м е с т а д л я н о в ы х п ч е л , р а з м е щ а т ь ц в е т ы и т. п .

Y~ko .cc World

u лиед-

классов
System.VW' ^ока не написан,
но все сост ав-
м Т с:“
Г “ «
Объект IVorld упрабдяеш миром
О с н о в н о й з а д а ч е й о б ъ е к т а W o r l d я в л я е т с я в ы зо в м е т о ­
д а G o ( ) в к а ж д о м к а д р е д л я к а ж д о г о э к з е м п л я р а Flow e r ,
В е е и Hive. Д р у г и м и с л о в а м и , о б ъ е к т W o r l d о б е с п е ч и ­
в а е т с у ш ;е с тв о в а н и е п ч е л и н о г о м и р а .

М е т о а ^ о ( ) й з кл ас­
са W orld вызывает,
одноименный м ет од
ОЛЯ веек объектов f o r e a c h (F lo w e r f lo w e r i n F lo w e r s )
пчелиного мира. f l o w e r . G o(ra n d o m );

f o r e a c h (В ее b e e i n B e e s)
b e e . Go (rctndom) ;
О сновная
ф орм а ^epe4env>'
(# ^ V , . . , ___
[h i v e . G o(randonO
1

5у8»ет.\№ ^''^° Н а м все равно придется


има/^ь дело с вызовом м й -
тода ОоО класса W orld, но
к эт ом у мы еще вернемся.

544 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

Система, осноВанная на кадрах


М е т о д Go ( ) д л я к а ж д о г о и з о б ъ е к т о в д о л ж е н в ы зы в а ть с я в к а ж д о м к а д р е н а ш е г о с и м у л я т о р а . П о д к а ­
д р о м в д а н н о м случае п од разум ев ается п р о и зв о л ь н о е к о л и ч е с тв о в р ем ен и : н а п р и м е р , к а р т и н к а м о ж е т
о б н о в л я т ь с я к а ж д ы е 1 0 с е к у н д и л и к а ж д ы е 6 0 с е к у н д л и б о к а ж д ы е 10 м и н у т .

С м ен а кад ров в л и яет н а с о сто я н и е к а ж д о го объ екта. У н ей с та р е е т и п ро в ер яет, не тр еб ую тся л и н о ­


вы е п ч е л ы . П ч е л ы п р о л е т а ю т н е б о л ь ш о й о т р е з о к п у т и п о н а п р а в л е н и ю к п у н к т у н а з н а ч е н и я , в ы п о л н я ­
ю т м а л ен ь ки й кус о к раб оты и л и стар ею т. К а ж д ы й ц в е то к п р о и зв о д и т небол ьш о е ко л и ч е ств о н е кта р а
и т о ж е с т а р е е т . Ю гас с World о т с л е ж и в а е т , ч т о б ы п р и к а ж д о м в ы зо в е м е т о д а Go ( ) с к а ж д ы м о б ъ е к т о м
происход или необходим ы е изм енения.

Каждый вызов ФО
в классе W orld приводит
я Л Ф этого ллетода для
всех объектов симулятора.

М е т о Э Go() должен
для каждой пчелы и каждого
цветка, иначе симулят ор
перест анет paSomamt^.

^ з ь м и в руку карандаш
П ри по стр о ени и си м у л я то р а б уд ет использован один из основны х
п ринципов о б ъ е ктн о -о р и е н ти р о в а н н о го п р о гр ам м и р о в а н и я — и н кап ­
сул яц ия. П р и в е д и те по д ва п р и м е р а и нкапсул яц и и д л я каж д ого из
созд анны х нам и классов.
Н і^ Вее Flower
1. 1. 1.

2. 2. 2.

дальш е * 545
ваш е м ест о в мире

Проблемы с инкапсуляцией!
Код для класса l/1/orld Обратите внимание на открытые
поля Hive, Bees и Flowers. Другой
К л асс W o r ld — о д и н и з сам ы х п р о с ты х в н а ш е м сим ул я то ­ класс может случайно присвоить им
р е. В о т о тп р а в н а я т о ч к а для н а п и с а н и я кода. Е сл и п р и с м о ­ значение null, что станет причиной
т р е т ь с я , вы у в и д и т е о т с у т с т в и е о т д е л ь н ы х ф р а г м е н т о в , к о ­ серьезных проблем! Подумайте, ка­
то р ы е мы совсем с ко р о добавим . кими свойствами и методами можно
воспользоваться для инкапсуляции.
using System.Drawing;
class World {
private const double NectarHarvestedPerNewFlower = 50.0;
private const int FieldMinX =15;
private const int FieldMinY = m ■. i KoopduHamfi,
определяющие границы
private const int FieldMaxX = 69 0
цветочного поля.
private const int FieldMaxY = 2 90

в м и р е сущ ест вует


public Hive Hive;
всего один улей,
public List<Bee> Bees; /^лножество пчел
public List<Flower> Flowers; и цветков.

public World 0 {
Bees = new List<Bee>(); ф о р т р у я новый м ир, мы
Flowers = n e w L i s t < F l o w e r > ()
Random random = n e w R a n d o m ()
. создаем улем м добавляем
for (int i = 0 ; i < 1 0 ; i++) %0 цветков.
AddFlower(random);
}

p u b l i c v o i d Go (Random random) { Здесь мы прост о вызывав,л л . .


H i v e . G o (random) ; < ----- - передавая ему
e-My экземпляр
экземпляр Random.

for (int і = Bees.Count - i >= 0 ; i--) {


Bee bee = Bees[i] ; — Мет од QoQ} вызывается для веек
b e e .G o ( r a n d o m ) ;
имеющихся в наличии пчел.
if (bee.Currentstate == Beestate.Retired) „
B e e s .R e m o v e ( b e e ); _______ ВышедыАуьо
■-------------- пчелу нужно удалить
} (дз нашего мира.

double totalNectarHarvested = 0;
Зля „.ж Э ого
for (int i = F l o w e r s .C o u n t - 1; i >= 0 ; І-) {
Flower flower = Flowers[i]; Нужно отслеживать
flower.Go 0 ; количество нектара,
totalNectarHarvested -t-= f l o w e r . N e c t a r H a r v e s t e d ; -собранное на каждом
if (!f l o w e r . A l i v e )
этапе. Поэт ому мы
сум м ируем нект ар,
F l o w e r s .R e m o v e (flower) ; собранный с каждого
} цветка.
ц вет ы такжр
следует удалить.

546 г л а в а 12
обзор и п р ед варит ельны е результ ат ы
^ з ь м и в руку карандаш
Решение П ри нап и сан и и с и м у л я то р а и спользовался один из основны х прин­
В от т е . кот оры е пришли ципов о б ъ е ктн о -о р и е н ти р о в а н н о го п р о гр ам м и р о в ан и я, инкапсуляция.
4 м 8 голову. У вас ест ь В от прим еры и нкапсул яц и и д л я каж д о го из созд анны х н ам и классов.
други е вариант ы ?
Hive Вее Flowe_r

1.Закры т словарь 1. Положение пчелы в п р о ­ 1. Э т от класс обеспечи­


Locations. ст ранст ве является свой­ вает мет од сбора н е­
ст вом только для чтения. ктара.
2. Эт о дает пчелам
м ет од добавления 2. Аналогично с ее возрас­ 2. Логическое значение
меда. том. Эт и свойства нельзя alive является закры ­
поменят ь из другого класса. тым.

if (totalNectarHarvested > NectarHarvestedPerNewFlower) {


foreach (Flower flower in P ow ers) пчелы
flower.NectarHarvested = 0; ^ R нлш еМ М м»е
опыляют цветы. В
AddFlower(random); счит ает ся, чт о собрав д о ст а ­
^
} точное количест во нект ара,
7 При дост ат очном количест ве
пчелы инициирую т появление
j н ект ара на поле появляем ся н о -
оыи цветок. нового цвет ка.
private void AddFlower(Random random)

Point location = new Point(random.Next(FieldMinX, FieldMaxX),


random.Next(FieldMinY, F i e l d M a x Y ) );
Flower newFlower = new Flower(location, random);
Fl o w e r s .Add (newFlower) ; п ^ ш б о т ч и к вы бирает положение
н а поле по с л у и л ^ н о м у принципу...

..и п ом ещ ает т уда


новый цветок.

_ Ч а с з» °

------- ^ а Д а Б а е М ы е ------
B o rtp o ^ jjl
вы получите список из четырех пунктов,
Почему бы не воспользоваться А почему циклы f o r считать новый цветок, который будет записан
циклом foreach для удаления увяд­ от конца списка к началу? под индексом #3, в следующий раз будет
ших цветов и отслуживших пчел? пропускаться, так как цикл перескочит на
^ ; Цикл не должен нарушать ну­ индекс #4.
Q ; Потому что удалить элемент коллек­ мерацию. Предположим, мы начали
Начав же просмотр с конца, вы никогда
ции изнутри просматривающего ее цикла просматривать с начала список из пяти
не пропустите цветок, помещенного на
foreach невозможно. Попытка сделать цветов, и оказалось, что один из них увял. освободившееся место.
это приведет к появлению исключения. Удалив цветок с индексом, например #3,

дальш е ► 547
соединим все вм ест е

ажнение В се четы ре кл асса написаны , теп ер ь их нуж но соед ин ить. С л е д у й те


д а н н о й н и ж е и н стр укц и и . П о м н и те , что вам п р и д ется в нести
и зм ен ен и я пр акти чески в каж ды й класс.

Обновите класс в е е .
Пчелы должны узнать о существовании улья и внешнего мира. Поместите в кон­
структор класса Вее в качестве параметров ссылки на улей и мир и сохраните их
для дальнейшего использования.

О Обновите класс H iv e .
Пчелы должны знать, что улей существует, улей же должен знать о существо­
вании внешнего мира. Поместите в конструктор класса H i v e ссылку на объект
W o r l d и сохраните ее. Обновите код создания новых пчел, передав в него
ссылки на улей и на мир.

О Обновите класс W o r ld .
Обновите класс w o r l d таким образом, чтобы при создании нового
улья ему передавалась ссылка на мир.

Л СТОП! На данный момент код уже должен


компилироваться. Если этого не происходит,
STOP поищите ошибки.
г

Внесите ограничение на создание пчел.


В классе H i v e есть константа M a x i m u m B e e s , указывающая,
сколько пчел в состоянии поддерживать улей (считается количе < -
ство насекомых внутри и снаружи). Теперь, когда объект H i v e
Подсказка: Обрат ит е
имеет доступ к объекту World, ограничение можно усилить.

М ир должен знать о создании новых пчел.


Класс W o r l d использует перечисление L i s t для слежения за
имеющимися пчелами. Убедитесь, что новые пчелы попадают ■«=— >
в список, состояние которого отслеживается объектом World.

548 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

ча<ап°
<аДаБаеМые
Б о 1 1 |=»ос;ь1

Зачем нужно исключение в методе G e tL o c a tio n () Q ; Вне зависимости от того, отмечаем мы местоположение
класса H ive? пчелы на экране или нет, оно является ее характеристикой. Объ­
екг Вее отслеживает, где именно находится каждая из пчел. При
^ ! Для обработки некорректных данных, передаваемых в ка­ каждом вызове метода Go () пчела перемещается на неболь­
честве параметра. Внутри улья есть несколько мест, но в метод шое расстояние в направлении пункта назначения.
GetLocations () МОЖНО передать произвольную строку. Что Нам нужно отслеживать положение пчел внутри улья и на поле,
произойдет, если переданное значение отсутствует в словаре? так как иначе невозможно узнать, достигли та или иная пчела
Какой результат должен вернуть метод? пункта назначения.

В случае некорректных параметров проще всего вызвать ис­ Но почему для хранения информации о местополо­
ключение ArgumentException. Вот как это делает метод жении используется именно структура P o in t? Разве она
GetLocation(): предназначена не для рисунков?

throw new ArgumentException(


^ S Да, именно эту структуру используют визуальные эле­
"Unknown location: " + location);
менты управления при описании своего свойства Location.
Она применяется и при создании анимации. Но это вовсе не
Этот оператор заставляет класс Hive показать исключение
означает, что с ее помощью невозможно следить за положением
ArgumentException С сообщением «Неизвестное место­
других объектов. Можно, конечно, создать отдельный класс
положение;» и указанием места, которое программа не может
BeeLocation с целочисленными ПОЛЯМИ X и Y. Но зачем
найти.
изобретать колесо, если можно воспользоваться уже имеющейся
структурой?
Таким образом пользователь немедленно получает информацию
о вводе некорректного параметра. Указав в сообщении этот па­
раметр, вы предоставляете сведения, позволяющие немедленно
устранить проблему.
Всегда проще переделать
Почему для описания местоположения была выбрана
структура P o i n t , если мы ничего не рисуем? существующий класс, фупк-
щюпальность которого,
ПО БОЛЬШЕЙ ЧАСТИ,
совпадает с нужной вам,
чем писать с нуля новый.

дальш е > 549


Здесь вы увидите, каким же способом наши четыре класса соединяются друг с другом.
ажнение
J '^ e m e n n e

Обновление класса в е е
П челы д олж ны у зн а ть о с у щ е с тв о в а н и и улья и в н е ш н е го м и р а . П о ­
м е с т и т е в к о н с тр у кто р к л а с с а В е е в к а ч е с т в е п а р а м е тр о в ссы лки на
ул ей и м ир и с о хр а н и те их д л я д а л ь н е й ш е го и спо ль зов ания.

class Вее {
// объявление констант
// объявление переменных
p r i v a t e W o r ld w o r l d ;
p r i v a t e H iv e h i v e ;

public Bee(int ID, Point InitialLocation, W o r ld w o r l d . H iv e h i v e ) {


// код
t h is .w o r ld e w o r ld ;
t h is .h iv e * h iv e ; Это вполне очевидно.,■
присвойт е эт и значения
закрыт ым п о л я м .

О Обновление класса Hive


П челы д олж ны зн ать , что с у щ е с тв у е т ул ей , ул ей ж е д о л ж е н зн ать
о с у щ е с тв о в а н и и в н е ш н е го м и р а. П о м е с ти те в ко н с тр у кто р кл а сс а
H i v e ссы л ку на о б ъ е кт w o r l d и с о х р а н и те е е . О б н о в и т е код с о зд а ­
н и я новы х п ч е л , п е р е д а в в н е го с сы л ку н а у л е й и н а м и р.

class Hive {
p r i v a t e W o r ld w o r l d ;
Ссылка на м ир да-
первой, т ак
public H i v e (W o r ld w o r ld ) как она использи-
t h i s .w o r ld e w o r ld ; в остальной
11 к о д части Конст рик­
тора.
}
public v o i d A d d B e e (R a n d o m random) {
// код создания новых пчел
Bee newBee = new Bee(beeCount, StartPoint, w o r ld , th is )
}

550 г л а в а 12
Если у вас не получается написать
)аботаюпщй код, скачайте готовую версию с сайта:
1Нр://\¥те.ЬеаёПг811аЬ8.сот/Ьоок8/ЬГс8Ьагр/
е О граничение на создание пчел
В кл а с с е Hive е с ть к о н с та н та MaximumBees, у ка зы в а ю щ а я , скол ь ­
ко п ч е л в с о с т о я н и и п о д д е р ж и в а т ь у л е й (у ч и т ы в а е т с я к о л и ч е с т в о
н а с е к о м ы х в н у тр и и с н а р у ж и ). Т е п е р ь , ко гд а о б ъ е к т H i v e и м е е т
д о с т у п к о б ъ е к т у Wor l d , о г р а н и ч е н и е м о ж н о у с и л и т ь . О бъект W orld позво
public v o i d G o (Random random) {
if ( w o r l d .B e e s .Cotuat < M a x i m i m B e e s пчел и
СрйОНЦИлЬ его г ААґЧґп,
&& H o n e y > M i n i m u m H o n e y F o r C r e a t i n g B e e s м а л ш о в о з м о ж т ш для
ScSc r a n d o m .N e x t {10) == 1) { ^ ^лнного улья.
AddBee(random); |

) ‘р о в н е н ш
)
М ир должен знать о создании новых пчел
Кл асс World использует п е р еч и с л е н и е List д л я сл е ж е н и я за и м е ­
ю щ и м и с я п ч е л а м и . У б е д и те с ь , что новы е пчелы п о п а д а ю т в сп исо к,
с о с т о я н и е к о т о р о г о о т с л е ж и в а е т с я о б ъ е к т о м W orl d .

private void AddBee(Random random) {


b e e C o u n t ++;
// Calculate the starting point
Point StartPoint =11 start the near the nursery
Bee newBee = ne w B e e ( b e e C o u n t , StartPoint, world, this);
w o r l d . B e e s . A dd ( n e w B e e ) ,• Ч т о одна WO причин
ооп(л из .....
необходит ст и ссылки ня
^ Добавляется новая пчела. Х е к ~ W o r U 6 классе H ive.

О Обновление класса w o r ld .
О б н о в и т е к л а с с w o r l d т а к и м о б р а з о м , чтоб ы при с о з ­
д а н и и н о в о го у л ь я е м у п е р е д а в а л а с ь с с ы л к а н а м и р .

public World 0 {
Bees = new List<Bee>(); Здесь передается ссылка
Flowers = new L i s t < F l o w e r > () на объект H iv e.
H iv e a n ew H i v e ( t h i s ) ;
Random random = n e w R a n d o m ();
for (int i = 0 ; i < 1 0 ; i++)
AddFlower(random);

дальш е > 551


пуст ь п чел ы ведут се б я прилично

Победение пчел
у нас отсутствует м е т о д G o () объе к т а Вее. М ы начали
писать код для н е к о т о р ы х состояний, н о м н о г о е оста­
лос ь н е з а к о н ч е н ы м (Idle, F l y i n g T o F l o w e r и ч а с т и ч н о
MakingHoney).

П р и ш л о в р емя заполнить пробелы: состояния без


ИЗ
Э ем стбия пчела

в состояние поиска Если остались цветы с несо­


public v o i d G o ( R a n d o m random) {
ц беткй с нектар о м . бранным нект аром и пчела
Age++; собирает нужное для жиз­
switch (Currentstate) { недеятельности количество
меда. В прот ивном случае
case Beestate.Idle:
п ч е м ост ает ся в состоянии
if (Age > CareerSpan) { дездейск\лвия.
Currentstate = Beestate.Retired
Н у ж е н другой
i f ( w o r ld .F lo w e r s .C o u n t > 0
} e ls e xudow
живой цoemc
цветок
&& h i v e . C o n su m eH o n ey (H o n e y C o n s u m e d )) { g:::: i c HCKmapoM-
F lo w e r f l o w e r = J
w o r ld .F lo w e r s [ r a n d o m .N e x t (w o r ld .F lo w e r s . C o u n t)] ;
Если все условия i f ( f l o w e r . N e c t a r >= M in im u m F lo w e r N e c ta r && f l o w e r . A l i v e ) {
соблюдены, пчела d e s t in a t io n F lo w e r = flo w e r ;
лет ит на новый і C u r r e n t s t a te = B e e s t a t e .F ly in g T o F lo w e r ;
цветок. }
Убедитесь, что за то
} время, пока пчела л е­
break; т ит к цвет ку, цветок
case B e e s t a t e .F l y i n g T o F l o w e r : не завянет.
if ( ! w o r ld .F lo w e r s .C o n t a in s (d e s tin a t io n F lo w e r ) ) <
C u r r e n t s t a t e = B e e s t a t e .R e tu r n in g T o H iv e ;
e l s e i f (I n s id e H iv e ) {
По этой причине if ( M o v e T o w a r d s L o c a t io n ( h iv e .G e t L o c a t io n ( " E x it ') ) ) {
мы передали “ I n s id e H iv e = f a l s e ;
ссылку на улей l o c a t i o n = h i v e . G e tL o c a tio n ( " E n tra n ce " ) ;
конст рукт ору
класса Вее. Если пчела добралась до выхода, она мож ет покинут ь
улей. Обновите ее положение. С поля же она будет
лет ет ь в ст орону входа.
( M o v e T o w a r d s L o c a tio n (d e s tin a tio n F lo w e r . L o c a t i o n ) )
C u r r e n t s t a te = B e e s t a t e .G a th e r in g N e c t a r ;
break; Если пчела
case B e e s t a t e .G a t h e r i n g N e c t a r : вылетела из
double nectar = d e s t i n a t i o n F l o w e r . H a r v e s t N e c t a r ();
улья, а цветок
^<^0, она лет ит
if ( n e c t a r > 0)
собирать с него
NectarCollected += nectar; нектар..
else
Currentstate = Beestate.ReturningToHive;
break;

552 г л а в а 12
обзор и п редварит ельны е результ ат ы

Эт о выход. Когда улей с о ­


храняем. положение E xitj эм о Эт о вход. В о з в р а щ а я с !^ ^
соот вет ст вует точке на в улей,, пчелы лет ят
ф орм е обьект а HiVe^ в к о т о ­ по направлению
рой располагает ся картинка к входу на ф орм е
с изображением выхода. обьект а field.

\
П оэт ом у в словаре location
i

^панятся два от дельных


Е Г Г л о ж е и » » E x it .

case B e e s t a t e .R e t u r n i n g T o H i v e :
if (!I n s i d e H i v e ) {
if ( M o v e T o w a r d s L o c a t io n ( h iv e .G e t L o c a t io n ( " E n t r a n c e " ) ) ) {
I n s id e H iv e = tr u e ; в случае проникновения
l o c a t i o n = h iv e .G e t L o c a tio n ( " E x it" ) в улей обновите п о л о ­
} жение и ст а т у с п е р е ­
менной insideHtSl'e.
}
else
if ( M o v e T o w a r d s L o c a t i o n ( h i v e .G e t L o c a t i o n ( " H o n e y F a c t o r y " ) ) )
C u r r e n tsta te = B e e S t a t e .M a k i n g H o n e y ;
break; £сли вы находитесь R
case BeeState.MakingHoney: ^ ‘^ '^ Р а в л я й т е с ь н Г ^
if {NectarCollected < 0 . 5 ) {
NectarCollected = 0;
Currentstate = Beestate.Idle;

}
else
if (h iv e .A d d H o n e y ( 0 . 5 ) ) ва т ь эт о т нектир'
N e c t a r C o lle c t e d -= 0 . 5 ; изводст ва меда...
e ls e
...заберите его у пчелы,
N e c ta r C o lle c te d = 0;
break;
case Beestate.Retired: вели У'^^‘^ ,^ Т ^ ;1 ;е Т Т н 1 е н и е fa
A d dH oneyO верн ^ н<
// D o nothing! We're retired!
break;
h

У
Отработавшая пчела
должна ждать, пока о б ь ­
ект Hive не удалит ее из ШТУРМ
перечисления. После этого Каким образом следует отредактировать симулятор,
она больна делать, что
хочет! чтобы пчела трати л а два кад ра на д остиж ение цветка,
а потом е щ е д ва кадра на в озвращ ение в улей? Какие
методы каких классов следует изменить, чтобы
получить та ко е поведение?

дальш е ► 553
продолж аем создават ь наш м ир

Оснобная форма
К а к вы з н а е т е п р и к а ж д о м в ы зо в е м е т о д а О о ( ) в се о б ъ е к т ы
н а ш е г о м и р а с л е г к а м е н я ю т с я . Н о к а к в ы зв а ть э т о т м е то д ?
Р а з у м е е т с я , п р и п о м о ш ;и ф о р м ы ! П р и ш л о в р е м я з а н я т ь с я е е
п о стро ени ем .
М е т к и 6 п р а во м ст олб це будит
Д об авьте ф орм у к п р о е кту и п р и д а й те ей п о ка за н н ы й н и ж е от ображ ат ь с т а т и с т и к у .
вид. О н а с о д е р ж и т н ов ы е эл ем е н ты у п р а в л е н и я , н а з н а ч е н и е П рисвойт е и м им ена B e e s ,'
Flowers, H oney InHiVe и т . п.
к о то р ы х будет р а с с м о тр е н о н а сл ед ую щ и х с тр а н и ц а х .

Ч/
Каждая из э т и х м е т о к
Э л е м е н т T o o lS trip п о ­ Forml.cs [Design] ПX за н и м а е т одну ячей -
м е щ а е т в ве р хн ю ю ку э л е м е н т а у п р а вл ен и я
ч а с т ь ф о р м ы п а н ель T ableLayoutP anel. Вы з а ­
инст рум ент ов. В ос­ ^ ieehwe Simulator f “s п о лн я е т е ее, как обычную
пользуйт есь раскры ­ т а б ли ц у в M icrosoft W ord.
Start Simulation Reset
ва ю щ и м ся с п и ско м Щ е л к н и т е на м ален ькой
. # Bees —-j gggj —
элем ент а в конст рук
:^A.itavW9l&.....Howws . черной с т р е л к е , чт обы
т о р е и добавьт е две Tsstal honey n the twe | Hon^hHve " добавит ь, у д а л и т ь или
кнопки. П р исвойт е па Tdirf'riedarjiJhgS^_| NeaBrtiftewera_^~ п о м е н я т ь р а зм е р ст олбца
р а м е т р у P isp la yS tyle Frames rm ....... *__ "I FranesRun
fiame rate ........ f Frametoe и ли ст роки.
кнопок значение T e x t

Simuletion paused
Э лем ент S ta tu sS trip
добавляет в н м ж - ^
нюю част ь ф орм ы Добавьте элемент
ст р о к у состояния. EooiStripl taistatusStripl © timerl у п р а в л ен и я T im er. Он
В оспользуйт есь р а с ­
не я вля ет с я ви зуа льн ы м
крывающ имся с п и ­ поэт ом у п о я вля ет ся
ском эт о го эл е м е н ­ снизу от формы.
т а , чтобы добавит ь
также StatusL abel. Э ле м е н т у п р а вл ен и я T o o lS trip добавляет п а н ель
и н с т р у м е н т о в в вер х н ю ю ч а ст ь ф о р м ы , а э л е ­
м е н т S ta tu s S tr ip — с т р о к у сост ояния в ниж нюю.
Но кр о м е э т о го , они п о я вля ю т ся в виде значков
под ф о р м о й , чт обы д а т ь ва м д о с т у п к р е д а к т и ­
р о ва ни ю своих свойст в.
[ f o r e a c h (F lo w er f lo w e r i n F lo w e rs)
flo w e r.G o (ra n d o m );

f o r e a c h (Bee b e e i n B ees) -----f


b e e .G o {random ) ;

1%
'®pewe«v>

h i v e . Go (random ) ;

System.Wv

554 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

Получение с та ти сти ки
О т р е д а к т и р у е м д о б а в л е н н ы е э л е м е н т ы у п р а в л е н и я . М ы н е будем
п о о ч е р е д и в ы д ел я ть к а ж д ы й и з н и х , а в о с п о л ь зу е м с я м е т о д о м ,
о б н о в л я ю щ и м с та т и с ти к у сим улятора:

большая часть этого


p r i v a t e v o id U p d a te S ta ts { T im e S p a n fra m e D u ra tio n ) { кода получает данные
B e e s .T e x t = w o r l d .B e e s .C o u n t .T o S t r i n g O ; о м обьекм а w orld
F lo w e r s .T e x t = w o r ld .F lo w e r s .C o u n t .T o S tr in g O ; к обновляет метки.

H o n e y ln H iv e . T e x t = S t r i n g . F o r m a t ( " { 0 : f 3 } " , w o rld .H iv e .H o n e y );


Первый парам ет р
'v d o u b le n e c t a r = 0 ;
f o r e a c h ( F lo w e r f l o w e r i n w o r l d . F l o w e r s ) выводится 8 виде числа
им ена м е­
т о к 9 олЖ" n e c t a r += f l o w e r . N e c t a r ; без десятичной точки,
, , , , зат ем после пробела
кы бы т ь S N e c t a r l n F l o w e r s . T e x t = S t r i n g . F o r m a t ( " { О: f 3 , n e c t a r ) ;
т ем и F r a m e s R u n .T e x t = f r a m e s R u n . T o S t r i n g ( ) ; парам ет р с одним
же^ ч т о
ц 6 ф орм е. d o u b le m il l i s e c o n d s = f r a m e D u r a tio n .T o ta lM illis e c o n d s ; Г к Т о ^ ^ м /л е Э у ^ т '
if ( m i l l i s e c o n d s != 0 . 0 ) буквы ms (в скобках).
F ra m e R a te .T e x t = s t r i n g . F o r m a t ( " { 0 : f O } T [ T 7 f l } m s ) " ,
1 0 0 0 / m illis e c o n d s , m illis e c o n d s );
e ls e _— Част от ой кадров называется
F r a m e R a t e . T e x t = " N /A " ; количество кадров, показыва­
емых за одну секунду. И н ф о р ­
} мация о продолжительности
одного кадра хранит ся в объ­
Добавьте
эт от мет од ект е Т/т е5рап. Чтобы узнать
в файл Formü-, част от у, мы делим 1-0 0 0 на
А откуд а взялся объ ект W o rld , е сл и мы количество миллисекунд, к о т о ­
е го е щ е не создали? И к ч ем у все эти рое показываемся один кадр Д
разговоры о врем ени и кадрах?

Мы поговорим об
эт ом подробнее
С о тв орени е М ира в разделе,
посвященном
Здесь вы видите т от Вы п р ав ы , нам нуж но со зд ать о б ъ е к т W o rld . Д обавьте создании}
ж е самый мет од в ко н стр у к то р ф о р м ы следующую строчку: объекта
S tring .Form a tQ J к о ­ Tim eSpan.
т орый использовали p u b l i c F o r m l () {
в ш ест надцат ирич- In itia liz e C o m p o n e n tO ;
н о м дампе. Но вмест о , „
вывода информации W o r ld ( ) ;
в xZ, вы указываете f3 >
и отображаете число „ , ,
с т ремя десятичными Д о бавьте к ф о р м е за к р ы т о е п о л е w o r ld .
знакам и . касается кода, с в я за н н о го со врем ен ем ... нам
ж е т р е б о в а л с я сп о со б м н о го к р ат н о го в ы зо в а м етод а
Go О в классе W o rld ... зн ач и т, нам т р е б о в а л с я тай м ер .

дальш е * 555
Создайте новый проект , чтобы
повт орное воспроизведение
посм от рет ь, как именно р а ­
бот аю т таймеры. З я т е м мы
вернемся к сим улят ору и п р и ­
Таймеры меним полученные знания на
практике.
Т а к о й п о л езн ы й о б ъ е кт к а к т а й м е р п о зв о л я е т запускать од но
и т о ж е с о б ы т и е с н о в а и с н о в а , т ы с я ч у р а з в секунд у. ^^Упр^нение!
Создайте новый проект
О т к р о й т е V is u a l S tu d io и с о з д а й т е п р о е к т с ф о р м о й . П е р е т а щ и т е н а
н е е та й м е р и т р и к н о п к и . Щ е л к н и т е н а т а й м е р е и п р и с в о й те свойству
In t e r v a l з н а ч е н и е 1 0 0 0 . И з м е р е н и я в е д у тс я в м и л л и с е к у н д а х , с о о тв ет^
с т в е н н о , с о б ы т и е б у д е т з а п у с к а т ь с я о д и н р а з в секунд у.

О Щ ел кните на кнопке E v e n t s в окне P rop erties


(Н а п о м и н а е м , ч т о э т о к н о п к а с о з н а ч к о м м о л н и и .) Т а й м е р у с о о т в е т - „г ^
с т в у е т в с е го о д н о с о б ы т и е T ic k . Щ е л ч к о м в ы д е л и т е з н а ч о к T i m e r в со^ытий'^можно
к о н с т р у к т о р е , з а т е м д в а ж д ы щ е л к н и т е н а с т р о к е в о к н е E v e n ts . Б у д е т добавить двой-
а в т о м а т и ч е с к и созд ан о б р а б о т ч и к с о б ы ти й , св я зан н ы й со сво й ств о м , ным щелчком
на значке Tim er.

К-но»^кй Events в окне Properties - п X


p ro p erties дает дост уп -iis^dl^^stenn.Wtndows.Forms.Timer
к событиям, связанным С элемент ом управления
с выделенным элемент ом Ъ т е г связано c o L m u e
управления. Т'ск.^Двойной щелчок н а
этой ст рочке приводит
с о ^ Ь ! ^ ^ ^ <з^/!’<Я(3'отч«ка
Гкк
Описание события Occurs whenever the specified inter^'al time
находится в нижней
части окна. Л elapses.

в“ « » ! " ::
О Код для события T i c k и кнопок
В о т код, ко то р ы й п о зв о л и т п о н я ть п р и н ц и п раб оты тай м ер а:
на консоль

private void timerl_Tick(object sender, EventArgs e) {


Эти кнопки no Console.WriteLine(DateTime.Now.ToString())
ды е 1-000 м и л л и с е к у н д ) .
звоАяют вам n } -
играт ь CO с в о й ^ ' ^ ^ ^ ^ ' ^ ^ toggleEnabled_Click(object sender, EventArgs e) {
ст вом Enabled и if (timerl.Enabled)
методами S ta r tQ timerl .Enabled = false;-, й с и j
и StopQ. Первая Cooucmoo Enabled запускает
меняет значение останавливает таймер,
свойства Enabled timerl.Enabled = true;
с tru e на false и T
наоборот, две private void startTimer_Click(object sender, EventArgs e){
остальные вы - timerl.Start () ; ^ ------------ ----— ---- _ Мет од S tartQ запускает
S tartQ и StopQ Console.WriteLine("Enabled = " + timerl.Enabled) - maiAMep и присваивает
} свойству Enabled значение
private void stopTimer_Click(object sender, EventArgs e) true. М ет од StopQ о с т а -
}навливает т аймер и п р и ­
timerl .Stop 0 ; -------------- ----------------
сваивает свойству Enabled
Console.WriteLine("Enabled = " + timerl.Enabled); значение false.

556 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

Тайме|>ы используют обработчики событий

К а к и м о б р а з о м т а й м е р у з н а е т , ч т о д е л а т ь в к а ж д о е м гн о в е н и е ?
П о ч е м у м ето д t i m e r l _ T i c k () запускается п р и к а ж д о м о тс ч е те
тайм ера? Т у т мы возвращ аем ся к с о б ы т и я м и д е л е га та м , с к о ­ а сД еной
т о р ы м и вы п о з н а к о м и л и с ь в п р е д ы д у щ е й гл ав е. В о с п о л ь з у й т е с ь
ф у н к ц и е й G o Т о D e f in it io n , ч т о б ы в с п о м н и т ь п р и н ц и п р а б о т ы В предыдущем прим ере
использотлся ст а н ­
д е л е га т а E v e n t H a n d l e r :
дартный обработчик
событий.

Щ елкните правой кнопкой ааыши на переменной t im e r l . . .


... и в ы б е р и т е в м е н ю к о м а н д у G o Т о D e f in it io n д ля п е р е х о д а к у ч а с т к у к о д а , в к о т о р о м за д а е тся
п е р ем ен н а я t i m e r l . Н а й д и т е строчку:

this .timerl .Tick -н= n e w S y s t e m . E v e n t H a n d l e r ( t h i s .t i m e r l _ T i c k ) ;

T r
Один из делегатов класса
К
напысаннш
Э „о T ic k э л е м е н т « System : базовый обработчик Это t i^ w e r 3 - Т .с к О -
событии. Эт о делегат ° именно
а указат ель на один или Д е л е гя » ^
-------------------
несколько -
методов. на него.
1 Л -9 П

Т еперь щ елкните правой кнопко й мы ш и на д е л егате E v e n tH a n d le r...


... и с н о в а в ы б е р и т е в м е н ю к о м а н д у G o Т о D e f in it io n О б р а т и т е в н и м а н и е н а н а з в а н и е н о в о й
в к л а д к и ; E v e n t H a n d le r [f r o m m e t a d a t a ] . О н о о з н а ч а е т , ч т о к о д , о п р е д е л я ю щ и й E v e n t H a n d l e r ,
н а п и с а н н е вам и. Э т о в с тр о е н н ы й ко д .N E T F ra m e w o rk , а И С Р с ге н е р и р о в а л а с тр о чку, е го
пред ставл яю щ ую :

public d e le g a te void EventHandler(object sender, EventArgs e);


~V"
Bom почему все события в С * им ею т
парам ет ры O bject и EventArgs, именно
т акую ф орм у имеет делегат, опреде­
ляющий обработку событий.

Какой код нужно написать, чтобы запустить метод W orld’s G o ()


10 раз в секунду?

дальш е > 557


распр еделение по врем ени

Таймер ддя симулятора 8 .N E T класс D ateTim e х р а ­


нит информацию о времени,
Д о б ав и м т а й м е р в наш сим улятор. У вас уже есть соответствую щ и й а свойство Now возвраищ-
эл е м е н т у п р ав л ен и я, в е р о я т н о , с и м ен ем t i m e r l . Вручную свяж ем ет т&куищю дату и время.
его с м ето д о м о б р а б о т ч и к а с о б ы т и й R u n F ram e () : Вычислить р а з н и ц у двух дат
О бьект Tim eSpan обладает т акими свой­ позволяет обьект Tim eSpan:
ствами как Daus (Д ни), Hours (Часы ), Seconds он вычит ает один обьект
(Секунды) и Milliseconds (Миллисекунды), что D ateTim e из другого и воз­
позволяет измерят ь временные п р о м е ж у т к и ^ вращает одьект Tim eS pan,
в различных единицах. содержащий разницу.

p u b lic p a r t i a l c la s s F o rm l : F orm { „ \/Jnr!d.


world world; ________ у 5»«»
p r i v a t e R andom ra n d o m = new R a n d o m { ) ;
p r i v a t e D a te T im e s t a r t = D a te T im e ,N o w ; Здесь будет
p r i v a t e D a te T im e e n d ; ___________________
_______—— _ вычисляться
p r i v a t e i n t fr a m e s R u n время работы
0 ;
■Мы хот им
сколько кадров
p u b lic F o rm l0 { уже показано.
I n i t i a l i z e C o m p o n e n t ()
w o r l d = new W o r l d { ) ;
Запуск
tim e r l. In te r v a l = 50;
M w связали обработчик
t im e r l.T ic k += n ew E v e n t H a n d l e r ( R u n F r a m e ) ; с собственным
t im e r l.E n a b le d = f a l s e ; .ллпймера. методом RunFram e(). „
Запуск mauMt-fy и в одной секунде
U p d a t e S t a t s ( n e w T im e S p a n О ) ;
Ю о о миллисекунд,
} обновляем с т а т и с т и к и так что таймер
^ с новы м о б ь е к т о м T im e S p a n ( ^ й
о т с ч е т врем ени). ^ {мооыи будет срабатыбать
p r i v a t e v o id U p d a te S ta ts (T im e S p a n fra m e D u ra tio n ) {
2.0 раз в секунду.
// Предыдущий код для обновления статистики
}

p u b lic v o id R u n F ra m e(o b jec t s e n d e r , E v e n tA rg s e ) {


fra m esR u n + + ; у в е л и ч ь т е количест во кадров
w o r l d . Go (ra n d o m ) ; ^ ----------- на I з а п у с т и т е м е т о д ио{)
e n d = D a te T im e .N o w ; о б ьек т а w orld.
T im e S p a n f r a m e D u r a t i o n = en d - s t a r t } З а т е м м ы у зн а е м ,
sta rt = en d ; -------- — ~ сколько вр ем ени проилло
U p d a te S ta ts (fr a m e D u r a tio n ); с м о м е н т а проигры вания
последнего кадра.
}
О бно ви т е с т а т и с т и к у с новой
п р о д о лж и т ельн о ст ью врем ени.

558 г л а в а 12
обзор и п редварит ельны е результ ат ы

нение Н а п и ш и те о б р аб о тч и ки собы тий д ля кнопок S ta r t S im u la tio n


и R e s e t э л е м е н та ToolStrip, В от что д ел а ю т кнопки:

1. И зн ач а л ь н о на первой кнопке д о л ж н а быть надпись


« S ta rt S im u latio n ». Щ елчок на ней за п у с к а е т сим улятор,
Если вы еще а надпись м е н я е тс я на _____ _____ ____________
не п ер ет а­ « P a u s e S im u latio n ». П ри ; Forml.cs [Design] '■ а х
щили элемен- п о с та н о в ке си м у л я то р а на
т ы ToolStrip п аузу надпись м ен яется
и S tatusS trip из Beehive Simulator
на « R e s u m e sim ulation».
окна toolbox на start Simulation Reset
ф ор м у, сделай­ #Веез ’[Bees
2. Н а второй кнопке д о л ж н а
те эт о сейчас. аАШ жме....... ........... [ Ftowm
бы ть над пи сь « R e s e t»
Тсйй Нштеуin the rtve і1HoneyhHve
Щ елчок на ней приводит ТЫй пЩіг м f e іШ _! Nedwhfloweis
к п е р е за гр у зке м и ра. При Frafti^ fitfi FramesRun
п о став л ен но м на паузу Frenerate РпдаеЯйе
т а й м е р е те к с т на первой
кнопке д о л ж е н м енять ся ! Simutetiort paused
с « R e s u m e sim u latio n » на
« S ta rt S im u latio n ».

■шаtcfOlStripl lisstatusStripi

Н а эт от вопрос нет однозначного


ответа. Мы прост о к о т и М .ч т о б ь
вы подумали над дальнейшим
пост роением симулят ор . Аважды щелкните н а кнопке
Г ооІЗ ігір в конст рукт оре, чтобы
добавить к ней обработчик
событий, как к обычной кнопке.
^ з ь м и в руку карандаш часш °
_ Задаваем ы е —
Ка к вы д у м а е т е , что о стал о сь н есд ел ан ны м на
Б о Ц |Э о С Ь 1
этом э та п е работы н ад си м ул я то р о м ? З а п у с ти те
програм м у. З а п и ш и те , каки е и зм е н ен и я нуж но
В чем отличие цикла от
д обав и ть , п е р ед те м к а к пер ехо д и ть к р аб о те
кадра?
с граф икой.

1); Отличие чисто семантическое.


№ ша система работает цикличе­
ски. Но как только мы вставим в
проект графические фрагменты,
разговор пойдет исключительно
о кадрах.

дальш е > 559


пуст ь все зараб от ает

В от как вы глядит код обр аб о тч и ко в ; Ротй.С5{Ое$*дпЗ


ажнение собы тий д л я кнопок s ta rt S im u latio n
и R es et.
ешение
ТоМпе^агЩбШ 1 Nectsr^l^wers'
Ргаипишп “ ^ Iftam esRjn.....
frame гш» ' ........ '\ ВвгаеКгйе

S tm y i^o n paused

Код ф орм ы остается без изм енений.


^ t o o f S t r ip l fcssstelusStripl

p r iv a t e v o id s t a r t S im u la t io n _ C lic k (o b je c t sen d er, E v e n tA rg s e) {


i f (tim e r l.E n a b le d ) {
t o o l S t r i p l . I t e m s [ 0 ] . T e x t = " R esu m e s i m u l a t i o n " ;
С
t im e r l . S to p 0 ;
Убедитесь,
что имена t o o l S t r i p l . I t e m s [0 ] . T e x t = " P a u s e s i m u l a t i o n " ; ^
элементов
^ t im e r l.S t a r t 0 ; I ^JosSeH ue-
управления
ф орм ы с о ­ }
впадают с }
т еми, к о ­
т орые вы
используете p r i v a t e v o i d r e s e t _ C l i c k ( o b j e c t s e n d e r , E v e n t A r g s e ) { Перезагруз­
в своем коде. fra m esR u n = 0 ; ка симулят ора
осуществляется
w o r l d = n e w W o r ld 0 ; ~— повт орным созда­
if ( ! t im e r l.E n a b le d ) нием экземпляра
t o o l S t r i p l . I t e m s [ 0 ] .T e x t = " S t a r t s im u la t io n " ; W orld и переза­
грузкой метода
(ram esRun.

М ет ку на первой кнопке следиет


менят ь только в случае, ког^а Та
ней написано «Resum e sim ulation »
В случае надписи «P ause slmi,ln+-

560 г л а в а 12
обзор и п редварит ельны е результ ат ы

Тестирование

П р о д е л а н а больш ая р абота. С ко м п и л и р у й те код,


исправьте о п е ч а т ки и зап усти те сим улятор. Ч т о у
вас п о л у ч и л о с ь ?

Beehive Simulât... неплохо!

^ I Start Simulation Reset ‘процессе работы-

# Bees 6
# Rowers 10
Total h o n ^ n the hive 3.200 J
ра5о-
Должны
кнопки Total nectar in the fleld 15.СМЮ
запуска си м ул я ­ Frames run 0
т о р а , пост ановки N/A
на п а узу и п е р е ­
Frarrre rate
загрузки-

Simulation paused я »

i \

Каж ет ся, не работ ает


только ст рока состояния-

Ч^ажнениеВ от ш а н с проявить свои зн ан и я . Н уж н о с д е л а ть та к, чтобы пчелы о тч и ­


ты вались сим улятору, и эта и н ф о р м а ц и я появл ял ась в с тр о ке состояни я, одна
Н а этот р а з вам пр ед сто и т не только н а п и са ть ббл ь ш ую часть кода, но и
с ам о с то я те л ь н о реш и ть , что и м ен но писать. В ам н уж ен м етод , которы й ^цЗеился 8се
б уд ет вы зы ваться при каж д о м и зм е н ен и и со сто ян и я пчелы . '^ а с с ы , кроМе
Чтобы нем но го вам пом очь, мы н а п и са л и м етод , которы й с л е д у ет д о б а - одного-
вить к ф о р м е . К л ас с вее д о л ж е н вызы вать его при и зм е н ен и и состояни я
пчел;

private void SendMessage(int ID, string Message) {


statusStripl.Items[0].Text = "Bee #" + ID + ": " + Message;

дальш е ► 561
реш ение упраж нения

Т р ебо вал о сь найти способ и нф ор м ир о вать си м ул ятор о д е йстви ях


пражненке пчел.

решение
Эт о было добавлено в класс Вее.
Мы воспользовались
f вызовом для
class Вее {
// весь уже существующий код
p u b lic B eeM essage M e ssa g e S e n d er ;

public void Go(Random random) { А елегат ВееМе55аде берет


Age++; б к а ч е с т в е парам ет ров н о ­
м ер пчелы и сообщение С его
B e e s ta te o ld S ta te = C u r r e n tsta te ;
пом ощ ью пчела отправляет
switch (currentstate) { сообщение в форму.
// остальная часть оператора s w it c h без изменений

if ( o l d S t a t e != C u r r e n t s t a t e
&& M e s s a g e S e n d e r != n u l l )
M e ssa g e S e n d e r (ID , C u r r e n t S t a t e .T o S t r in g ( ) ) ;
При изменении ст ат уса пчелы
} вызывается м ет од BeeMessaqe
на который указывает делегат.
Изменения,,
внесенные в об ъ ек т у Hive также
Класс Hive. т ребует ся делегат, ^
чтобы передать каждой
class Hive { пчеле метод, который
С // весь уже существующей код будет вызываться после
- создания этой пчелы
p u b lic B eeM essage M e ssa g e S e n d er ; мет одом Ас1<1Вее().

H i v e (World world, B e e M e ssa g e M e ssa g e S e n d e r )


t h i s .M e s s a g e S e n d e r = M e s s a g e S e n d e r ; -------------- -
// существующий код конструктора
}

public void AddBee(Random random) {


// код метода A d d B e e О
Bee newBee = new Bee(beeCount, StartPoint, world, this);
n e w B e e .M e s s a g e S e n d e r += t h i s .M e s s a g e S e n d e r ; A d d B e e f\
world.Bees.Add(newBee) ■ каждая н о в а Т п ^ ‘1 ^^^‘^^
метод, на
будет указывать.

562 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

p u b lic d e l e g a t e v o id B e e M e s s a g e ( in t ID , s t r i n g M e ssa g e );

В класс W orld также


требовалось внести
изменения.
=Z Z ^ ZO M iS F 4 .
SendMessaee6-

class World {
// весь существующий код

public W o r l d (B eeM essa g e m e ssa g e S e n d e r ) {


Bees = new List<Bee>();
Flowers = new List<Flower>0 ; ^ К л ассу W orld делегаты не
H iv e = n ew H i v e ( t h i s , m e ssa g e S e n d e r ); 1^редуются. Он прост о
передает ллетод для
Random random = n e w R a n d o m (); оызова экземпляра Hive
for (int i = 0 ; i < 1 0 ; i++)
AddFlower(random);

и , наконец, нужно
обновить ф орм у.
sT Новый делегат создается из
public partial class Forml Form { класса Вее (убедитесь, что
// объявление переменных мет од BeeMessage является
от кры т ы м ) и указывает на
public FormlО { мет од SendMessageQ.
InitializeComponentO ;
w o r l d = n ew W o r ld ( n e w B e e M e s s a g e ( S e n d M e s s a g e ) ) ; 3
// остальная часть конструктора формы

p r iv a t e v o id r e s e t_ C lic k (o b je c t sen d er, E v e n tA rg s е ) {


fra m esR u n = 0;
w o r l d = n ew W o r ld ( n e w B e e M e s s a g e ( S e n d M e s s a g e ) ) ;
Щ ^ " ет ся м 'и )Т м " е'
if (! t im e r l .E n a b l e d )
t o o l S t r i p l . I t e m s [ 0 ] .T e x t = " S t a r t s im u la tio n " ;

p r iv a t e v o id S e n d M e s s a g e (in t ID , s t r i n g M essa g e) {
s t a t u s S t r i p l . I t e m s [ 0 ] . T e x t = " B e e #" + ID + " + M essa g e;
} f Э т о м ет о д . K o m o p f M b i e ^ M
} -------дали... не забудьте вписать его.

дальш е ► 563
сгруппируем пчел

Работа с группами пчел


П ч е л ы ж у ж ж а т в о кр у г улья и н а п о л е, си м у л я то р
р а б о та ет! Здорово? Н о п о к а визуальная часть сим у­
л я то р а не работает, эти м мы займ ем ся в следую щ ей
гл ав е. И н ф о р м а ц и ю м ы м о ж е м п о л у ч а т ь т о л ь к о п о ­
средством со о б щ ен и й , ко то р ы е пчелы о тп р ав л я ю т
в ф о р м у п р и п о м о щ и о б р а т н о г о в ы зо в а . Д о б а в и м д о ­
п о л н и тел ь н ую и н ф о р м а ц и ю об и х д еятел ьн ости.

Ф орм а обновляет с т а ­
т и с т и ч е с к и е данны е и »У Beehive Simulator
о т о б р а ж а е т со о б щ ен и я,
кот оры е пчелы п о сы ла ­ Pause simulation Reset
ю т как о т ч ет о п р о д е­ #Bees 6 Добавим элемент ListBox для
ланной работ е. # Rowers 11 отображения дополнительной
Total hofwy in the hive 1.Ш информации о пчелах.
С Told nectar in the Add 34.330
frames Rjn
Frame rate 16(K.5m s)
kte: 1 bee
FlyingToRower: 2tjees
GatheringNectar: 1 bee
ReturningToHive: 2 bees

Bee #1; Idle


\

U одна ни ч е го не д е л а е т .

ШТУРМ
Вы зн аете достаточно д ля вставки в ф орм у эл ем ен та ListBox. П одум айте, как
им енно он ф ункционирует. Э то слож нее, чем каж ется на первый взгляд. Что
нужно сделать, чтобы определить количество пчел в каж дом из состояний Вее
s ta te ?

564 г л а в а 12
обзор и п р ед варит ельны е результ ат ы

Коллекция коллекционирует... ДАННЫЕ


П ч ел ы с о х р а н е н ы в к о л л е к ц и и L i s t < B e e > . К о л л ек ц и и п р е д н азн а ч е н ы
д ля х р а н е н и я д ан н ы х ... п р а к ти ч е с к и , как б азы д ан н ы х. Каждую пчелу
м ож н о п р е д с та в и ть в виде с т р о к и , в к о т о р о й указан о с о с то я н и е , и д ен ­
т и ф и к а т о р и т. п. В о т как вы гл яд ят п ч ел ы в ви д е кол л екц и и :

®Pe4eHV»

П о л я о б ъ е к та В ее с о д ер ж ат м н о ж еств о д ан н ы х. К о л л екц и ю о б ъ е к то в
можно п р е д с та в и ть в виде с т р о к б азы д ан н ы х. К аж д ы й о б ъ е к т со д ер ­
ж и т д ан н ы е в п о л ях , т а к ж е как каж дая с т р о к а б азы со д е р ж и т д ан н ы е
в столбцах.

Ш ID = 987 1 currentstate = MakingHoney ^


П Ю = 1 2 І (c urrentstate = FlyingToFlower ^
ID = 1 9 8 2 currentstate = GatheringNectar t

База данных
^ а б л ц ц а с названием
есть Большинство коллекций,
Вее со ст о лб ц а м и ID
« c u rre n tsta te. особенно содержащих
объекты, можно предста­
вить как своего рода базу
данных.
дальш е * 565
вст роенны й язы к запросов

Какая разница, в како м


виде м ы м о ж е м представить
к о л л е к ц и ю , есл и м ы не м о ж е м
использовать ее как базу данны х?

П редставьте запросы к колл екциям , базам


д ан н ы х и д а ж е к X M L -д окум ентам с одинаковы м
синтаксисом !
В C # и м еется полезная ф ун кц и я L IN Q (L a n g u a g e
I N t e g r a t e d Q u e r y — в с т р о е н н ы й я з ы к з а п р о с о в ). Э т а
ф у н кц и я д ает вам в о зм о ж н о с ть р а б о та ть с д ан н ы м и и з
м ассивов, с п и ско в , с тека, о ч е р ед ей и д р уги х ко л л е кц и й
п р и пом ощ и едины х операций.

L IN Q п о зв о л я е т испо ль зов ать п р и р а б о те с ко л л е кц и я м и


т о т ж е с и н та кс и с , ч т о и п р и р а б о те с базам и д анны х.

З а п р о с ы L IN Q оам накобо
З о т а ю т с коллекциям и
и с базами данных-

var beeGroups =
from bee in world.Bees
group bee by bee.Currentstate
into beeGroup 1 currentstate = MakingHoney L
ID = 12 1 currentstate = FIvinaToFlower B F
orderby beeGroup.Key 1 1 ID = 1982 j currentstate = GatheringNectar [,
select beeGroup; База данных
Е сл и бы данные о пчелах находились
в базе (или в файле X M L)? UN Q
работал бы с ними т ем же способом.

<Ьее id=”987" currentState=”MakingHoney” />


<bee id=”і2’ currentState=”FlyingToFlower />
<bee id= 1982" currentState="GatheringNectar” /:
\
566 г л а в а 12
обзор и п редварит ельны е результ ат ы

LINQ упрощает работу с коллекциями и базами данных


L I N Q б у д е т п о с в я щ е н а ц е л а я гл а в а , а п о к а вы м о ж е т е в о с п о л ь ­
j^oTOoBo
з о в а ть с я е г о в о з м о ж н о с т я м и и д о б а в и т ь в с и м у л я т о р д о п о л ­
н и тел ь н ы е ф у н кц и и . В ведите э то т код. Н и ч е г о с тр а ш н о го , К IjIlo Ir lJ > e й Л e н U Io
е с л и вы н е п о н и м а е т е е го . О т о м , к а к о н р а б о т а е т , м ы п о г о в о ­
р и м в гл ав е 15.

private void SendMessage{int ID, string Message) {


s t a t u s S t r i p l .I t e m s [ 0 ] . T e x t = "Bee #" + ID + ": " + Message;
var beeGroups = Это запрос U N Q , гр ц п п и р ц -
from bee in w o rld.Bees ю щ ш всех пчел коллекции по
свойст ву C u rre n tsta te .
group bee by bee.Currentstate into beeGroup
orderby beeGroup.Key К - л ю ч о м явля е т с я свойст во пчелы ■
C u r r e n ts ta te , т ак чт о состояния б удут о т о -
select beeGroup;
браж атьсй в ф о р м е именно в т аком порядке.
l i s t B o x l .I t e m s .C l e a r {);
убеЗитесь.f o r e a c h (v a r . . a r g u E i J j L b e g g ^ ^ ) {
что эт о string S; ^^^^'^oups появилась из за п р о ­
совпадает if (g r o u p .C o u n t () == 1) са UNQ. Мы мож ем п осчи т ат ь членов эт эт,ой
с им енем груп п ы и по очереди п р о см о т р ет ь их.
элем ен т а s =
Э т о т код обеспечивает правильное упот ребление
listbox вашей
английского слова « п ч ел а » во множ ест венном числе.
ф орм ы ^ л nSa6uM в т екст овое
l i s t B o x l . I t e m s . A d d (group. Key . T o S t r i n g О + ^ групп ы
+ group.Count 0 + " bee" + s) ; (е е клнзч) u сметчик.
if (group.Key == B e e S t a t e .I d l e
& & g r o u p .C o u n t () == w o r l d .B e e s .C o u n t () Так как количест во
незанятых
&& framesRun >0) { ~ известно...
l i s t B o x l . Items.Add("Simulation ended: all bees are idle");
t o o l S t r i p l .I t e m s [0] . T e x t = "Simulation ended";
s t a t u s S t r i p l .I t e m s [ 0 ] . T e x t = "Simulation ende d " , -мож но проверить^
не являю т ся ли все
t i m e r l .E n a b l e d = false;
пчелы незанятыми.
и з полож ительного
р е зу л ь т а т а
проверки следует
о т сут ст ви е в ул ье
м еда, а значит ,
заверш ение раб от ы
Симулятора.
L IN Q будет п о д р о б н о р ассм о тр ен в сл ед ую щ и х
гл а в а х .

П о это м у п о ка н е старайтесь по н ять и запом н ить си н ­


такси с.

дальш е ► 567
сохраняем мир

Тестирование (Вторая попытка)


С к о м п и л и р у й т е к о д и з а п у с т и т е п р о е к т . В с л уч ае
о ш и б о к п р о верь те с и н такси с , о со б ен н о новы й
ко д с L IN Q . П о с м о тр и м , к а к р а б о та е т н а ш сим у­
л ятор!

Hj? Beehive Simylator


Связанный Вы добавите эти элементы
с ф ормой Pause Simulation Reset Я|
и обработчики событий для них
илаймер
упрйбляе!^ +? Bees 6 ^
1Л.рОЦйССОМ # Rowers 11 Ст ат ист ика является
юаЬотм\ CW -
Total honey in the hive
м уляторй . 1.ЖЮ ' ^ результ ат ом запросов^
Total nectar in the field М.Ш / ^ которые ф орм а посылает
одьект у World.
Frames run Ш
Frame rate ie (e ,5 m s )i
Объект ы , имеющие методы,
IcBe; 1 bee которые связаны с делегатами
Эти данные R'/ngTonower; 2 bees или обработчиками
получают ся событий другого объекта,
Gathering Nedar: 1 bee предст авляют собой ссылку,
пут ем за -
просов LINQ ReturningToHive; 2 bees по которой будет следовать
к коллекци­ сериализатор.
ям, Которые \Г
происходят в Объект ы , связанные с обработ ­
каждом Кадре. чиком событий, вызываемых эле­
Bee ^ 5 Idle мент ам и управления, нужно п о ­
мечат ь как [N onSerialized]. Иначе
вы попыт ает есь сериализовать
элемент управления и, как след­
Пчелы выполняют обратный вызов ф орм ы , ствие, получит е исключение
что позволяет оперативно менят ь ин ф ор­ SerializationE xception.
мацию при изменении ик ст ат уса. V

Иногда требуется сериализовать не весь объект, а его часть. Напшмер вы cmt^oum. I


систему, в которой регистрируется пользователь, и нужно сохранить в файл обь~
Поле password можно пометить атрибутом
[NonSmaUzed]. В этом случае метод SeriahzeO пропустит это поле.
Атрибут iNonSerialized] особенно полезен, когда объект ссылается на несешализи^
попытка сериализаими формы станет причиной исключения
Ser alizat,onExcepbon. Именно это исключение п о и т ся , если вы ^ p u a Z u e Z oS^^^
ссылающийся на форму. Но пометив поле с этой ссылкой атрибутом [NonSeriatizedl '
вы предотвратите переход метода SerializeQ по ссылке. LNonseriaUzedJ.

568 глава 12
реш ение упраж нения

Вот код, заставляющий работать кнопки Save и Open.


Н е забудьте про дополнительные
операт оры using.
ненке using System.lO;

|^ешение using S y s t e m . R u n t i m e .S e r i a l i z a t i o n .F o r m a t t e r s .B i n a r y ;

Классы WoHd, HivCj Flow er и Bee [S e r ia liz a b le ] [S e r ia liz a b le ]


нужно сделать сериализуемыми. class World { class Flower {
В процессе сериализации объекта [S e r ia liz a b le ] [ S e r ia liz a b le ]
W orld .N ET обнаружит ссылки на
class Hive { class Bee f
объекты Hive. Flow er и Вее и с е ­
риализует ик.
, Убедитесь, что поле
[N o n S e r ia liz e d ]
MessageSender в классах
Hive и Bee помечено
public BeeMessage MessageSender;
ат рибут ом [NonSerialized].

К од для кнопки Save.


nT
private void s a v e T o o lS tr ip B u tt o n _ C X ic k (object sender, EventArgs e) {
bool enabled = timerl.Enabled;
if (enabled) Для ф айлов, в к о -
timerl.Stop () ; т оры е записы ва­
ет ся инф ормация
из си м ул ят ора, мы
SaveFileDialog saveDialog = new SaveFileDialog () ; исп ользуем р а с ш и -
saveDialog.Filter = "Simulator File (*.bees)|* .bees"; pCHue .bees.
saveDialog.CheckPathExists = true;
saveDialog.Title = "Choose a file to save the current simulation";
if (saveDialog.ShowDialog{) == DialogResult.OK) {
try {
BinaryFormatter bf = new BinaryFormatter();
Именно
здесь мир using (Stream output = File.OpenWrite(saveDialog.FileName)) {
сохраня- bf .Serialize (output, world)
ет ся в Помните, что вместе с объектолл
bf.Serialize(output, framesRun);
файл.
}
}
catch (Exception ex) {
MessageBox.Show("Unable to save the simulator file\r\n" + ex.Message,
"Bee Simulator Error" MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
if (enabled)
timerl.Start

570 г л а в а 12
о б з о р и п р е д в а р и т е л ь н ы е результаты

Последние штрихи: Open и Save


В се у ж е п р а к т и ч е с к и го то в о для п е р е х о д а к следую щ ем у э та п у — до­
бавл ен ию в н аш сим улятор гр а ф и ки . О стало сь пред усм о тр еть в о зм о ж ­
н о с т ь з а г р у ж а т ь д а н н ы е , с о х р а н я т ь и х и в ы в о д и ть с т а т и с т и к у .
О кнопке Print
разговор пойдет
Значки O pen, Save и P rin t в следующей главе.
Э л е м е н т T o o lS tr ip у м е е т а в т о м а т и ч е с к и в с т а в л я ть с т а н д а р т н ы е з н а ч ­
к и : n ew , o p e n , save, p r in t , c u t, copy, p a ste и h e lp . Щ е л к н и т е н а э л е м е н т е
T o o lS trip п р а в о й к н о п к о й м ы ш и и в ы б е р и т е к о м а н д у Insert Standard
' Item s. В в е р х н е й ч а с т и ф о р м ы п о я в и т с я п а н е л ь с к н о п к а м и . Щ е л к н и т е
н а п е р в о й и з н и х — э т о к н о п к а n e w — и у д а л и т е е е. Т р и с л е д у ю щ и е к н о п ­
к и (o p e n , save и p r i n t ) н а м н у ж н ы . Р а з д е л и т е л ь и о с т а л ь н ы е к н о п к и т а к ­
ж е т р е б у е т с я у д а л и ть . С в о й с т в о CanOverflow э л е м е н т а T o o lS trip д о л ж н о
и м е т ь з н а ч е н и е fa lse (ч т о б ы к н о п к и с п р а в о й с т о р о н ы п а н е л и и н с т р у ­
м е н т о в н е д о б а в л я л и с ь в м е н ю п е р е п о л н е н и я ), а с в о й с т в о GripStyle
- з н а ч е н и е H id d e n (ч т о б ы с к р ы т ь р а с п о л о ж е н н ы й сл е в а д е с к р и п т о р
п е р е м е щ е н и я ).

О О бработчики собы тий для кнопок


Н о в ы е к н о п к и назы ваю тся o p e n T o o l S t r i p B u t t o n , s a v e T o o l S t r i p B u t t o n и printTool-
S t r i p B u t t o n . Д в о й н ы м щ е л ч к о м д о б а в ь те к н и м о б р а б о т ч и к и с о б ы т и й .

Добавьте код, заставляющий работать кнопки Open и Save.


)аж нш е
1. Кнопка Save должна сериализовать объект world в файл. Она должна останавливать таймер (если си­
мулятор все еще работает, таймер может перезапуститься после сохранения) и вызывать окно диалога Save.
Сериализации должен подвергаться не только объект world, но и количество кадров, прожитых симулятором.
Сначала вы получите исключение S e r i a l i z a t i o n E x c e p t i o n с текстом Туре 'Forml'is not marked as
serializable. Ведь делегат B e e M e s s a g e связан С формой, значит, ее пытаются сериализовать.

Добавьте полям M e s s a g e S e n d e r в классах Hive и Вее атрибут [ N o n S e r i a l i z e d ] .

2. Кнопка Open десериализует мир из файла. Таймер ведет себя так же, как при щелчке на кнопке Save. Долж­
но открываться окно диалога Open и происходить десериализация указанного пользователем файла. После чего
можно снова связать делегат M e s s a g e S e n d e r с формой и при необходимости перезапустить таймер.

3. Не забудьте про обработку исключений! Убедитесь, что проблемы с чтением и записью в файл не влияют
на состояние мира. Создайте всплывающие окна с сообщениями об ошибке.

дальш е > 569


обзор и п редварит ельны е результ ат ы

\сод
p r i v a t e v o i d o p e n T o o l S t r i p B u t t o n _ C l i c k ( o b j e c t s e n d e r , E v e n tA r g s e ) {
W o r ld c u r r e n t W o r l d = w o r l d ; Перед т ем как о т кры т ь и п роч и т ат ь
i n t c u rre n tF ra m e s R u n = fram e sR u n ; ф айл, сделаем копию т екущ его состояния
м и ра и п а р а м ет р а fram esRun. В случае
проблем м и р будет восст ановлен из эт ой
b o o l e n a b le d = t i m e r l . E n a b le d ;
копии.
i f (e n a b le d )
t i m e r l . S to p ();

O p e n F i l e D i a l o g o p e n D i a l o g = new O p e n F i l e D i a l o g ( ) ;
o p e n D ia lo g . F i l t e r = " S im u la to r F i l e ( * . b e e s ) |* .b e e s " ;
o p e n D ia lo g . C h e c k P a th E x is ts = t r u e ; Hacm pouKu
o p e n D ia lo g .C h e c k F ile E x is ts = t r u e ; u вызов окна
o p e n D i a l o g .T i t l e = "C h o o se a f i l e w ith a s i m u l a t i o n t o диалога
O pen File.
i f ( o p e n D i a l o g . S h o w D i a l o g () == D i a l o g R e s u l t . OK) {
try{
B i n a r y F o r m a t t e r b f = new B i n a r y F o r m a t t e r ( ) ;
O n c p a m o p ___ m u s i n g ( S t r e a m i n p u t = F i l e . O p e n R e a d ( o p e n D i a l o g . F ile N a m e ) )
2A f —
panm u p ye-m w o r l d = (W o rld ) b f . D e s e r i a l i z e ( i n p u t ) ; Здесь происходит
fra m e sR u n = ( i n t ) b f . D e s e r i a l i z e ( in p u t) ; десериализация м ира
mOKa. J и количест ва кадров.

c a tc h (E x c e p tio n ex) {
M e s s a g e B o x .S h o w ( " U n a b le t o r e a d t h e s i m u l a t o r f i l e \ r \ n " + e x .M e s s a g e ,
"B e e S i m u l a t o r E r r o r " , M e s s a g e B o x B u t t o n s . OK, M e s s a g e B o x I c o n . E r r o r ) ;
w o r l d = c u r r e n t W o r l d ; «as---------
При появлении исключения мы
f r a m e s R u n = c u r r e n t F r a m e s R u n ;<
восст анавливаем последню ю
} сохраненную версию м ира
и п а р а м ет р а fram esRun.

w o r l d . H i v e . M e s s a g e S e n d e r = new B e e M e s s a g e ( S e n d M e s s a g e ) ;
f o r e a c h (B ee b e e i n w o r l d . B e e s )
b e e . M e s s a g e S e n d e r = new B e e M e s s a g e ( S e n d M e s s a g e ) ;
if (e n a b le d )
После загрузки мы
tim e rl.S ta rt 0 ; ^ ~ подсоединяем Э е л е г о т u
перезап ускаем т аймер-

Рабочую версию этого файла можно получить на сайте Head


First Labs: www.headlirstlabs.com/books/hfcshaip/

дальш е у 571
13 э л е м е н т ы у п р а в л ен и я и Г р а'^и Ч есК и е ‘^’р а Ш е н ш ы

Ф ^1^
Ф Наводим красоту ^

И н о г д а у п р а в л е н и е г р а ф и к о й п р и х о д и т с я брать в с в о и р у к и .
До этого момента визуальный аспект наших приложений был отдан на от­
куп элементам управления. Но иногда этого недостаточно, например, если
вы хотите анимировать изображение. Вам предстоит научиться создавать
свои элементы управления для .NET, применять двойную буф еризацию
и даже рисовать непосредственно на формах.
везде о бъ ект ы

Элементы управления как средстбо Взаимодействия с программой


Э л е м е н т ы T e x tB o x , P i c t u r e B o x , L a b e l... вы у ж е д а в н о и м е е т е
п р е д с т а в л е н и е о р а б о т е с н и м и . Н о ч т о именно вы о н и х зн а е те ?
(К р о м е т о го , ч т о и х м о ж н о п е р е т а с ки в а ть и з о к н а Т о о Ш о х н а
ф о р м у ).

Создание пользовательских элементов управления


Э л ем енты Т о о Ш ох бесценны п р и п о с тр о е н и и ф орм и п р и л о ­
ж е н и й , н о н и ч е г о т а и н с т в е н н о г о в н и х н е т . Э т о в с е го л и ш ь
к л а с с ы , а л ю б о й к л а с с вы м о ж е т е н а п и с а т ь с а м о с т о я т е л ь н о .
В С # с о зд а т ь п о л ь з о в а т е л ь с к и й э л е м е н т у п р а в л е н и я к р а й н е
л е г к о . Д о с т а т о ч н о н у ж н о г о б а з о в о го к л а с с а и ф у н к ц и и н а с л е ­
дования.

Ваши элем енты управления в окне ToolBox


Н и ч е го та и н с тв е н н о го н е т и в о кн е ТооШ ох. О н о п р о см а тр и ­
в а е т кл а с с ы п р о е к т а и в с т р о е н н ы е кл а с с ы . N E T в п о и с к а х э л е ­
м е н т о в у п р а в л е н и я . П р и о б н а р у ж е н и и к л а с с а , р е а л и з у ю щ е го
н у ж н ы й и н т е р ф е й с , о т о б р а ж а е т с я з н а ч о к . В а ш и э л е м е н т ы М о ж н о создат ь
та к ж е ото бразятся в это м о кн е. ^ наследующий ^лас-

ся в окне toolboK

Код , добавляю щ ий к цюрме


элем енты управления и даже
уд ал яю щ и й и х в процессе работы програллмы
Ф о р м е в к о н с т р у к т о р е м о ж н о п р и д а т ь и д р у г о й в ид . В ы у ж е р а б о т а ­
л и с э л е м е н т а м и P i c t u r e B o x . И х м о ж н о т а к ж е д о б а в л я т ь и уд ал ять .
В к о н ц е ко н ц о в , п р и п о с т р о е н и и вам и ф орм ы , И С Р добавл яет
к н е й э л е м е н т ы у п р а в л е н и я ... э т о о з н а ч а е т , ч т о вы м о ж е т е н а п и ­
с а ть а н а л о г и ч н ы й к о д и и с п о л ь з о в а т ь е г о п о м е р е н е о б х о д и м о с т и .

574 г л а в а 13
элем ент ы управление и гр аф ические ф рагм ент ы

Элементы управления - это то>ке объекты


Вы уж е о св ед о м л ен ы о в аж н о сти э л е м е н т о в у п р а в л е н и я . Вы п о л ьзо вал и сь кн оп кам и , тек с т о в ы м и п о ­
л я м и , ф лаж кам и , м етк ам и и други м и эл ем ен там и н а ч и н а я с главы 1. И в о т п р и ш л а п о р а узнать, ч т о это
т ак и е ж е о б ъ екты , как и все остал ьн ы е.
Э л ем ен ты у п р ав л ен и я п р о с т о ум ею т р и с о в а т ь сам и себя. О б ъ е к т F orm сл ед и т за их со с то я н и ем п р и
помош;и сп е ц и а л ь н о й к о л л ек ц и и C o n t r o l s . Э та ко л л е к ц и я д ае т вам во зм о ж н о сть д о б ав л я ть эл ем ен ты
уп р авл ен и я в ваш код и удалять их.
' CV.

Б о т ф о р м а для неслож но­


го прилож ения. К оллекция
C ontrols содерж ит ссы лки
на бее эл е м е н т ы у п р а в л е ­
ния ф ормы -

форм а имеет я эле­


мент ов управления,
поэт ом у в коллекции
Controls содержится
^ ссылок на с о о т ­
вет ст вую щ ие объ­
екты.

Трем
ф о р м е ^ roo>^6evv\"
t o n t r o is

1ЛЛЙ Lahel-

дальш е > 575


как мило!

Анимируем симулятор улья


Н а ш з а м е ч а т е л ь н ы й с и м у л я т о р н е р а д у е т гл аз. П р и ш л о в р е м я с о зд а т ь в и з у а ­
л и зац и ю , д ем он стр ирую щ ую пчел в д ейств ии . А н и м и р уем улей п р и п о м о щ и
эл ем ентов управл ения.

О П р ои сходящ ее показы вает пользовательский интерф ейс


С и м у л я то р будет с о сто я ть и з т р е х о к о н . О с н о в н о е , о то б р а ж а ю щ е е с тати с ти ку, у ж е го то в о .
О с т а л о с ь д о б а в и т ь о к н о , в к о т о р о м м о ж н о н а б л ю д а т ь за п р о и с х о д я щ и м в н у т р и у л ь я, и о к н о
с и зо б р аж ен и е м ц в е то ч н о го пол я, н а ко то р о м пчелы с о б и р аю т н е ктар .

^ B eeteveS m uiebr

Pause «m utatton Res^ І|вІ ^ Эт о окно показывает


#Boes
SRonwfS
6
11 происходящее внутри
Totai n ectarine» fidd
Rwneatun
3 4 -^ улья.
983
frame (Bte 1€^б2.5ги)
kle:1bee
Построенная f^ngToRower: 2 bees

HetumrigToHive: 2bees

?я Аликродиспле-ем Bee #1; Idle


на ш его с и м у л я ­
т ора.

^О'^^Ркие окна, при


В эт ом окне вы сворачивании осЯ^вно -
видите цветочное го окна они исчезают.
поле и собираюш^их К р о м е того, они следи-
нект ар пчел. " — ^ к>т зд основным окном
при его перемещениях
по экрану.

О Заставим работать кнопку P rint


О к н о с т а т и с т и к и и м е е т р а б о т а ю щ и е к н о п к и O p e n и S ave, в т о в р е м я к а к к н о п к а P r in t п о к а н е
ф у н кц и о н и р у е т. С делаем та к , ч то б ы щ е л ч о к н а н е й п р и в о д и л к р а с п е ч а тке д е й с тв и й сим уля­
то р а.

Beehive Simuiator

Pause simulation Reset ^ ^


#Єеез 6
#Fl0wers 11
Te«dhone>'inthehive 1.200_

576 г л а в а 13
элем ент ы управление и граф ические ф рагм ент ы

О О кно Hive
П ч е л ы л е т а ю т п о в се м у п р о с т р а н с т в у с и м у л я т о р а . К о г д а о н и
залетаю т в улей, п р о и схо д я щ ее о то б р а ж а ется в это м о кн е.

The Hive

В улье выделены м ри точки. Пчелы п о ­


являются в пит омнике, пользуются
выходом, когда приходит время л е­
т ет ь на цветочное поле, зат ем они
(юзора 1ц аю т ся и от правляют ся на
срадрику по переработ ке нектара.

Выход из улья находится в ф о р ­


ме hive, а вход - в ф о р т held.
(П оэт ом у оба значения были п о ­
мещены о словарь.)

\
Эт о вход в улей. /До­
Сборнектара происходит в окне Field стигнув этой точки,
О сн о в н ы м за н я ти е м пчел является сбор н е кта р а , ко то р ы й пчелы исчезают с поля
б у д е т п е р е р а б о т а н в м ед. П о е д а н и е э т о г о м е д а д а е т п ч е л а м и появляются у выхода
э н е р ги ю для сб о р а н о в о го н е кт а р а . в окне hive.

дальш е у 577
зам ечат ельное предст авление

Эти обш кт ы у ж е п о ст р о ен ы .
Визуализатор
Н а м тр е б у ется класс, к о т о р ы й н а о сн о в е и н ф о р м а ­ О бъект W orld отслеживает все. ч т о ^
ц и и и з н а ш е г о м и р а б у д е т р и с о в а т ь в д в ух н о в ы х состояние
ф о р м а х улей п ч е л и ц веты . С э то й зад ач ей о тл и ч н о улья, каждой пчелы и каждого цветка
с п р а в и т с я кл а с с Я е п д е г е г . Б л а го д а р я и н к а п с у л я ­
ц и и у ж е и м е ю щ и х с я классов нам н е п о тр еб уе тся
сил ьн о м е н я ть код.

Эт от объект задает
главное окно. Вы его
уже построили.

Каждая пчела зн а-
ет свое положение
о прост ранст ве,
м ы же можем
его использовать,
'^'^обы нарисовать
об ъ ект ы Hive пчелу в форме.
и Field явля­
ют ся ф орм а
м и, связанны^
ми с основной
Благодаря инкап­
формой. Визуализатор бе-
рет информацию
суляции классов
от объекта W orld
и обновляет две до­
Вее, Hive, Flower
черние формы.
и World добавле­
ние класса, визу­
ализирующего эти
в и -з у -а -л и -з и -р о -в а т ь , г л а г .
представлять или изображать. объекты, не тре­
Учитель попросил визуалижровать бует значительного
линии модели на листе бумаги. редактирования
кода.
578 г л а в а 13
элем ент ы управ ление и гр аф ические ф рагм ент ы

формы для Визуализации


О б ъ е к т W o rld о тс л е ж и в а е т все п р о и с х о д я щ ее в си м ул яторе, н о н е в со с т о я н и и н аглядн о п о к а за ть р е ­
зультат. Эту р аботу в ы п о л н я е т о б ъ е к т R e n d e r e r . О н с ч и ты в а е т и н ф о р м а ц и ю с о б ъ ек т о в W o rld , H iv e ,
B ee и F l o w e r и п р е д с та в л я е т е е в г р а ф и ч е с к о м виде.

•состояние
объектов
о б ъ е к т W orld инкапсулирован, поэт ом у
объект R en d erer использует для получе­
ния информации свойства этого объекта
и всех, объектов, которые с ним связаны.

Визуализируется каждый кадр


В ы зв ан н ы й о с н о в н о й ф о р м о й м етод Go () о б ъ е к т а W orld д о л ж ен в ы зы в а ть м етод R e n d e r () о б ъ ек ­
т а R e n d e re r для о б н о в л е н и я г р а ф и ч е с к о й и н ф о р м а ц и и . Ц в е т ы о то б р а ж аю т ся п р и п ом о щ и эл ем ен та
P i c t u r e B o x . Д ля п ч ел мы созд ад им а н и м и р о в а н н ы й эл ем е н т у п р ав л ен и я B e e C o n t r o l .

дальш е ► 579
управляем граф ическим и ф рагм ент ам и

Элементы управления прекрасно подходят для визуализации


С о зд ан и е н о в о й пчелы д о л ж н о со п р о в о ж д ать ся п о я в л ен и ем н о в о го эл ем ен­
т а B e e C o n t r o l в ф о р м е о б ъ е к т а H iv e . Э т о т э л е м е н т б у д е т м е н я т ь п о л о ж е н и е
п р и п е р е м е щ е н и я х пчел ы . В ы л ет и з улья о з н а ч а е т уд ал ен и е э л е м е н та и з ф о р ­
м ы H iv e и д о б а в л е н и е е г о в ф о р м у F i e l d . В о з в р а щ е н и е в у л е й с о п р о в о ж д а е т ­
ся о б р а т н ы м п р о ц е с с о м . И в се э т о в р е м я к р ы л ь я п ч е л ы д о л ж н ы д в и га т ь с я . Э т у
за д а ч у л е г к о р е ш и т ь п р и п о м о щ и э л е м е н т о в у п р а в л е н и я .

Q П р и п о я в л е н и и н о в о й п чел ы созд ается B e e C o n tr o l, к о т о р ы й


д о б а в л я е т с я в к о л л е к ц и ю C o n t r o l s ф о р м ы H iv e .

Controls.Add(new BeeControl()

О бъе^

О П р и п е р е м е щ е н и и п ч е л ы и з улья н а п о л е э л е м е н т у д а л я е тс я и з
к о л л е к ц и и C o n t r o l s улья и д о б ав л яется в о д н о и м е н н у ю ко л л е к­
цию ф ормы F ie ld .

о С о стар и в ш ись , пчелы вы ходят на пен си ю . Если п р и пр о в е р ке


о бъ ектом R e n d e re r сп и с ка B ees об н ар уж и в ается отсутстви е п че­
л ы , о н у д а л я е т е е э л е м е н т у п р а в л е н и я и з ф о р м ы H iv e .

Controls.Remove(theBee

580 г л а в а 13
Q
элем ент ы управление и граф ические ф рагм ент ы

^ з ь м и в руку карандаш_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
О пределите назначение каждого из ф рагм ентов кода. В се эти
ф рагм енты принадлеж ат форме.

this. Controls. Add (new ButtonO); ................................

Form2 childW i n d o w = ne w Form2(); .......................................


childWindow.Backgroundlmage = .................................
Properties.Resources.Mosaic; .................................
childWindow.BackgroundlmageLayout = .................................
ImageLayout.Tile; .......................................
C h i l d W i n d o w .S h o w (); .......................................
формй снабжена элем ен т ом ListBox
для добавления элем ен т ов в список можно
и спользоват ь м е т о д A ddR angeQ .

Label rayLabel = ne w L a b e l {); ^ .......................................


m y Label.Text = "Твое любимое животное"; .......................................
myLabel .Location = n e w P o i n t d O , 10); .......................................
List B o x myList = ne w L i s t B o x (); .......................................
m y L i s t .I t e m s . A d d R a n g e ( n e w o b j e c t [] .......................................
{ " К о т " , " П е с " , " Р ы б к а " , "Нет" } ); .......................................
m y L i s t . L o c a t i o n = n e w Point(10, 40); ^
Controls .Add (myLabel) ;
и оъ я сн я т ь назначение каждой
controls .Add (myList) ; Строки не т р еб ует ся , дост ат очно
за п и сат ь, какую ф ункцию вы пол­
няет ф рагм ен т .

Label controlToRemove = null; .......................................


foreach (Control control in Controls) { .......................................
if (control is Label .......................................
&& control.Text == "Bobby") .......................................
c o n t rolToRemove = control as Label; ......................................

}
C o n t r o l s .Remove (controlToRemove) ; Что получит ся, если т ак сделат ь, вы мож ете
c o n tr o lT o Remove.Dispose О ; п о см о т р ет ь сами. П опыт айт есь понят ь, поч е­
м у получает ся именно т акой р е зу л ь т а т !

Д опол н ител ь ны й вопрос: Как вы дум аете,


почем у опер атор c o n t r o l s . R em ove () не
бы л пом ещ ен в цикл foreach?

дальш е ► 581
\шг \же \щг
/ш\ Уш\ / П Уш\щшш

ОЗЬМИ в руку карандаш


Вот какую ф ункцию вы полняет каждый из ф рагм ентов кода:
'ешение
this .Controls .Add (new ButtonO) ;
Создаем кнопку и добавляем ее к форме.
Имеет значения по умолчанию (например,
сбойсмво Text будет пусм ы м ).
Form2 childWindow = new Form2();
Создаемся вторая ф орм а Рогм 2,
childWindow.Backgroundlmage =
загруж аемся фоновое изображение
Properties.Resources.Mosaic;
из файла Mosaic, ком орое заполняем
childWindow.BackgroundlmageLayout =
ёсю ф орм у. З ам ем полученное окно
ImageLayout.Tile;
Ьем онсм рируем ся пользовамелю...................
childWindow.ShowO ;

Создаемся м ем ка, задаемся ее м е к с м


Label myLabel = new LabelO;
и положение. З ам ем создаемся
myLabel.Text = "Твое любимое животное";
мекст овое поле, в него добавляются
myLabel.Location = new PointdO, 10);
чем ы ре э л е м е н т а , и поле пом ещ аем ся
ListBox myList = new ListBox();
под м ем кой. М ем ка и м ексм овое
myList.Items.AddRange( new object []
поле добабляюмся к ф орм е
{ "Кот", "Пес", "Рыбка", "Нет" } );
и ом ображ аюм ся.
myList.Location = new PointdO, 40);
Controls.Add(myLabel);
Controls.Add(myList); Чт о произойдет при
о м сум ст вии элемента
ВоЬВу в коллекции
Controls? '~ \

Label controlToRemove = null;


и^икл просм ам риваем все элементы
foreach (Control control in Controls) {
,!^правления ф орм ы в поисках мет ки
if (control is Label
с т екст ом «ВоЬЬу». После обнаруж е-
&& control.Text == "Bobby")
ния м ет ка удаляется из формы.
controlToRemove = control as Label;
}
Controls.Remove(controlToRemove);
Попытавшись т ак сделать, вы получи­
те исключение. Ведь нарушение целост ­
controlToRemove.Dispose(); ности коллекции ведем к непредсказу -
емым результ ат ам. Именно поэт ому
используемся цикл for.

Д о п о л н и тел ь н ы й вопрос: Как вы д ум аете, Коллекции нельзя редакм ировам ь


Ч/*
^
почем у опер атор C o n t r o l s . R em ove ()
.^fJP.^duHe цикла foreach.
не бы л пом ещ ен в цикл fo reach ?

582 г л а в а 13
элем ент ы управ ление и гр аф ические ф рагм ент ы

Первый анимированный элемент управления


М ы собираем ся п о с тр о и ть с о б с тв е н н ы й э л е м е н т у п р а в л е н и я с ан им иро ванны м и зо б р аж ен и ем п ч е­
лы . П о л у ч и т ь а н и м а ц и ю н е т а к т я ж е л о , к а к к а ж е т с я : р и с у е т с я р я д и з о б р а ж е н и й , к о т о р ы й п р и п о с л е д о ­
в а т е л ь н ы м п о к а з е с о з д а е т и л л ю з и ю д в и ж е н и я . Э т о н е с л о ж н о с д е л а ть с р е д с т в а м и C # и .N E T .

казе изображений возникает иллюзия движения к р ы л ь е в ‘^ Р^^кта. При быст ром п о ­

Поместим элемент б окно Toolbox


Е с л и вы к о р р е к т н о п о с т р о и т е э л е м е н т у п р а в л е ­
н и я B e e C o n t r o l, т о с м о ж е те п е р е та с ки в а ть е го
Скачать изображения для этой
н а ф о р м у и з о к н а T o o lb o x . О н в ы гл я д и т? к а к у ж е
з н а к о м ы й в ам э л е м е н т P i c t u r e B o x , д е м о н с т р и ­
главы можно по адресу:
ровавш ий и зоб р аж ен ие пчелы , про сто на это т
раз ка р ти н ка аним ирована.
www.headfirstlabs.com/ books/
hfcsharp/
T o o lb o x » D X
Если
л \ B e e C o n tro l on а fo r m C o m p o n e n ts КЛЙСС
(„,.д„1ллля О т о м ,
P o in te r —----------------------- □
1*“ !
B e e C o n tro l - ‘ F o rm l

. £>.All vVindovvs Form s


л C o m m o n C o n tro ls

P o in te r i
i
1 B u tto n

ki C h ec k B o x

H C h ec k ed L is tB o x w

я в л ,е т „ B u C o n tro l

дальш е ► 583
пользоват ельские элем ент ы управления

Базовый класс PictureBox


Т а к к а к в се э л е м е н т ы в о к н е to o lb o x я в л я ю т с я о б ъ е к т а м и , п о л у ч и т ь
н о в ы й э л е м е н т л е г к о . Д о с т а т о ч н о д о б а в и т ь к п р о е к т у к л а с с , н а сл ед у ­
ю щ и й о т о д н о г о и з с у щ е с т в у ю щ и х к л а с с о в , за д а в з а т е м п о в е д е н и е н о ­
в о го э л е м е н т а . % *
%
П р и с в о и м но в о м у эл ем енту уп р ав л ен и я и м я B e e C o n tr o l. С н ачал а
св яж ем с н и м с та ти ч н у ю ка р ти н ку , к к о т о р о й п о то м будет добавлена
Днимируй ш е э щ о
а н и м а ц и я , т о есть н а ч н е м с у ж е з н а ко м о го эл ем е н та P ic t u r e B o x .

С оздайте новы й проект и д о б а в ь те к е г о р е с у р с а м ч е т ы р е а н и м и р о в а н н ы е я ч е й к и . П о м ­


н и т е , к а к вы д о б а в л я л и л о г о т и п о б ъ е к т в и л ь с к о й к о м п а н и и в гл ав е 1? Н о в д а н н о м с л у ч ае
вас и н т е р е с у ю т н е р е с у р с ы формы, а р е с у р с ы проекта. Н а й д и т е ф а й л R esou rces.resx про­
екта в о к н е S o lu tio n E x p lo r e r ( к а к п о к а з а н о н а р и с у н к е ) и д в а ж д ы щ е л к н и т е н а н е м д л я
п е р е х о д а н а в к л а д к у R e s o u rc e s .

8 гл й б е 1 М(з( добавляли лого­ Solution Explorer ■ 'П К


т ип к файлу ф орм ы Resources.
На эт от р а з изображения Изображения
будут добавлены в общук) J з BeeControl o m r fo n дудут доступны
к о л л е к ц и ю ресурсов, благодаря л Щг Properties ОЛЯ всего проект а,
чему они ст анут доступны « не только для
' ї ї Assemblylnfo.cs ■ формы.
для всех классов проекта.
1> ІІІ^ ReSOU№№jräil
:> Settings.settirrgs Дважды щ елкни­
feÜ References т е на ст рочке
Вернитесь к главе 1 , чтобы всп ом ­ l3I Resources Resources.resx для
нить, каким образом вы эт о сделали. ^ перехода НЛ вКАйд-
Ш Forml.cs
ку Resources.
'5^ Program.cs

о И з о б р а ж е н и я п ч е л ы вы м о ж е т е с к а ч а т ь п о а д р е с у h ttp ://w w w .h ea d firstla b s.co m /b o o k s/


h fcsh a rp /. З а т е м в в е р х н е й ч а с т и в к л а д к и R e s o u rc e s в ы б е р и т е в п е р в о м р а с к р ы в а ю щ е м с я
с п и с к е в а р и а н т Im a g e s , а в с п и с к е A d d R e s o u rc e в а р и а н т A d d E x is tin g F ile ...

Bee animation l.png Bee animation 2.png Bee animation 3.png Вее animation 4.png
И м п о р т и р у й т е эт и изобра­
жения и д о Ш ь т е их к р е с у р ­
584 г л а в а 13 с ам своего проекта.
элем ент ы управление и граф ические ф рагм ент ы

О Д оступ к добавленны м ресурсам осущ еств л яется п о сред ств ом класса


P r o p e r t i e s .Resources. В в е д и те в п р о и з в о л ь н о м м е сте ко д а P r o p e r t i e s .
R e s o u r c e y r ~ ^ , H о к н о In te lliS e n s e п о к а ж е т р а с к р ы в а ю щ и й с я с п и с о к со в с е м и
мы
програм м ы мзо
им енно она указы ваем И С Р на бражение хранят ­
необходимость вызвать окно с ся в памят и как
объекты Bitmap-

pictureBoxl.Image =
Properties.Resources.Bee_animation_l;
I
Здесь указывается изображение,
связанное с элемент ом PictureBox
(в данном случае наиле начальное
изображение).
Нужно добавить с т т ч -
^ y / j s i n g System. ^
Эти изображения M n dow s-Form s», т ак
хранят ся в виде как б проект е при -
с Ы с т в а обще­ ^ ^ с т в у ю т элементы
го дост упа класса PictureBox и Tim er.
P ro p e rtie s . Resources.
Добавим элемент BeeC ontrol!
Здесь т аймеру
class BeeControl : PictureBox { присваивают -
ся начальные
private Timer a n im a tio n T im e r = new Timer() значения п у ­
public BeeControl() { т ем создания
убедит есь, animationTimer.Tick += new EventHandler(animationTimer_Tick) его экземпляра,
что в верхней задания свой -
animationTimer.Interval = 150; ства Interval
части клас­
animationTimer.Start 0; и добавления
са находит ­
ся ст рочка BackColor = System.Drawing.Color.Transparent; обработчика
«using System. BackgroundlmageLayout = ImageLayout.Stretch; событий.
Windows. J
fo rm s» .
private int cell = 0; После возвращения
void animationTimer_Tiok (object sender, EventArgs е) { к кадру # 1 , значение
парам ет ра cell с т а -
cell++; ^ новится равным О.
Каждый от счет switch (cell) {
т аймера вы ­ Backgroundlmage Properties.Resources.Bee_animation_l; break;
зывает событие, Properties.Resources.Bee_aniraation_2; break;
Backgroundlmage
оно увеличивает
значение п е р е ­ Backgroundlmage Properties.Resources.Bee_animation_3; break;
менной cell на Backgroundlmage Properties.Resources.Bee_animation_4; break;
1 , и при п о м о­ Backgroundlmage Properties.Resources.Bee_aniraation_3; break;
щи операт ора default: Backgroundlmage = Properties.Resources.Bee_animation_2;
sw itch назначает^
cell = 0; break;
карт инку свой­ введя код элемент а управления, перест ройт е програм м у
ст ву Image. }
для отображения изменений в конст рукт оре.
П острой те программу. Э л е м е н т у п р а в л е н и я B e e C o n t r o l д о л ж е н п о я в и т ь с я в о к н е
to o lb o x . П е р е т а щ и т е е г о н а ф о р м у , и вы п о л у ч и т е анимированную п ч е л у !
удален ие элем ент ов управления

Кнопка добавления элемента BeeControl


Д о б а в и т ь э л е м е н т у п р а в л е н и я к ф о р м е л е г к о , д о с т а т о ч н о д о б а в и ть е го в к о л л е к-
ц и ю C o n t r o l s . Т а к ж е л е г к о о н у д ал яется и з ф о р м ы . Н о э л е м е н ты у п р а в л е н и я -^ 7 ^ ^
р е а л и з у ю т и н т е р ф е й с I D i s p o s a b l e , п о э т о м у с л е д и те за в ы с в о б о ж д е н и е м р е- ^
с у р с о в п о с л е у д а л е н и я э л е м е н то в .

У д ал и те элем ент BeeControl с формы и добавьте кнопку ^


О т к р о й т е к о н с т р у кт о р ф о р м и удалите эл ем ен т B e e C o n tr o l. Д об ав ьте к ф о р м е к н о п к у
С делаем та к , чтоб ы о н а управляла п о я в л ен и ем и и сч е зн о в е н и е м эл ем ен та B e e C o n tr o l.

О Код управляю щ ей кнопки


В о т к а к в ы гл я д и т о б р а б о т ч и к с о б ы т и й д л я к н о п к и ;
Вы используете
инициализатор для
задания свойств
BeeControl control = null; BeeControl после соз­
private void buttonl_Click(object sender, EventArgs e) { / дания его экземпляра.
До^а6леин&1м if (control == null) { ^
б коллек­ control = new BeeControl0 { Location = new Point(100, 100) } ;
цию Controls ^ C o n t r o l s . Add ( c o n t r o l ) ;
элемент
} else { О перат ор using гарант ирует вы ­
немедленно
появляется u s in g ( c o n t r o l) { свобождение ресурсов после удаления
на форме. C o n t r o l s .R e m o v e ( c o n t r o l) ; элемента из коллекции Controls.
}
control = null;
}
}
З а п у с т и т е п р о гр а м м у . П е р в ы й щ е л ч о к н а к н о п к е д о л ж е н д о б а в л я т ь э л е м е н т B e e C o n t r o l .
П р и в т о р о м щ е л ч к е э л е м е н т у д а л я е тс я . С с ы л к а н а н е г о х р а н и т с я в з а к р ы т о м п о л е c o n t r o l .
П р и и с ч е з н о в е н и и э л е м е н т а о н а у к а з ы в а е т н а з н а ч е н и е n u ll.

Чтобы добавить элемент сДеной


Toolbox в окно toolbox, создайте
класс, производный от
[ j i BeeControt on 3 fo rm С о т р о п ^ класса Control.
Pointer
BeeControl
_;> All W indows Forms
J» C o m m on Controls Все визуальные элементы в окне Toolbox
наследуют от S y s t e m .W in d o w s . F o rm s .
Pointer
C o n t r o l . Члены этого класса вам уже
fab] Button знакомы; v i s i b l e , w id t h , H e i g h t , T e x t ,
0 CheckBox L o c a t i o n , B a c k C o lo r , B a c k g r o u n d lm a g e ...
ВЫ ИХ видите в окне Properties для всех эле­
H CheckedListBox ментов управления.

586 глава 13
элем ент ы управ ление и гр аф ические ф рагм ент ы

Удаление дочерних элементов управления


К л а с с элементов у п р а в ­
ления реализует инт ер­
О т р а б о та в , эл ем ен ты у п р а в л е н и я д о л ж н ы в ы своб ож дать ресурсы .
фейс IDisposable, по эт о­
Н о э л е м е н т B e e C o n t r o l с о з д а е т э к з е м п л я р э л е м е н т а T im e r ... к о ­ м у нужно проследить,
т о р ы й о с та етс я неуд ал енны м ! К с часть ю , эту п р о б л е м у л е гк о р е ­ чтобы все элементы
ш и т ь — д о с т а т о ч н о п е р е к р ы т ь м е т о д D i s p o s e {). удалялись, когда в них
пропадает надобность.

О П ер екр о й те м етод bisposeO и уд ал и те тайм ер


Н а ш к л а с с B e e C o n t r o l д о л ж е н о б л а д а ть у н а с л е д о в а н н ы м м е т о д о м D i s p o s e ( ) . Д о с т а т о ч н о
п е р е кр ы ть и р а с ш и р и ть э т о т м ето д . В в ед и те в ко д класса кл ю ч е в о е слово o v e r r i d e :

class BeeControl : PictureBox {


o v e r r id e
D1sfKee(booS disposing) После введения клю чевого
r Dock {ge^ set;} слова o verride появит ся
DoybleBuffered {get; set;} от о IntelliSense с «аереч-
i# Equals(object obj)
кюытия м ет одов, вы дери
^ Focused {get; }
т е м е т о д PisposeQ.
Font {get; set;}
^ ForeCotor {ge^ set;}
1 % GetAccessibilityObjectByldOnt objectid)

И С Р в в е д е т с т р о ч к у b a s e . D i s p o s e ( ) , к о т о р о й б у д е т в ы зы в а ть с я д а н н ы й м е то д :
protected override void Dispose(bool disposing) {
base.Dispose(disposing);
}
Любой соз­
Код удаления тайм ер а данный вами
Д о б ав ьте в к о н е ц н о в о го м е то д а D is p o s e () ко д , в ы зы в аю щ ий м е­
то д a n im a tio n T im e r .D is p o s e О , ко гд а а р гу м е н т d i s p o s i n g и м е ­ элемент управ­
е т з н а ч е н и е tr u e .
protected override void Dispose(bool disposing) {
ления должен
base .Dispose (disposing) ;
if
^
( d is p o s in g ) {
.
Мы перекрываем n o-
меченный ключевым
удалять все
словом, protected метод
a n im a t io n T im e r .D is p o s e О ;
DisposeQ, вызываемый
реализаиией метода
порожденные
IDisposable.DisposeQ эле­
}
мента управления. им элементы
Т еп ер ь эл ем ен т B e e C o n tro l у б и р а е т за собой!
и другие допу-
П р о в е р ь т е с а м и . Добавьте точку останова в в с т а в л е н н у ю с т р о ч к у
и з а п у с т и т е п р о гр а м м у . П р и к а ж д о м у д а л е н и и э л е м е н т а B e e C o n t r o l скаюпще уда­
и з к о л л е к ц и и C o n t r o l s б у д е т в ы зы в а ть с я м е т о д D i s p o s e ( ) .
ление объекты.
Если вы собираетесь создавать свои элементы управления, вам будет полезно
прочитать материалы с сайта ЬЦр://т5с1п.т1сг080Д.С0т/ги-ги/11Ьгагу/8У81ет.1а1вР05аЫе.а15Р0$е.а50х

дальш е > 587


создание элементов управления

Класс UserControl
Есть и более простой способ создания элементов управления для окна
toolbox. Вместо того чтобы пользоваться классом, наследующим от су­
ществующего элем ента управления, вам нужно добавить в свой проект ^
кл асс U s e r C o n t r o l. Вы работаете с ним, как с ф орм ой, перетаскивая н а 'чк'тт тт
него элементы из окна toolbox. И точ н о так же, как в случае с ф орм ой вы ] 0 U 0,
пользуетесь собы тиями. П оэтому заново создадим элем ент B e e C o n t r o l , V/ m
но уже с помощью класса U s e r C o n t r o l . ^

О О ткройте новы й проект W indows Forms A pplication и добавьте к его ресурсам ч еты ре и зо­
браж ения. П еретащ ите на форму кнопку и введите известны й вам код, добавляю щ ий и уда­
ляю щ ий элемент B e e C o n t r o l .

© Щ елкните правой кнопкой мыши на имени проекта в окне Solution E xplorer и вы берите
команду A dd » U ser C ontrol... Д об авьте эл е м ен т B e e C o n tr o l. О н будет откры т в кон­
структоре форм.
Используйте метод animationTimerJTickQ и поле cel! field
^— из старой версии элемента управления.
А
^
^
П еретащ ите на ваш элем ент управления элемент Timer. Н азовите его a n im a tio n T im e r ,
св о й ств у I n t e r v a l п р и с в о й т е зн а ч е н и е 150, св о й ств у E n a b le d - зн а ч е н и е tru e . Дваж­
ды щ елкните на тайм ере, чтобы добавить обработчик собы тия Tick. И спользуйте для него
то т же код, что и раньше.

О О бновите конструктор элем ента BeeControl:


М ; Эти изменения можно внести
^ и посредством окна Properties.
In itia liz e C o m p o n e n tO ;
BackColor = System.Drawing.Color.Transparent;
BackgroundlmageLayout = ImageLayout.Stretch;
}

О З ап у сти те програм м у, кнопка долж на работать так же, как и раньш е, просто теп ерь ею
управляет класс U s e r C o n t r o l . И м енно он отвечает за появление и удаление элемента
BeeControl.

Класс UserControl позволяет легко добавить элемент управле­


ния в окно Toolbox. Как и при работе с формой, вы можете
перетаскивать на него другие элементы управления и пользо­
ваться событиями.
588 глава 13
элементы управление и графические фрагменты

-------^
I С кол
ко л ько я н и пользовалась
Z' эл ем е н та м и уп р а вл е ни я, м н е
элементам
ни разу не п р и х о д и л о с ь и х удалять!
О
Зачем это вообщ е делать?

-- —

Ч
Вам не тр еб о в ал о сь уд ал ять элем енты упр ав л ени я, та к как
эту раб оту вы пол няла ф орм а.
Впрочем, не верьте нам н а слово. Воспользуйтесь функцией по­
иска и найдите в коде слово Dispose, вы обнаружите, что ИСР
добавила метод в ф айл F o r m l .D e s i g n e r .c s для перекры ти я
метода D i s p o s e {) и вы зова своего собственного метода b a s e .
D i s p o s e О . П ри удалении ф орм ы а в т о м а т и ч е с к и у д а л я е т с я
в с я с в я з а н н а я с н е й к о л л е к ц и я C o n t r o l s . Н о если вы вручную
удаляете элементы управления с ф орм ы или создаете новы е эк­
зем пляры , не принадлеж ащ ие коллекции C o n t r o l s , высвобож­
дать ресурсы предстоит уже вам.
^аД аБаеМ ы е
Ч асто Б о Т І р о С ь і

и O p e n F i l e D i a l o g . Их значки появля­
ются под формой. Редактируя свойства элемента
Почему код формы элементов
OpenFileDialog, Я заметил сообще­
BeeControl, один из которых получен
Проверьте сами. Создайте пустой класс, ние об ошибке: «Вы должны перестро­
из элемента PictureBox, а другой на
наследующий от класса P i c t u r e B o x . ить проект, чтобы изменения отобрази­
базе класса UserControl, работает
Перестройте проект и дважды щелкните лись в любом открытом конструкторе».
совершенно одинаково?
на его названии в окне Solution Explorer Почему оно появилось?

Г ; Коду безразлично, каким именно об­


разом реализуется объект B e e C o n t r o l .
Появится сообщение:

«Чтобы добавить компоненты в г ; Элементы управления отображаются


класс, перетащите их из окна Toolbox конструктором, и для отображения послед­
Ему важна лишь возможность добавить
и воспользуйтесь окном Properties для ней версии элемента программу требуется
объект методу C o n t r o l s .
задания их свойств». перестроить.

После двойного щелчка на строчке Перетащите из окна toolbox элемент Помните, как двигались крылья пчелы на
OldBeeControl в окне Solution O p e n F i l e D i a l o g . Он появится в элементе B e e C o n t r o l , даже в про­
Explorer появилось сообщение о до­ виде значка. Вы можете выделите его цессе перетаскивания его из окна Toolbox?
бавлении в мой класс компонентов. и изменить его свойства. Проделайте Программа еще не была запущена, но напи­
Что это значит? эту операцию, а затем вернитесь к коду санный вами код уже выполнялся. Таймер
класса. Посмотрите на конструктор, там запускал событие T i c k , а его обработчик
вы найдете код создания экземпляра менял картинку. Подобное поведение
Q ; Когда вы создаете элемент управле­
O p e n F i l e D i a l o g И задания его возможно только после компиляции кода,
ния, добавляя к проекту класс, наследу­
свойств. когда программа работает в памяти. Именно
ющий от элемента P i c t u r e B o x , ИСР
поэтому вам напомнили о необходимости
позволяет вам работать с компонентами.
регулярно обновлять код, чтобы элементы
К ним относятся невизуальные эле­
управления отображались корректно.
менты управления, например, T i m e r

дальше > 589


вот что вы будете делать

Поместим на форму анимиробанных пчел


Все готово для анимации симулятора. У вас есть класс B e e C o n t r o l и две форм ы
Нужно связать
и нужно располож ить пчел в п ространстве и перем ещ ать их с одного объекта формы, отобража­
на другой, поддерж ивая это движ ение. Требуется такж е располож ить цветы в ющие улей и поле, с
ф орм е F i e l d F o r m . Впрочем, это п ростая задача, так как цветы статичны. Этот формой ст ат ист и­
н овы й код мы поместим в класс Renderer. Вот что он будет делать: ки. в результ ат е
свертка последней
формы будет приво­
дить к свертке п е р ­
$ор м а со статистикой станет базовой вых двух. Д ля этого
Добавим к нашему проекту две недостаю щ ие ф ормы . Ф орма форму со ст а т и ­
H i v e F o r m будет отображ ать собы тия в улье, а ф орм а F i e l d F o r m — стикой нужно объ­
Перед т ем явить владельцем.
как п р и ­ происходящ ее на поле. П ом естите в конструктор основной форм ы
ст упит ь строчки отображ ения двух дочерних форм. П ередайте ссылку в ос­
к пост ро­ новную форму, указывая, что именно она является их владельцем:
ению визу­
ализатора. p u b lic F o rm lО {
Каждая форма облада­
подумаем, / / остальной код конструктора Form l ет методом ShowQ. Если
как именно h iv e F o rm . Show (t h i s ) вы хот ит е сделать одни
должен рабо­
т ат ь класс fie ld F o rm .S h o w (th is ) форму владельцем другой,
передайте ссылку на нее
Renderer... о мет од ShowQ.
О Класс R e n d erer ссы лается на объект w orld и на обе срормы
Класс R e n d e r e r долж ен знать полож ение каждой пчелы и каждого
цветка, поэтому ему нужна ссылка на объект World. А при добав­
лении, перем ещ ении и удалении элементов управления с ф орм не
обойтись без ссылок на эти формы:
c la s s R e n d e re r {
p riv a te W o rld w o r ld ; Начните класс
Renderer с эт их
p riv a te H iv e F o rm h iv e F o rm ; строк.
p riv a te F ie ld F o rm fie ld F o rm ;

О Состояние элементов отслеж и в аю т словари


Класс W o r 1 d следит за пчелами и цветами при помощ и списков L i s t < B e e > n L i s t < F l o w e г >.
Визуализатору нужен доступ к объектам В е е и Flow e r , чтобы понять, какие элементы
B e e C o n t r o l и P i c t u r e B o x им соответствуют. Если он не находит элем ента управления,
то долж ен создать его. В этом вам помогут словари. В класс R e n d e r e r нужно добавить два
закры ты х поля:
. . Эти словари позво -
p riv a te D ic tio n a ry < F lo w e r, P ic tu re B o x > flo w e rL o o k u p = д яю т виЗуализатору
new D ic tio n a r y < F lo w e r , P i c t u r e B o x > () ; хранит ь элементы
С p riv a te D ic tio n a ry < B e e , B e e C o n tro l> beeLookup = ^ управления длЯ каЖ -
X ; --------— дой пчелы и каждого
new D ic tio n a r y < B e e , B e e C o n tro l> ()
из НйШ&ВО
мира^

590 глава 13
элементы управление и графические фрагменты

воздействует как на
обе формы, т ак и на все объекты
построенные вами в предыдущей '
глабе для симулятора.

О бъект^

П ч е л ы и цветы знаю т свое м есто


Класс P o i n t хран и т инф орм ацию о полож ении цветов и пчел. Для лю бого объекта Вее
можно легко посм отреть B e e C o n t r o l и задать полож ение.
b e e C o n tro l = b e e L o o k u p [b e e ];
b e e C o n tro l.L o c a tio n = b e e .L o c a tio n ; Мы можем увидеть элемент цправ-
ления для любой пчелы и любого
цбетка. После чего положение эле-

Визуализатор добавляет элем енты управления новым пчел ам


Если метод словаря C o n t a i n s K e y О для выделенного объекта В е е возвращ ает значение
false, значит, для данной пчелы отсутствует элемент управления. Класс R e n d e r e r дол­
жен создать B e e C o n t r o l и добавить его в словарь, затем на ф орм у (Н е забудьте вызвать
метод B r i n g T o F r o n t () (П ом естить вперед) элем ента управления, чтобы пчела не оказа­
лась закры та цветами.)
if { ib e e L o o k u p .C o n ta in s K e y (b e e )) {
b e e C o n tro l = new B e e C o n tr o l0 { W id th = 4 0 , H e ig h t = 40 };
b e e L o o k u p .A d d (b e e , b e e C o n tr o l) ;
h iv e F o rm .C o n tro ls .A d d (b e e C o n tro l); Метод СопЬа1п5Кеу() показывает
b e e C o n tro l.B r in g T o F ro n t( ) ;
наличие пчелы в словаре. Если
она т ам от сут ст вует , значит,
} e ls e ее нужно добавить вместе с ее
b e e C o n tro l = b e e L o o k u p [b e e ]; элементом управления.

М ет од BringToFrontO га р а н т и р уе т .
J.H , ^гррда бидцт появляться
Помните, что в качестве ключа словари м огут исполь­ . «А»
зовать что угодно? В данном случае ключом выступает
объект Вее. Визуализатору нужно знать, какой элемент ф е р т Him э т о т “ 7
BeeControl на форме соот вет ст вует той или иной пче­ бает появление пчел поверх фона.
ле. Поэтому он находит пчелу в словаре, определяет ее
элемент управления и таким образом получает возмож­
ность перемещать ее по форме.

дальше > 591


начнем!
Эт о элем ен т ул равл е
ния PictureBox. свой­
ст во B ackgroundlm age
ф ормы Hive u Field кот орого связано с
внеш ним изображ е­
Добавим недостающие формы. В о з ь м и т е су щ ес т в у ю щ и й п р о е к т си м ул я то­ нием у л ь я Свойст ву
B a c k g r o u n d lm a g e L a y o u t
р а и при помощи команды Add » Existing Item... д о б а в ь т е п о л ь зо в а т ел ь ск и й
присвоено значение
э л е м е н т уп р а в л ен и я B e e C o n t r o l . В классе U s e r C o n t r o l три файла - .cs
Stretch. При загруз
• d e s i g n e r . cs
и . r e s x - добавьте их все. В файлах . c s и . d e s i g n e r . cs изме­ ке изображений улья
ните пространство имен в соответствии с вашим проектом. П ерестройте проект- в конструктор р е ­
элемент B e e C o n t r o l должен появиться в окне Toolbox. Добавьте графические сурсов они начина­
р е ^ с ы . Затем щ ел к н и т е п р а в о й к н о п к о й м ь п п и н а и м е н и п р о е к т а в окне ю т отображаться
в списке, вызываемом
Solution Explorer и выберите команду Windows Form... в меню Add. Назовите фай­
щелчком на кнопке ...
лы H i v e F o r m .cs и F i e l d F o r m .cs, и ИСР автоматически присвоит их свойствам рядом со свойством
N a m e значения H i v e F o r m и F i e l d F o r m . Вы только что созда^1и два новых класса Backgroundlmage в окне
Properties.

Поменяйте
размеры
форм, как
показано
на скрин­
шотах.

присвойте значение

Свойству формы Backgroundlmage


присвоите изображение улья,
а BackgroundlmageLayout —■Stretch.
Определим мест0П0А0)кение объектов
Р^'ЖНО понять, где на ф орм е F ie ld F o r m находится улей. В окне P roperties создайте
обработчик для с о б ы т и я M o u s e C l ic k ф о р м ы H i v e и добавьте код;
p r i v a t e v o id H iv e F o rm _ M o u s e C lic k (o b je c t s e n d e r, M o u s e E v e n tA rg s e) f
M e s s a g e B o x .S h o w (e .L o c a tio n .T o S trin g ()) ;

Мы скоро запустим форму. В этот момент вам нужно будет щелкнугь на изображ ении
выхода, и обработчик собы тий покаж ет точную координату этого места.
Добавьте аналогичны й обработчик к ф орм е Field. Щ елчком получите коорди-
наты вьгхода, питом ника и ф абрики. Эти координаты позволят обновлять метод
I n i t i a l i z e L o c a t i o n s {), написанны й в предьщущей главе для класса Hive:
З а п уст и в
Симу л я - Fp r i v a t e v o i d I n i t i a l i z e L o c a t i o n s О
т о р , вы ^
смож ете l o c a t i ,o n s = n e w D i c t i o n a r y^ < ws I-#
t ir i-Lxx^
n g ,f IPt ^oXi Xn Lt>> ( ) ;
-- и ____________
locations.Add("Entrance'', n e w Point (626, j a o ) ) •
І у н ч и в , уберите обра­
заполнит ь ботчик щелчка м ьт ки
locations. A d d ("Nursery", n e w P o i n t d T T T l e z E ^ '-g
коллекцию l ocations.Add("HoneyFactory", n e w P o i n t T l 57 , 78))• .он m oZo -
координат locations. A d d ("Exit", n e w P o i n t U V S , 180));} нат коорди -
внут ри ваших объектов.
улья.
Э т о наш вари ант координат , для ваших
ф о р м координат ы м о г у т от личат ься.
элементы управление и графические фрагменты

Все поля визуализатора


Построение бизуализатора закрыты, так как другие
классы не должны имет ь
доступа к его свойствам.
Вот код класса R e n d e r e r . О сновная ф орм а вы­ Класс world просто вы­
зы вает метод R e n d e r О этого класса после ме­ зывает методы RenderQ
тода W o r l d .G o ( ) , чтобы отобразить на ф ормах и ResetQ. Первый рисует
пчел и цветы. Убедитесь, что изображ ение цвет­ графику на формах, а
второй при перезагрузке
ка ( F l o w e r . p n g ) такж е загружено в ваш проект. форм удаляет с нмх эле­
менты управления.
c la s s R e n d e re r {
p riv a te W o r l d world;
О тслеж и вани е состояния пчел
p riv a te H iv e F o rm hiveForm; H « “
пчел- и цветов осуществляется че­
p riv a te F i e l d F o r m fieldForm; рез объекты Вее и Flower. А ля
отображения цоетод
цветов использи
использу--
p riv a te D ic tio n a ry < F lo w e r, P ic tu re B o x > flowerLookup = \ Р'^^мгеБох, й Зля
n e w D i c t i o n a r y < F l o w e r , P i c t u r e B o x > () ; / ^^^С^ГпрТТом ош ТТ^
p r i v a t e L i s t < F l o w e r > deadFlowers = n e w L i s t < F l o w e r > { ) ; > варей визуализатор связывает
^аждую пчелу и г^1
^ ^каждый цветок
Л.УГк(/01Ц Ц0С1/
p riv a te D ic tio n a ry < B e e , B e e C o n tro l> beeLookup = С
с- П

K ЭАаАЛР.ииллплл!.
элементами BeeControl , .
new D ic tio n a r y < B e e , B e e C o n tro l> ( ) ; и rictureßoK.
p riv a te L is t< B e e > retiredBees = n e w L i s t < B e e > ( ) ; J A
p u b lic Renderer ( W o r l d w o r l d , H iv e F o rm h iv e F o rm , F ie ld F o rm fie ld F o rm ) { Когда цветок
вянет, а пчела
th is .w o rld = w o rld ;
уходит на з а ­
th is .h iv e F o rm = h iv e F o rm ; служенный о т ­
th is .fie ld F o r m = fie ld F o rm ; дых, информация
} о ник удаляется
Таймер основной формы вызывает м е - из словарей при
— ■mod RenderQ, который обновляет пчел помощи спи­
p u b lic v o id Render 0 { ^ и цветы, а зат ем очищает ик словари. сков deadFlowers
D raw B ee s( ) ; и retiredBees.
D ra w F lo w e rs ( ) ;
R e m o v e R e tire d B e e s A n d D e a d F lo w e rs ( ) ;

p u b lic v o id Reset 0 {
fo re a c h (P ic tu re B o x flo w e r in flo w e rL o o k u p .V a lu e s ) {
fie ld F o rm .C o n tro ls .R e m o v e (flo w e r);
flo w e r.D is p o s e ();
}
fo re a c h (B e e C o n tro l bee in b e e L o o k u p .V a lu e s ) { При перезагрузке симулятора для
h iv e F o rm .C o n tro ls .R e m o v e (b e e ); каждой формы вызывается метод
fie ld F o rm .C o n tro ls .R e m o v e (b e e ); Controk.RemoveQ, удаляющий все
b e e .D is p o s e 0 ;
элементы управления. Он находит
каждый элемент в словаре и вызы­
} вает для него.метод PisposeQ. По­
flo w e rL o o k u p . C le a r (); сле чего очищает и словари также.
b e e L o o k u p .C le a r 0 ;

дальше > 593


цвет ы рисуются при помощи двух циклов foreach. Первый
добавляет элемент PictureBoxes для новых цветов, а второй Первый цикл foreach
удаляет эт от элемент для цветов, которые завяли. использует словарь
flowerLookup для про-
i — верки наличия элемента
p r i v a t e v o i d DrawFlowers() { управления у выделенного
f o r e a c h (F lo w e r f lo w e r i n w o r ld .F lo w e r s ) 1/ цветка. Не обнаружив
if ( ! flo w e rL o o k u p .C o n ta in s K e y (flo w e r)) {
такового, он создает но­
вый элемент PictureBox
P ic tu re B o x flo w e r C o n tr o l = new P ic tu r e B o x ( ) {
с помощью инициализа­
W id th = 45 , тора объектов, добавля­
Метод PrawFlowersQ H e i g h t = 55
ет его к форме, а затем
задает положение
элемента PictureBox im a g e = P r o p e r t i e s . R e s o u r c e s . F lo w e r , добавляет его в словарь
на форме при помощи Сл S i z e M o d e = P i c t u r e B o x S i z e M o d e . S t r e t c h l m a g e flowerLookup.
свойства Location объ- L o c a tio n = flo w e r.L o c a tio n
екта Flower. }; --------------
flo w e rL o o k u p .A d d (flo w e r, flo w e rC o n tro l)
Второй цикл foreach
fie ld F o rm .C o n tro ls .A d d (flo w e rC o n tro l); ищет в словаре
} flowerLookup элементы
PictureBox. которые уже
fo re a c h (F lo w e r f l o w e r i n flo w e r L o o k u p .K e y s ) не принадлежат форме,
if (. ! w o r l d . F l o w e r s . C o n t a i n s ( f l o w e r ) ) {
и удаляет их.
P ic tu re B o x flo w e rC o n tro lT o R e m o v e = f l o w e r L o o k u p [ f l o w e r ] ,■
fie ld F o r m .C o n tro ls .R e m o v e ( flo w e rC o n tro lT o R e m o v e );
flo w e rC o n tro lT o R e m o v e . D is p o s e ( ) ;
d e a d F lo w e r s .A d d ( f lo w e r ) ; удалив элемент PictureBox, OH
} к вызывает его метод DisposeQ
\ — • После чего объект Flower добав­
ляется в список deadFlowers.
p riv a te v o id DrawBees ( ) {
Метод DrawBeesQ т а к ­
B e e C o n tro l b e e C o n tro l; же использует два цикла
fo re a c h (B e e b e e in w o rld .B e e s ) { , roreach. Он по сути де­
b e e C o n tro l = G e tB e e C o n tro l(b e e ); лает то же самое, что
if (b e e .In s id e H iv e ) { и мет од DrawFlowersQ. Но
в данном случае функция
if ( fie ld F o r m .C o n tr o ls . C o n ta in s (b e e C o n tro l)) сложнее, поэтому мы рас­
M o v e B e e F ro m F ie ld T o H iv e ( b e e C o n t r o l) ; его функции
пределили его срункции по
no
} e ls e i f (h iv e F o rm .C o n tro ls .C o n ta in s (b e e C o n tro l) Л 1 е т о 5 д л 1 ^ 0^ -
M o v e B e e F ro m H iv e T o F ie ld (b e e C o n tro l) ; легчив понимание кода.
b e e C o n tro l. L o c a tio n = b e e .L o c a tio n ; kj t ^ /n д
Метод DrawBeesQ проверя-
’ em . не возникла ли ситуация.
когда пчела находится в улье.
f o r e a c h (В ее Ь е е i n b e e L o o k u p .K e y s ) { а ее элемент управления —
После удаления эле-^^ *: ' w o r l d . B e e s . C o n t a i n s ( b e e ) ) { форме FieldForm или
мента BeeControl b e e C o n tro l = beeLookup [b e e ]; наоборот. Д ля перемещения
нужно вызвать его ^ \ элемента BeeControls между
if ( f i e l d F o r m . C o n t r o l s . C o n t a i n s ( b e e C o n t r o l ) ) формами используются два
метод DisposeQ.
при эт ом будет fie ld F o rm .C o n tro ls .R e m o v e (b e e C o n tro l) ; дополнительных метода.
удален и связанный i f ( h i v e F o r m . C o n t r o l s . C o n t a i n s ( b e e C o n t r o l ) ) Второй цикл foreach p a -
с ним таймер h iv e F o rm .C o n tro ls .R e m o v e (b e e C o n tro l) ; ботает в основном как в
b e e C o n tro l.D is p o s e О ; К ;; методе DrawFlowersQ. но
, r e t ir e d B e e s .A d d ( Ь е е ) ; приходится уда-
} 1 лят ь элементы BeeControl
} из правой формы.

594 глава 13
элементы управление и графические фрагменты

В в е р х н е й ч а с т и к л а с са R e n d e r e r д о л ж н ы Метод GetBeeControiQ ищет пче­


н а х о д и т с я с т р о к и u s i n g S y s t e m .D r a w in g лу в словаре beeLookup. При о т с у т ­
и u s i n g S y s t e m .W in d o w s .F o r m s .
ствии нужной пчелы он создает эле­
м ент управления BeeControl размером
4 0 X 4 0 и добавляет его к форме hive
(ведь именно здесь появляются новые
p riv a te B e e C o n tro l GetBeeControl ( B e e b e e ) { пчелы).
B e e C o n tro l b e e C o n tro l;
if ( Ib e e L o o k u p .C o n ta in s K e y (b e e )) {
/" ■ '“ d e c o n t r o l = new B e e C o n t r o l 0 { W id th = 40, H e ig h t = 40 } ;
He забудьте: b e e L o o k u p . A d d (b e e , b e e C o n tro l);
! означает НЕТ! h iv e F o rm .C o n tro ls .A d d (b e e C o n tro l);
b e e C o n tro l.B r in g T o F ro n t() ;

}
e ls e
b e e C o n tro l = b e e L o o k u p [b e e ]; М ет од MoveBeeFromHiveToFieldQ
б ерет указанный эл ем ен т BeeControl
re tu rn b e e C o n tro l;
из коллекции Controls улья и добавля­
} ет его в коллекцию Controls поля.
Г '
p riv a te v o id MoveBeeFromHiveToField ( B e e C o n t r o l b e e C o n t r o l )
h iv e F o rm .C o n tro ls .R e m o v e (b e e C o n tro l);
b e e C o n t r o l. S iz e = new S iz e (2 0 , 20
Пчелм на поле им ею т меньший размер,
fie ld F o rm .C o n tro ls .A d d (b e e C o n tro l); чем пчелы в улье, соответственно,
b e e C o n tro l.B r in g T o F ro n t( ) ; метод должен менять свойство Size
элемента BeeControl.

p riv a te v o id MoveBeeFromFieldToHive( B e e C o n t r o l b e e C o n t r o l ) {
fie ld F o rm .C o n tro ls .R e m o v e (b e e C o n tro l); Метод MoveBeeFromFieldToHiveO
b e e C o n tro l.S iz e = new S iz e ( 4 0 , 4 0 ); возвращает элемент управления
h iv e F o rm .C o n tro ls .A d d (b e e C o n tro l); BeeControl обратно в улей. Соот­
b e e C o n tro l.B r in g T o F ro n t( );
ветственно, он должен увеличить
его размер.
}
К ак только методы VrawBees()
и PrawFlowersO обнаруживают,
p riv a te v o id RemoveRetiredBeesAndDeadFlowers() что цветка или пчелы больше
нет, они добавляют эти объекты
fo re a c h (B e e b e e in re tire d B e e s )
в списки deadFlowers и retiredBees,
b e e L o o k u p .R e m o v e (b e e ); чтобы в конце кадра произошло их
re tire d B e e s .C le a r (); удаление.
f o r e a c h (F lo w e r f l o w e r in d e a d F lo w e rs )
flo w e rL o o k u p .R e m o v e (flo w e r);
d e a d F lo w e rs . C le a r ( ) ; Напоследок визуализатор вызывает м е ­
тод для удаления из словарей всех увядших
] цветов и прекративших работу пчел.

дальше > 595


соединим все вместе

Соединим оснобную форму с формами HiveForm и FieldForm


М ы создали визуализатор, н о у него не т ф о р м , в к о т о р ы е м о ж н о б ы л о б ы в ы в о д и т ь и з о б р а ж е н и е . Ч т о ­
б ы и с п р а в и т ь ситуацию, в е р н е м с я к классу F o r m о с н о в н о й ф о р м ы (скорее всего, о н называется F o r m l )
и в н е с е м и з м е н е н и я в код:

Код перезагрузки мира помещен в м е -


1^ о д ResetSimulatorQ
public partial class Forml : Form { S a fo m
p r i v a t e H iv e F o r m h iv e F o r m = n ew H iv e F o r m () ; ? акт ам и в к ц ч е °
p r i v a t e F ie ld F o r m f i e l d F o r m = n e w F i e l d F o r m O ; ^ noKa^J^ T
p r iv a te R en derer re n d er er ; — Pernod Skow(). их

// остальны е поля Kodj создающий экземпляр


объекта World, помест ит е
public Forml О { в метод ResetSlmulatori).
I n i t i a l i z e C o m p o n e n t () К онст рукт ор основ­
ной формы помещает
форма передает ссылку на на мест о две дочер­
M o v e C h ild F o r m s ( ) ;
-себя методу Рогт.31ло\ы{), ние формы, а потом
h iv e F o r m .S h o w (th is ); ' ' становясь т аким способом отображает их. З а ­
f ie ld F o r m .S h o w ( t h is ) ; родительской формой. т ем он вызывает м е ­
R e s e tS im u la to r { ); тод ResetSimulator().
создающий экземпляр
tim e r l. In te rv a l = 50; объекта Renderer.
t i m e r l . T i c k += n e w E v e n t H a n d le r ( R u n F r a m e ) ;
tim e rl.E n a b le d = fa ls e ;
U p d a t e S t a t s (ne w T im e S p a n ()) ; Так как свойство StartPosition обеих дочер-
них форм имеет значение Manual, основная
форма может перемещать их при помощи
. p r i v a t e v o i d M o v e C h ild F o r m s {) Т
h i v e F o r m . L o c a t i o n = n e w P o i n t ( L o c a t i o n . X + W id th 10, L o c a tio n .Y ) ;
f ie l d F o r m .L o c a t i o n = n ew P o i n t ( L o c a t i o n .X ,
Этот код заст авля-
^ L o c a t i o n . Y + M a t h .M a x ( H e ig h t , h i v e F o r m . H e i g h t )
' ет дочерние формы
перемещаться. При
эт ом форма Hive
public void RunFrame(object sender, EventArgs e) { расположена рядом
framesRun++; с основной формой,
w o r l d .G o (random) ; Эт а строка в методе RunFrame а форма Field —
r e n d e r e r .R e n d e r О ; заставляет симулят ор обнов- под ними.
^ П предыдущ ий ко д ^ Т ь ^ в Т м е т ^ Т ^ О ^ '" Событие Move возника­
ет при каждом перем е­
щении основной формы.
p r i v a t e v o i d F o r m l_ M o v e (o b je c t s e n d e r E v e n tA rg s е) { Метод MoveChildFormsQ
M o v e C h ild F o r m s ( ) ; ^ . а до-
гарант ирует , что tio-
} ^ ^ EycKvfe в окне
Properties позволяет добавить
черные формы будут
слЬ ом .
Свойство Startposition обеих дочерних оораоо^ чик события Move. •
форм должно имет ь значение Manual,
иначе метод MoveChildFormsQ рабо­
т ат ь не будет.
элементы управление и графические фрагменты

Здесь мы создаем экземпляры


классов World и Renderer. которые
\ у перезагружают симулятор.
p r i v a t e v o i d R e s e t S i m u l a t o r () {
fra m esR u n = 0 ;
w o r l d = n ew W o r ld ( n e w B e e M e s s a g e ( S e n d M e s s a g e ) ) ;
r e n d e r e r = n e w R e n d e r e r ( w o r l d , h iv e F o r m , f i e l d F o r m )
}

p riv a te v o id re s e t_ C lic k ( o b je c t s e n d e r, E v e n tA rg s e)
r e n d e r e r .R e s e t 0 ;
Эля
R e s e tS im u la to r ( );
i f ( ! t i m e r l . E n a b le d )
t o o lS t r ip l. Ite m s [ 0 ] .T e x t = "S ta rt s im u la tio n " ;

}
p riv a te v o id o p e n T o o lS trip B u tto n _ C lic k (o b je c t s e n d e r, E v e n tA rg s e) {
/ / О статок кода для кнопки остается без изм енений.

ren d erer. R e se t();


r e n d e r e r = n ew R e n d e r e r ( w o r l d , h iv e F o r m , f ie ld F o r m );

Напоследок добавим на элемент ToolStrip код

m ^ ^ йЭллеиия всех пчел к ц в е т о в


из коллекции Controls обеих форм, а затем
бмзудлизйто]» на основе за ­
груженного пользователем мира.
Час1Ю°
<аДаБаеМые
БоПРоСЬ!

Для отображения формы мы использовали метод Можно ли редактировать предустановленные элементы


Show ( ) , но зачем мы передавали в качестве параметра управления и вносить изменения в их код?
ключевое слово t h i s ?
I 1; Нет, доступ к коду встроенных элементов управления Visual
I);Дело в том, что форма это всего лишь очередной класс. При ^udio отсутствует. Но каждый из этих элементов является
классом, от которого вы можете наследовать. Вспомните, как
ее отображении вы создаете экземпляр этого класса и вызывае­
те его метод S h o w ( ) . Существует перегруженная версия этого наследованием от элемента P i c t u r e B o x вы создали элемент
метода, которая в качестве параметра использует родительское B e e C o n tro l.
окно. Между родительским и дочерним окном существует особая
связь, например, сворачивание родительского окна ведет к авто­
матическому сворачиванию всех дочерних окон.

дальше > 597


что-то не так

Тестирование... М М . . .
Скомпилируйте код, исправьте лю бы е обнаруж енные ош ибки и запустите симулятор.

пчелы должны
Теп е р ь
радост но т т т ь
крыль>яллы- ,

4?

Поменяйте конст ант ы


и п о см о т р и т е, как
ви зуали зат ор р е а ги р у е т
на изменение количест ва
ц вет ов и пчел.

598 глава 13
элементы управление и графические фрагменты

Выглядит красиво, но чего-то не хв ата е т...


П рисм отревш ись к пчелам, жужжащим вокруг улья и цветов, вы зам етите, что их визуализация проис­
ходит не без проблем. П омните, что свойству B a c k C o l o r элем ента B e e C o n t r o l было п рисвоено значе­
ние C o l o r .T r a n s p a r e n t ? К сож алению , этого недостаточно, чтобы избеж ать проблем.

П р облем ы с производительностью
Вы заметили, как замедляется симулятор, когда все пчелы залетаю т в улей? О собенно это
заметно, если добавить пчел, увеличив константу в классе Hive. О братите внимание, как
при этом меняется частота кадров.

О Н еполная прозрачность срона


И другая, совсем отдельная проблема. Графический ф айл с цветком имеет прозрачны й
фон. Это позволяет поместить цветы на ф он ф орм ы , но не очень красиво выглядит, когда
цветы начинаю т перекры ваться.

Прозрачный фоновый цбеид


элем ен т а PictureB ox озня
т е т , чт о на
в эт о м м ест е б уд ут выво Л
диться прозрачные пикселы. К огда один элем ен т
сквозь кот оры е будет про PictureBox оказы вает ся
ст у п а т ь ф он ^ В £ ^ - ^ о поверх др уго го . С * р и с у е т
эт о не всегда т о . чт о нам прозрачны е пикселы, удаляя
нужно. част ь пикселов с нижнего
элем ент а.

5он пчел такж е недостаточно прозрачен


К ажется, парам етр C o l o r .T r a n s p a r e n t им еет ряд ограничений. Когда пчелы садится на
цветок, появляется вы резанны й прямоугольный фрагмент. П розрачн ость лучше п роявля­
ется внутри улья, но при п ерекры ти и изображ ений пчел возникает та же проблема. К роме
того, если присм отреться к пчелам, перемещ аю щ имся по улью, можно заметить, как ис­
каж ается изображ ение.

дальше > 599


нехватка ресурсов

Проблемы с производительностью
Загруж енны е изображ ения пчел имею т больш ой размер. Д ействительно большой. О ткройте одно из
них в программе просм отра и убедитесь. Это означает, что элемент P i c t u r e B o x ужимает изображ ение
при каждом изменении. А ведь изм енение масш таба изображ ения занимает время. И м енно поэтому
движ ение пчел замедляется, когда они залетаю т в улей. П розрачн ы й ф он элемента B e e C o n t r o l удваи­
вает работу: сначала требуется уменьшить изображ ение пчелы , а затем участок ф она, на котором будут
выводиться п розрачны е пикселы.

Вее animation l.png Изображение пчелы


велико, и элем ен т у
PictureBox в каждом
кадре т ребует ся
врем я, чтобы его
ум еньш ит ь.

J
Hive (Inside).png

Э лем ент P i c t u r e B o x ум ен ь ш ает


карти нку с изо браж ени ем пчелы .
Э то зан и м ает в р ем я...

К арт и н ка с изображ ени­


ем внут реннего у с т р о й ­
ст ва улья тоже огром на.
Каждый р а з, когда перед
ней оказы вает ся п ч е ­
ла, элем ен т PictureBox
ум ен ьш ает ее до нужного
р а зм ер а .

...п о э то м у д л я ув ел и чен и я п р о и зв од и тел ьно сти си м ул ятора карти нку


нуж но ум ен ь ш и ть до ее отображ ения.

600 глава 13
д л я у в е л и ч е н и я и р и и а ь и д и гсльиис 1И Д^иао^гио плпо^си/и'ю«! \
метод, меняющий размер изображения в момент его загрузки, что- ^
б ы для о т о б р а ж е н и я п ч е л и ф о н о в о г о р и с у н к а улья пользоваться у ж е ^ у 1Х р а Ж Н 0Н и 0
п т м а с ш т а б и п о в а н н ы м и веосиями. ^

9 М ето д R e s i z e l m a g e для визуализатора


Все изображ ения проекта хранятся как объекты B i t m a p . Вот статический метод, меняю щ ий их
разм ер, которы й нужно добавить в класс R e n d e r e r :
p u b l i c s t a t i c B i t m a p R e s i z e lm a g e ( B i t m a p p i c t u r e , i n t w i d t h , i n t h e i g h t ) {
B itm a p r e s iz e d P ic t u r e = new B it m a p ( w id th , h e i g h t ) ;
u s i n g (G r a p h ic s g r a p h ic s = G r a p h i c s . F r o m l m a g e ( r e s i z e d P i c t u r e ) ) {
g ra p h ic s -D ra w lm a g e (p ic tu re , 0, 0, w id th , h e ig h t) ;

L tu rn re s iz e d P ic tu re - \ Ч то такое о б ш кт g ra p h ics и как работ ает


^ re tu rn r e s iz e d P ic tu е, данный мет од, М И поговорим чут ь позднее.

@ М етод R e s i z e C e l l s для элем ента B e e C o n t r o l


Элемент B e e C o n t r o l хран и т собственны е объекты B i t m a p в виде массива. Вот код, заполняю ­
щ ий этот массив и дающ ий каждому элементу правильны й размер:
О б ъ е к т ы B it m a p , хранящие картинки с изображением пчел,
p r i v a t e B itm a p [] c e l l s = n e w B i t m a p [ 4 ] ; при помощи метода ResizelmageQ. X
p r iv a te v o id R e s i z e C e l l s () { \
c e l l s [0 ] = R e n d e r e r .R e s iz e Im a g e ( P r o p e r tie s .R e s o u r c e s .B e e _ a n im a tio n _ l, W id th , H e ig h t ) ;
c e lls [1] = R e n d e r e r .R e s iz e Im a g e ( P r o p e r tie s .R e s o u r c e s .B e e _ a n im a tio n _ 2 , W id th , H e ig h t ) ; \ J
c e lls [2 ] = R e n d e re r .R e s iz e lm a g e ( P r o p e r tie s .R e s o u r c e s .B e e _ a n im a tio n _ 3 , W id th , H e ig h t) r >ьС
c e l l s [3 ] = R e n d e re r .R e s iz e lm a g e ( P r o p e r tie s .R e s o u r c e s .B e e _ a n im a tio n _ 4 , W id th , H e ig h t)
}
@ П у сть оператор s w i t c h работает с м ассивом cells, а не с ресурсами
О п ератор switch обработчика собы тий Tick задает свойство Backgroundlmage:

B a c k g ro u n d lm a g e = P ro p e rtie s .R e s o u rc e s .B e e _ a n im a tio n _ l;
Зам ените Properties .Resources .Bee_animation_l на cells [0 ] .И зам ените и остальны е строки
case, введя на вторую позицию c e l l s [1 ], на третью —c e l l s [2 ], на четвертую — c e l l s [3 ], на
пятую - c e l l s [2 ], на позицию default - c e l l s [1 ], чтобы отображ ались только отмасштабиро-
ванны е рисунки.

Добавить вызов м етод а R e s i z e C e l l s О ДЛЯ элем ента B e e C o n t r o l


Н овы й метод R e s i z e C e l l s () долж ен вы зы ваться в ниж ней части конструктора. Затем дважды
щ елкните на строчке B e e C o n t r o l в окне Properties. О ткройте в этом окне страницу Events (щелк­
нув на значке с изображ ением молнии) и дважды щ елкните на строчке Resize, чтобы добавить
обработчик этого собы тия. Н овы й обработчик такж е долж ен вы зы вать метод R e s i z e C e l l s (),
чтобы масш табирование ф орм ы сопровож далось масш табированием аним ированны х картинок.

О вручную выберите дюновое изображ ение формы


В окне P roperties вы берите для ф онового и зображ ения улья вариант (п о п е ). Затем в конструкто­
ре загрузите вместо него отм асш табированное изображ ение.
p u b lic p a rtia l c la s s H iv e F o rm : F o rm { Свойст во C lientR ectangle ф орм ы вклю чает
p u b l i c H iv e F o rm О { f^^ctangte, содержащее
инф орм а-
In itia liz e C o m p o n e n tO ; 00 от ображ аемой области
B a c k g ro u n d lm a g e = R e n d e r e r .R e s iz e lm a g e (
P r o p e r t i e s . R e s o u r c e s . H i v e ___i n s i d e _ , \
C lie n tR e c ta n g le .W id th , C lie n tR e c ta n g le .H e ig h t) ;

j ^
Запусти те си м улятор, он раб отает нам ного бы стрее!
подробно о графике

ф орм ы и элем ент ы


Объект Graphics управления снабжены м е ­
т одом C reateG raphicsQ j
П осмотрим на добавленны й к визуализатору метод R e s i z e l m a g e {) . О н кот орый возвращ ает
начинает с создания объекта B i t m a p требуемого размера. Затем п ри ^
Вппрочем
ю о и ем, ^скоро
с^ р о Ы все
мощ и метода G r a p h i c s . F r o m l m a g e О создается о б ъ е к т G r a p h i c s и увидит е собственными
используется метод D r a w l m a g e () этого объекта для отображ ения кар­ глазам и.
тинки на объекте B i t m a p . О братите внимание, каким образом методу
Вы передаете м ет о ­
D r a w l m a g e {) передаю тся парам етры w i d t h и h e i g h t . И менно в этот мо­ ду изображение с дан-
мент осуществляется масштабирование. Н аконец вам возвращ ается толь­ ными о т ом . какую
ко что созданный объект B i t m a p , которы й можно использовать в каче­
стве ф онового изображ ения улья или аним ированного рисунка пчелы.

public static Bitmap Resizelmage(Bitmap picture, int width, int height)


/ ширину и высоту оно
должно иметь.

{
Bitmap resizedPicture = new Bitmap(width, height);

using (Graphics graphics = Graphics,Fromlmage(resizedPicture)) {


graphics.Drawlmage(picture, 0, 0, width, height); ^
} ^
ш т о д FromlmageO возвраш,ает новый обш кт drapkics,^
V _ позволяющ ий вст авлят ь граф ические f
return resizedPicture; бражение. В оспользуйт есь функцией IntelliSense аля у
чения м ет од ов, принадлежащих классу Graphics. Н апри­
} м е р , м ет о д P raw lm ageQ коп и рует
resizedP ictu re с координат ой (О. О) и f
соот вет ст ви и с заданными п а р а м ет р а м и w id th и height.
Масштабирование изоб|>а)кения
Это пример. Закончив его,
П еретащ ите кнопку на форму F i e l d и добавьте к ней этот код, удалите кнопку и код.
создающ ий элемент P i c t u r e B o x разм ером 100 хЮО пикселов
с черн ой линией по краю, что позволяет оц ен ить его размер.
Затем метод R e s i z e l m a g e {) масштабирует картинку пчелы до
Метод Resizelmage ( )
80 X 40 пикселов и н азначает свойству Image. П осле добавле­
н и я к ф орм е элем ента P i c t u r e B o x появляется пчела,
создает объект
p riv a te
{
v o id b u tto n l_ C lic k (o b je c t se n d e r, E v e n tA rg s e)
Graphics, рисую­
P ic tu re B o x b e e P ic tu re
b e e P ic tu re .L o c a tio n
= new P ic tu r e B o x 0 ;
= new P o in t d O , 1 0 );
щий на невидимом
b e e P ic t u r e . S iz e = new S iz e d O O ,
b e e P ic tu re .B o rd e rS ty le
1 0 0 );
= B o r d e r S ty le . F ix e d S in g le ;
объекте Bitmap. Он
b e e P ic tu re .Im a g e = R e n d e re r. R e s iz e lm a g e (
P ro p e rtie s .R e s o u rc e s .B e e _ a n im a tio n _ l, 80, 4 0 );
возвращает о^ект
}
C o n tro ls .A d d (b e e P ic tu re );
^ТЬейеи Bitmap, который
Вы наблюдаете процесс масштабиро­
вания изображения—сжатая к а р т и н к а '^ и отображается на
намного меньше элемента PictureBox.
И метод ResizelmageQ уменьш ит р а з­
мер элемента. форме или элементе
602 глава 13
KctureBox.
элементы управление и графические фрагменты

Если проблем с произ­


Объект Bitmap водительностью нет,
добавляйте пчел, пока
Ч то происходит с граф ическим и файлам и, которы е программа не замед­
импортирую тся в ресурсы проекта? Вы уже знаете лит работу!
способ доступа к ним — P r o p e r t i e s . R e s o u r c e s .
Н о как именно поступает с ними прилож ение? Класс Bitm ap имеет несколько перегрцжен-
.NET п р е в р а щ а е т и з о б р а ж е н и я в о б ъ е к т Bitmap:
ных конструкторов. Этот загружает гра­
фический файл с диска. Если передать емц
целые числа, указывающие желаемые ширина
и высоту, результ ат ом станет обьект
Bitm ap, которому пока не сопоставлено ни­
какого рисунка.

j i t m a p bee = n e w B i tmap("Bee a nimation 1 .p n g " )

Bee animation 1-png


Эта на
га-
Отобра)кение объекта Bitmap
р а н т и р уе т
Загруж енны е в объекты B itm a p изображ ения ф орм а по­
казывает на экране при помощ и следующего кода: . П е«”

using (Graphics g CreateGraphicsО ) {


g .Drawlmage(myBitmap, 30 , 30 , 150 , 150);
f
Метод DrawlmaaeQ использует
объект Bitm ap в качестве
изображения, которое нужно ..начальные
Г ...и размер. 150 X IS O пикселов.

нарисовать... координаты X и Y...

Чем они больше... М асш таб и р о в ани е изображ ений


Вы обратили внимание на два последних парам етра ме­ тр еб ует боль ш ой п р о и зв о д и ­
тода D r a w l m a g e () ? Ч то, если изображ ение, помещ аемое тел ь ности ! Н ичего страш ного,
в объект B i t m a p , им еет разм ер 175 х 175? Его требуется если эта п р о ц ед ура явл яется
уменьшить до разм ера 150 х 150. А как быть, если объект о д но кратно й. Но когда ее п р и ­
B i t m a p содерж ит изображ ение разм ером 1500 х 2025? ход ится вы пол нять в КАЖД01У!
М асш табирование п роизой дет намного медленнее.... КА Д Р Е , работа програм м ы за­
м едляется. И м енно со слиш ком
Это изображение бо льш им разм ером загр у ж ен ­
имеет размер ны х вами и зображ ений связана
3 0 0 X 500 сли ш ком м едленная работа
пикселов... сим улятора.

...уменьшается до размера й.50 х 1 5 0 пикселов.


И это замедляет работу симулятора!
дальше ► 603
методы создания графики

Пространство имен System.Drawing


О бъект Graphics принадлеж ит пространству имен S y s t e m .
Drawing. В . N E T F r a m e w o r k имеется набор инструментов называют иногда
для работы с граф икой, намного превосходящ ий по возмож­ <^raphics Device. ^^начает
ностям знаком ы й вам P i c t u r e B o x . Вы мож ете рисовать ф о р ­ феис для npedcmaiMuf^^'*^'T^P~
мы, работать со ш риф там и и сложной граф икой... благодаря 'веских обшктое)
помощью г т Г и ш if, ^
объекту G r a p h i c s . Он создается каждый раз, когда требуется с обьекта Q r a p S
добавить или отредактировать граф ический фрагмент. Затем
вам остается воспользоваться его методами.

О бъ ект, на котором вы собираете рисовать


Н априм ер, возьмем форму. Ее метод C r e a t e G r a p h i c s ()
возвращ ает экземпляр G r a p h i c s , позволяю щ ий рисо­ М ет од CreateC!mph■csO
вать на самом себе. мож ет бы т ь вызван ф о р ­
м ой, кот орой он п ри н ад­
леж ит, или др уги м о б ъ ­
ект ом . В обоих случаях
он возвращ ает ссылки на
g t h i s .C r e a t e G r a p h i c s () объект агарЫ с5. м е т о ­
ды кот орого и создаю т
рисунок.

вы зо в эт о го экзем пляра
f
Вы ничего не р и с уе т е
S y ste m .^ " йгарЬ1с5 влияет на ф о р м у , на объект е С(гарЫс5.
с о з д а в ш у ю о б ьек т б1гарП1С5. Он использует ся для
рисования на других
объект ах.

о М етоды объекта &гарК|*с8


Каждый объект 0 г а р Ь 1 с з снабжен методами, позволя­
ющими рисовать на породивш ем его объекте. Создава­
ем ы е им фигуры и изображ ения появляю тся на форме.

Вы вы зы вает е м ет оды
объект а С га р Ь Ъ , .n v a w L i n e S ( )
а изображение
появляет ся на
породивш ем его К п р и м ер у, м ет о д
объект е. DrawLinesQ р и с у е т
набор линий на
объ ект е, создавш ем
экзем п л я р Q m pkics.

604 глава 13
элементы управление и графические фрагменты

Знакомство с 6 D I + лагат ься ст рока Ms/na с ~


В прот ивн ом случае при
О бъект G r a p h i c s позволяет рисовать лю бы е ф орм ы п р о ек т у ф орм ы ИГР ^°°^влении к
и изображ ения. Вы вы зы ваете его методы, и на создав­ Завит э Ц у Тт^рочкур ^ вб класс эт ой ф ормы
д о. -
шем его объекте появляется нужный рисунок.

Н ачнем с создания объекта G r a p h i с s . И спользуйте метод C r e a t e G r a p h i c s () ф ормы . П омните,


что этот объект реализует и н терф ей с I D i s p o s a b l e ( ) , поэтому при создании нового экземпляра
пользуйтесь оператором using: П омнит е, чт о изображение появляется
u s in g (G ra p h ic s g = t h i s . C r e a t e G r a p h i c s ()
I на объ ект е, создавилем э т о т экзем пляр.

О Ч тобы нарисовать линию , вы зовите метод D r a w L i n e {) и укажите координаты X и Y начальной


и конечной точек: К оординат а начальной точки...
g . D ra w L in e (P e n s . B lu e , 30, 10, 100, 4 5 );
. ...координат а конечной точки.
то же самое можно сделать при помош;и класса Point: мож ет е вы бират ь
цоет а, д ост ат оч -
д . D ra w L in e (P e n s .B lu e , new P o in t( 3 0 , 4 5 ) , new P o in t( lO O , 1 0 ) ) ; HO ввест и «C o lo r» ,
« P e n s» или «B ru sh es»
^ „ п ост ави т ь т очку, и в
О Э тот код рисует залиты й серым цветом прямоугольник с голубой рамкой, окне IntelliSense п о я -
Его разм ер задает объект R e c t a n g l e . В нашем случае верхний левы й угол вит ся перечень цветов.
находится в точке (150, 15), ш ирина 140 пикселов, а вы сота 90 пикселов.

g . F illR e c ta n g le ( B r u s h e s . S la te G ra y , new R e c ta n g le (1 5 0 , 15, 140,


g . D r a w R e c ta n g le ( P e n s . S k y B lu e , new R e c ta n g le (1 5 0 , 15, 140, 9 0 ));

Д ля рисования окруж ностей и эллипсов пользуйтесь методами D r a w C i r c l e () или


F i l l C i r c l e О . Размер ф орм ы задает объект R e c t a n g l e . Следующий код рисует
сдвинутые друг относительно друга для создания эф ф екта тен и эллипсы:
д . F illE llip s e (B ru s h e s .D a rk G ra y , new R e c ta n g le (4 5 , 65, 200, 1 0 0 ));
g .F illE llip s e ( B r u s h e s .S ilv e r , new R e c ta n g le (4 0 , 60, 200, 1 0 0 ));

М етод D r a w s t r i n g () вводит текст. Н о сначала вам нужно создать объект Font, определяю щ ий
ш рифт. Этот объект реализует и нтерф ей с I D i s p o s a b l e :
u s in g (F o n t a r ia l2 4 B o ld = new F o n t(" A r ia l" , 24, F o n tS ty le .B o ld )) {
g .D ra w s trin g (" H i th e re !", a ria l2 4 B o ld , B ru s h e s .R e d , 50, 7 5 );

Выполнив операт оры по^


порядку, вы полу^^ите ‘^ а ге мы
т а к ую ф орм у- Верхний создавали объект Graphics
левый уго л и м еет к о о р ­ п о эт о м у на ф о р м е ^ ’
динат у (О, О). о т с у т с т в у е т цифра 1 .

дальше ► 605
рисуем картинку

Рисуем на форме
Создадим новое прилож ение Ш1п(1о\У5, к о т о р о е рису­
ет картинку п о с л е щ ел ч к а н а ф о р м е . J a p u c y u n ie э щ о

9 Д обавим к ф орме собы тие C l i c k


Н а вкладке E v e n ts о к н а P r o p e r t ie s найдите собы тие Click и дважды щ елкните на нем.
Так как нам требуется объект G r a p h i c s , начните код обработчика собы тия с оператора
using. П ри работе с GDI+ вы используете объекты, реализующ ие и н терф ей с I D i s p o s a b l e .
Если не удалить их, они будут потреблять ресурсы до выхода из программы. П оэтому вам
потребуется множ ество операторов using:
u s in g (G ra p h ic s g = C re a te G ra p h ic s О ) { Эт о первая ст рочка М етода
обработчика события
Forml_Ciick(). Постепенно мы
предоставим вам и остальные
О П о ряд ок рисования на ф орме строчки.
Нам потребуется голубой ф он, поэтому начнем с голубого прямоугольника, все остальное
будет появляться п о в е р х н е г о . Вы воспользуетесь свойством ф орм ы C l i e n t R e c t a n g l e .
Это объект R e c t a n g l e , определяю щ ий границы доступной для рисования области. Для
создания этого объекта нужно указать координаты верхнего левого угла объекта Point,
а также ш ирину и высоту. П осле этого появятся свойства Тор, Left, R i g h t и B o t t o m . И та­
кой п о л е з н ы й м е т о д к ак C o n t a i n s ( ) , в о зв р а щ а ю щ и й з н а ч е н и е tr u e , е с л и т о ч к а п о п а ­
д а ет внутр ь п р ям оугол ьника. -----------------------------

д . F i l l R e c t a n g l e (B ru s h e s . S k y B lu e , C lie n tR e c ta n g le ) ; Чуть позже эт от метод


вам пригодится! Как
вы думаете, что вам
предстоит делать с
О Н арисуем цветок и пчел у
методом СопЬат$0)?
Вы уже знаете, как работает метод D r a w l m a g e ().

д . D r a w lm a g e (P ro p e rtie s .R e s o u rc e s .B e e _ a n im a tio n _ l, 50, 20, 75, 75) ;


g . D r a w lm a g e ( P r o p e r tie s .R e s o u r c e s . F lo w e r, 10, 130, 100, 1 5 0 );

Ручки предназначены для рисования линий


и им ею т толщину. Чтобы нарисовать
заполненную форму или текст
О Д о « » в и « ручку д л . рисования fl/' е™ *
Рисование лин и й осущ ествляется при помощ и объекта Реп, задающего цвет и толщину.
В строенны й класс P e n s дает вам множ ество вариантов (например. P e n s .R e d — это тон ­
кая красная ручка). К онструктор класса Р е п позволяет создавать собственные ручки. Для
рисования залиты х ф орм прим еняю тся кисти. Класс B r u s h e s предоставляет кисти раз­
личны х цветов.
u s in g (Р еп t h ic k B la c k P e n = new P e n (B ru s h e s . B la c k , 3 .0 F )) {

606 глава 13
Это мы добавим внутрь
оператора using, создающего
объект Реп.
Д обавим указы ваю щ ую на цветок стрелку
Н екоторы е методы объекта G r a p h i c s берут массив объектов P o i n t и соединяю т их ли­
ниями или кривыми. Методом D r a w L i n e s {) нарисуем наконечник стрелы, а методом
D r a w C u r v e О —ее основу. Есть и другие методы, работающ ие с массивами точек (например,
метод D r a w P o l y g o n () рисует замкнутые формы, а метод F i l l P o l y g o n O заполняет их).
д . D r a w L in e s (th ic k B la c k P e n , new P o i n t [] {
new P o i n t (13 0, 11 0 ), new P o i n t (12 0, 1 6 0 ), new P o i n t (15 5, 1 6 3 )});
g .D ra w C u rv e (th ic k B la c k P e n , n e w P o i n t [] {
new P o i n t (12 0, 1 6 0 ), new P o i n t (17 5, 120) new P o in t(2 1 5 , 70) });

} На основе массива точек метод


Здесь заканчивается блок
using, объект thickBlackPen VrawCurveO рисует гладкую,
ч
удаляется, так как он нам соединяющую их друг с другом
дольше не нужен. кривую.

О Д обавим шрифт
П ервое, что требуется для написания текста, — создать объект Font. С нова напиш ем опе­
ратор usin g , так как он реализует и нтерф ей с I D i s p o s a b l e . Создать ш риф т просто. Суще­
ствует несколько перегруж енны х конструкторов, самый простой использует имя ш рифта,
его разм ер и п еречи слен ие F o n t S t y l e .
u s in g (F o n t fo n t = new F o n t( " A r i a l " , 16, F o n tS ty le . I t a l i c ) ) {

Д обавим текст « N e c ta r h e re »
О пределим место для строки, выяснив, какой разм ер она будет иметь на ф орм е п ри по­
мощи метода M e a s u r e S t r i n g () , возвращ аю щ его парам етр SizeF. ( S i z e F — это версия
f l o a t парам етра Size). Так как мы знаем, где находится конец стрелки, поместим над ним
центральную точку надписи.
S iz e F s iz e = g .M e a s u re S trin g ( "N e c ta r h e re ", fo n t);
g .D ra w s trin g (" N e c ta r h e re ", fo n t. B ru s h e s .R e d , new P o i n t (
215 - (in t)s iz e .W id th / 2, 70 - ( i n t ) s iz e .H e ig h t) ) ;

} _______________
} ^ ^ f 'I® forml ^
Ч f-je забудьте закрыть оба блока using.

Дія создания обьекта Rectangle требуется


точка и параметр Size ( то есть ширина
и высота). Затем вы можете опреде­
лить его границы и проверить методом
Contains ( ) , сеть ли внутри обьект Point.
дальше > 607
как это выглядит?

в руку карандаш

1. Р абота с о б ъ е кта м и Graphics в кл ю ч а е т в себя


п р е д с та в л е н и е в а ш и х о б ъ е кто в в ко о р д и н а та х X и Y.
В о т ко д д л я п о с тр о е н и я п о ка за н н о й с н и зу сетки;
вам н уж н о за п о л н и ть о тс утс тв у ю щ и е части.

u s in g (G ra p h ic s g = t h i s . C re a te G ra p h ic s О )
u s in g (F o n t f = new F o n t ( " A r i a l " , 6, F o n tS ty le .R e g u la r)) {
fo r (in t X = 0; X < th is .W id th ; x += 20) {

} .........................................................................
fo r (in t у = 0; у < th is .H e ig h t; у += 20 ) {

s к too 1ЭЭ t«3 iSQ 29Q Ш m I

2Q
2. Ч то п р о и зо йд е т при за п уске п р и ве д е н н о го ниж е ed
кода? Н а рисуй те результат на ф о р м е, используя ЇІ
д л я р а зм е щ е ни я отд ельны х то ч е к то л ько что
в и зуа л и зи р о в а н н ую сетку. ■ tie

u s in g (Р еп р е п = : lab

МЙ
new Р е п (B ru s h e s .B la c k , 3 .0 F )) {
ЖГ
g .D ra w C u rv e (p e n , new P o i n t [] {
(ёб
new P o i n t (8 0 , 6 0 ), »3

new P o in t (2 0 0 ,4 0 ),
new P o i n t (18 0, 6 0 ),
new P o in t (3 0 0 ,4 0 ),
' Ш
})
g .D ra w C u rv e (p e n , n e w P o i n t [] { @
new P o i n t (3 0 0 ,1 8 0 ), new P o i n t (18 0, 200) Wl
new P o i n t ( 2 0 0 ,1 8 0 ), new P o i n t (8 0 , 20 0 ), №

});
g .D ra w L in e (p e n , 300, 40, 300, 1 8 0 );
g .D ra w L in e (p e n , 80, 60, 80, 2 0 0 );
g .D ra w E llip s e (p e n , 40, 40, 20, 2 0 );
g .D ra w R e c ta n g le (p e n , 40, 60, 20, 3 0 0 );
g .D ra w L in e (p e n , 60, 60, 80, 6 0 );
g .D ra w L in e (p e n , 60, 200, 80, 2 0 0 );

608 глава 13
элементы управление и графические фрагменты

P o lt ы ойектоб
Ko/nt и рисует вершины, которые
3. Э то т код р а б отае т с н е п р а ви л ь н ы м и ф о р м а м и . зат ем соединяются линиями.
О п р е д е л и те , ка ко й р и су н о к со зд а е т это т код,
и в о с п р о и зв е д и те его на сетке.

g .F illP o ly g o n (B ru s h e s .B la c k , new P o i n t [] {
new P o in t(6 0 ,4 0 ), new P o i n t (1 4 0 ,8 0 ), new P o i n t (2 0 0 ,4 0 ),
new P o in t (3 0 0 ,8 0 ), new P o i n t (3 8 0 ,6 0 ), new P o i n t (3 4 0 ,1 4 0 ),
new P o i n t (3 2 0 ,1 8 0 ) new P o in t (3 8 0 ,2 4 0 ), new P o i n t (3 2 0 ,3 0 0 )
new P o i n t (3 4 0 ,3 4 0 ) new P o in t (2 4 0 ,3 2 0 ), new P o i n t (1 8 0 ,3 4 0 )
new P o i n t (2 0 ,3 2 0 ), new P o i n t (6 0 , 2 8 0 ), new P o i n t (1 0 0 , 240)
new P o i n t (4 0 , 2 2 0 ), new P o in t (8 0 ,1 6 0 ),
});

u s in g (F o n t b ig = new F o n t(" T im e s New R om an" , 24, F o n t S t y l e . I t a l i c )) {


g . D r a w S tr in g ( "P o w !" , b ig , B ru s h e s .W h ite , new P o i n t (8 0 , 80));
g . D r a w s tr in g ( "P o w !" , b ig , B ru s h e s .W h ite , new P o i n t (1 2 0 , 120))
g . D r a w S tr in g ( "P o w !" , b ig , B ru s h e s .W h ite , new P o i n t (1 6 0 , 160))
g . D r a w S tr in g ( "P o w !" , b ig , B ru s h e s .W h ite , new P o i n t (2 0 0 , 200))
g .D r a w S tr in g ( "P o w !" , b ig . B ru s h e s .W h ite , new P o i n t (2 4 0 , 240))

Щ Forml
sr Г“ иГ w rr ISP ST ST w s - !&" pr ssr w
90
16
й
IS
fSB

m-
Ш
ssr
a
Ub
ЯЗ
8^
!5S
BB
Я
Ш

дальше ► 609
выглядит прекрасно, но...

возьми в руку карандаш


Решение Вам н у ж н о б ы л о вп и са ть не д о ста ю щ и й код и на р и со в а ть
ф и гуры , с о зд а ва е м ы е д вум я д р у ги м и ф р а гм е н та м и кода.

u s in g (G ra p h ic s g = t h i s .C re a te G ra p h ic s ())
u s in g (F o n t f = new F o n t( " A r i a l " , 6, F o n tS ty le .R e g u la r)) {
fo r (in t X = 0; X < th is .W id th ; x += 20) {

Сначала мы g.PrawLine(Pens.Blackj к. О. х, this.Height);


1<арисовали ................................................................................ Мы использовали
°е-ртикальные g.DrawStnng(x.ToStringQj f. Bmshes.Black, к. О); операторы using,
'^инии и чис­ 1 ......................................................................................... гарантировав т ем
ла вдоль оси У. ‘ самым, что одъек-
Вертикальные for (int у = 0 ; у < this.Height; у += 2 0 ) { ты Graphics и Font
линии распола­ будут уничтожены
гаются вдоль у, this.Width, у); после завершения
оси X через формы.
каждые 2 0 g.PrawString(y.ToStringQ, F, Brushes.Black, О, у);
пикселов. }
] Зат ем мы нарисовали
горизонтальные линии и
числа вдоль оси X. Вы вы­
брали значение Y и начали
линию с координаты (О, у)
в левой части формы по
« Fotim l направлению к координа­
J‘ аэ « » >90 те (О, this.Width) правой
части формы.
<0

90 ;
Forml о S
ю 2«0 I2S0 а -тщ'- к -
166
120

160
т
ээо

220
Ё40

яэ
ж
кй
т
здз

»0

610 глава 13
Нарисованные наш им визу-
ализатором перекрываю­
щиеся пчелы имели ст ран­
ный вид.
Решение проблемы с прозрачностью
Давайте устраним артефакты изображения в местах, где ма­
ленькие картинки перекрываются! Решить проблему поможет
метод D raw lm age () . Мы вернемся к нашему приложению »ажнение!
Windows и отредактируем его таким образом, чтобы перекры­
вающиеся изображения не удаляли фрагменты друг друга.

Д обавьте метод D r a w B e e {), рисующий пчел на объектах G r a p h i c s . О н


использует перегруж енны й конструктор D r a w l m a g e () , которы й опреде­
ляет место и разм ер рисунка при помощ и структуры R e c t a n g l e .
p u b lic v o id DrawBee ( G r a p h i c s g . R e c ta n g le re c t) { П ерекрывающ иеся и зо ­
g .D ra w lm a g e (P ro p e rtie s .R e s o u rc e s .B e e _ a n im a tio n _ l, re c t) ; бражения пчел вы гля­
дят намного лучиле.
}
Ь-
© Это новы й о б р а б о т ч и к с о б ы т и я C l i c k . О н располагает верх­
н ий левы й угол улья вне ф орм ы , в точке ( - w i d t h , - H e i g h t ) ,
Ш Ш

р аспростран яя его на двойную ш ирину и высоту ф орм ы , и дает


возмож ность менять ее размер. Затем методом D r a w B e e () до­
бавляю тся четы ре пчелы.
p riv a te v o id F o rm l_ C lic k (o b je c t s e n d e r, E v e n tA rg s e) {
u s in g (G ra p h ic s g = C re a te G ra p h ic s 0 ) {
g . D r a w l m a g e ( P r o p e r t i e s . R e s o u r c e s . H i v e ___i n s i d e _ ,
-W id th , -H e ig h t, W id th * 2, H e ig h t * 2 );

Сначала рисуется^ S iz e s iz e = new S iz e (W id th / 5, H e ig h t / 5)


фон улья, который D ra w B e e (g , new R e c ta n g le (
выходит за границы new P o in t(W id th / 2 - 50, H e ig h t / 2 - 40), s ize )) ;
формы. Зат ем мы
рисуем четырех п е ­ D ra w B e e (g , new R e c ta n g le (
рекрывающихся пчел, new P o in t(W id th / 2 - 20, H e ig h t / 2 - 60), size )) ;
если они не перекры­ D ra w B e e (g , new R e c ta n g le (
ваются, увеличьте
new P o in t(W id th / 2 - 80, H e ig h t / 2 - 30), size )) ;
форму и снова щ елк­
ните на ней. D ra w B e e (g , new R e c ta n g le (
new P o in t(W id th / 2 - 90, H e ig h t / 2 - 80), size));
}
} Но посмот рит е, что произой­
} дет, если перет ащ ит ь пчел за
границу формы и обратно!
...НО о ста ется проблема

О Запустите программу и щ елкните на ф орм е, чтобы нарисовать


пчел. Н о если перетащ ить форму за границу экрана, а потом вер­
нуть, и з о б р а ж е н и е и с ч е з н е т ! Сделайте то же самое с картинкой
«Nectar here», программу для которой вы написали несколько стра­
ниц назад, —т а ж е п р о б л ем а !

К ак ВЫ дум аете, что происходит?

дальше ► 611
вернемся к событиям

Событие Paint С формами и элементами


Н аш а граф и ка исчезает, стоит закры ть ее, наприм ер, дру­ управления связано со­
гим окном. К счастью, этот недочет можно исправить
при помош;и о б р а б о т ч и к а с о б ы т и я P a i n t . Это собы тие бытие Paint, дающее до­
вы зы вается п ри перерисовке формы . Одним из свойств
парам етра P a i n t E v e n t A r g s является объект G r a p h ic s , ступ к объекту Graphics,
которы й собственно и рисует на элементах управления.
который автоматически
О Д обавим обработчик события Paint
Дважды щелкните на строке Paint на вкладке Events
перерисовывает все изо­
окна Properties. Событие P a i n t возникает всякий
раз, когда появляется необходимость перерисовать
бражения.
изображение.
Properties - a X
Foniil System.Windows.Forms.Form
Дважды щелкните на строке Ра'тЬ, чтобы
' \*f *
добавить к эт ому событию обработчик.
• ■>“ • ■ .
Его парамет р PaintEventArgs обладает __ ► Paint Formi_Patnt
свойством С1трк!С5. поэтому все вами Paint
нарисованное будет «приклеено» к форме.
Occur when a controf needs repaînttog.

О Воспользуйтесь объектом Graphics


В данном случае обработчик собы тий долж ен начинаться не с оп ератора u s i n g , а вот так:
p riv a te v o id F o rm l_ P a in t(o b je c t se n d e r, P a in tE v e n tA rg s e) {
G ra p h ic s g = e .G ra p h ic s ;

О бъект был создан не вами, поэтому в ы н е д о л ж н ы е г о у н и ч т о ж а т ь .

О Скопируйте код, рисую щ ий перекрывающихся пчел и цветы


Добавьте метод D r a w B e e ( ) с предыдущей страницы к новому пользовательскому элементу управ­
ления. Затем скопируйте код события C l i c k в событие P a i n t к р о м е п е р в о й ст р о ч к и с о п е р а т о ­
р о м u s i n g , ведь у вас уже есть объект G r a p h i с s , которы й называется д. Теперь запустите програм­
му Г р аф ик а о с т а е т с я н а м ес т е ! ^
Проделайте аналогичную операцию
для рисунка «N ectar here».

Эитсй себя т р е р 7 с о в ы в ^ Т в с е 7 прихо-


графикоа — метки, текст, кнопки формах являются
с Х. Каждый раз, когда форма оказывается за маленький квадратик
формой, часть, которая была ск ш т а стан1й экрана или под другой
то перестает ^^АЕИСТВИТВЛЬНОЙ,
ет перерисовку <Ьормы, вызвав с о б ы Л е Paint Т ьГ м ож е^'^й'^^'^ иницииру-
^ ^ ь перерисовку форм и ^ ^ ш ен т о вуп р а в% ^ 1 Г ь ^ м Т ^ ^ ^ ^ ^
элементы управление и графические фрагменты

Используйте полученные знания о формах и элементах управления, чтобы попрактиковаться


в использовании объектов B i t m a p и метода D r a w l m a g e {). Создайте пользовательский
;н е н и е элемент управления, который с помощью элемента т г а с к в а г з меняет масштаб изобра­
жения.

Д обавим два элем ента Т г а с к В а г


О ткр о й те новы й проект W indows A pplication. Д о б а в ь т е э л е м е н т U s e r C o n t r o l —п ри ­
свойте ему имя Zoomer, а свойству S i z e - значение (300, 300). С оздайте два элемента
Т г а с к В а г —первы й внизу, а второй справа. Свойству O r i e n t a t i o n второго элемента
п рисвойте зн ачение V e r t i c a l . С войство M i n i m u m обоих элементов долж но равнять­
ся 1, M a x i m u m - 1 7 5 , V a l u e - 175, а T i c k S t y l e - None. Д ля ф он а вы берите белый
цвет. Добавьте элементам обработчик собы тия S c r o l l и заставьте оба обработчика
вы зы вать метод I n v a l i d a t e ().

Ваш пользовательский элемент


управления связан с событием Paint
и работает совершенно аналогично
элементу, который вы только что
применяли в форме. Воспользуйтесь Ломеститг по л­
зунки на белый (рон.
параметром е свойства Paint­ 13 эплом случае они
EventArgs. С ним связано свойство сольются с белым
Graphics, и все нарисованное при прямоугольником,
помощи объекта Graphics окажется который вы нарису­
на экземпляре элемента управления, ете о качестве ф о­
нового изображения.
который вы перетащили
из окна Toolbox.

О З агрузите изображение для элем ента управления в объект B itm a p


Д обавьте поле p h o t o объекта B i t m a p к элементу Zoomer. Используйте конструктор
экзем пляра B i t m a p для загрузки лю бимого изображ ения. Добавьте к элементу собы ­
ти е Paint. О бработчик сформ ирует граф и ческий объект, заполнит ф он белым цве­
том и затем с помощью метода D r a w l m a g e ( ) нарисует содерж им ое поля phot o , по­
местив его верхний левы й угол в точку (10, 10). Его ш ирина определяется парам етром
t r a c k B a r l .Value, а вы сота — t r a c k B a r 2 .Value. П еретащ ите элемент на форму.

Когда вы перемещайте ползунки,


элементы ТгаскВаг вызывают
Дбигдя метод Invalidate О . Ваш элемент
Ты меняете размер управления вызывает событие
изображения:
Paint и меняет размер фотографии.

дальше > 613


как работает событие paint

Еще немного попрактикуемся в использовании

ажнение объектов B i t m a p и метода D r a w l m a g e ().

І- ^ е ш е н и е
Построим форму, которая загружает
изображение из файла, а потом меняет его
о л ? ““
масштаб.
ширину « » « « « 6
public partial class Zoomer : UserControl {

Bitmap photo = new Bitmap(@"c;\Graphics\fluffy_dog.jpg");

public Zoomer0 { Выберите собственный файл — конструктор


Bitm ap работает с множеством форматов. По­
InitializeComponent 0 ; пробуйте воспользоваться методом OpenFileDialog
} для выбора подходящего изображения.

private void ZoomerPaint(object sender, PaintEventArgs e) {


Graphics g = e.Graphics;
g.FillRectangle(Brushes.White, 0, 0, Width, Height);
g.Drawlmage(photo, 10, 10, trackBarl.Value, trackBar2.Value);
^ Мы нарисовали больилой белый прямоугольник, заполнивший весь
элемент управления, поверх него было добавлено фото. Последние
два параметра определяют размер изображения: tra ckB a rl задает
ширину, а trackBarZ — высоту.
private void trackBarl_Scroll(object sender, EventArgs e) {
Invalidate();
}
private void trackBar2_Scroll(object sender, EventArgs e) {
Invalidate 0 ; Каждый раз, когда пользователь начинает перемещать
■> К ползунок, вызывается событие Scroll. Обработчики этого
■* события вызывают метод InvalidateÇ) элемента управления,
что приводит к перерисовке содержимого формы... при этом
новая копия изображения имеет другой размер.

Перетаскивание ползунка вызывает изменение р а з-


изображения при помощи метода DrawlmageQ.

д. Drawlmage(myBitmap, 30, 30, 150, 150);

614 глава 13
элементы управление и графические фрагменты

Перерисобка форм и элементов управления


Ча сДеной
Как уже было сказано, начав работать с объектами G r a p h i c s , вы получили
контроль над граф икой. Вы как бы говорите .NET «Эй, я отвечаю за свои дей­
ствия». А что делать, если вы не хотите п ерерисовки ф орм ы при ее сверты вании и разверты вании...
или наоборот, хотите, чтобы ф орм а обновлялась чаще? Разобравш ись, что происходит при п ерери сов­
ке, вы см ожете управлять процессом.

^ Каждая цю рма связана с собы тием Paint


О ткройте список собы тий для лю бой ф орм ы , и вы обнаружите
в нем собы тие P a i n t . О но вы зы вается, если нужно п ерери совать проделывали
форму. Н о каким образом? П ри помош;и метода O n P a in t , унаследо- такую операцию для
ванного от класса C o n t r o l . (O n в начале имени метода означает, что метода DisposeQ.
метод вызывает событие.) П ерекройте метод O n P a i n t :

М ет о^^‘^'^^ p ro te c te d o v e rrid e v o id O n P a in t ( P a in tE v e n tA r g s e) {
O nPaint для -— — ^Console. WriteLine ("OnPaint {O} {l}", DateTime .Now, e.ClipRectangle) ;
ЛН?(5ой ф о р м ы b a s e .O n P a in t (e) ;
и добавьте }
э т у ст року.
Подвигайте форму за границы экрана, сверните ее, спрячьте под другим окном и посмотрите на
окно вывода. Вы увидите, что метод O n P a i n t вызывает событие P a i n t каждый раз, когда ф ор ­
ма становится н ед ей ств и тел ьн о й и нуждается в перерисовке. П араметр C l i p R e c t a n g l e - это
прямоугольник, описывающ ий ставшую недействительной часть формы. О н передается свой­
ству P a i n t E v e n t A r g s собы тия P a i n t и увеличивает производительность, так как перерисовке
подвергается только та часть ф ормы , которой это действительно нужно.

О И спользуйте м етод I n v a l i d a t e о
Если скрыть, а потом снова сделать видимой часть ф орм ы , .NET вы­
зы вает собы тие Paint, которое вы зы вает метод I n v a l i d a t e () , и Метод InvalidateQ
передает ему парам етр R e c t a n g l e , указываюш;ий, какая часть фор- по ф орм ы
мы долж на быть перерисована. П осле чего .NET вы зы вает метод ^^дейст вит ель-
O n P a i n t , инициируюш,ий собы тие P a i n t ф орм ы , и недействитель-
пая область перерисовы вается. J V.

© М етод U p d a t e ( ) помещ ает запрос I n v a l i d a t e наверх


Ф орма все время получает сообщ ения. В системе, вызы ваю щ ей метод Вызвав
O n P a i n t , когда поверх ф орм ы оказы вается другой объект, существуют ваша
и другие сообщ ения. Н апечатайте o v e r r i d e и посм отрите список мето- ^ли элем ен т
дов, начинаю щ ихся с О п, каждый из них сообщ ает что-то ф орм е. М етод уу^равления недей -
U p d a t e () помещ ает сообщ ение метода Invalidate в самый верх списка, ^твительны и должны
быть перерисованы.
_ МетоЭм можно п ер е-
d М ето д R e f r e s h О как сум м а методов i n v a l i d a t e () и U p d a t e О прямоугольную
Формы и элементы управления обладают методом R e f r e s h ( ), которы й Q^/^acmb — она будет
сначала вызывает метод I n v a l i d a t e ( ) , объявляю щ ий недействитель- передана и п а р д м е ^ р у
ной всю занятую граф икой область, а затем U p d a t e ( ) , гарантирующий PaintEventArgs со
сообщению метода Invalidate самый вы сокий п риоритет. '

дальше ► 615
что это за мерцание?
Часто»
^аД аБ аеМ ы е
БоЦ|=>ос;ь1

без метода D i s p o s e { ) . Его использова­


Мне кажется, что менять раз­ То есть объект Graphics умеет ние гарантирует оператор u s i n g . В итоге
мер изображения пунше в Paint или не только рисовать на форме? высвобождение ресурсов осуществится
Photoshop. Я неправ? средствами .NET Для всех объекгов,
Q ; Этот объект рисует на любой созданных при помощи оператора u s i n g ,
Ql Вы можете так поступить, при структуре, которая в состоянии его по­ метод D i s p o s e () автоматически
условии, что именно вы контролируете родить. Объект B i t m a p дает вам объекг вызывается в конце блока. Именно это
изображение и что оно больше не будет G r a p h i c s , при помощи которого вы ри­ гарантирует, что ваша программа по мере
менять размер. Но в большинстве случа­ суете на невидимом фоне. Этот фон может работы не начнет занимать в памяти все
ев вы получаете графический фрагмент располагаться не только на формах. Пере­ больше и больше места.
из стороннего источника, например, от тащите на форму кнопку, перейдите к коду
коллеги-дизайнера. В этом случае его раз­ и введите имя и точку. В окне IntelliSense Для создания элементов управ­
мер приходится менять внутри кода. вы увидите метод C r e a t e G r a p h i c s ( ) , ления лучше пользоваться классом
возвращающий объекг G r a p h i c s . Все, что UserControl или наследованием
Но не лучше ли менять размер изо- вы нарисуете на этом объекте, окажется на от одного из встроенных элементов
I ражения вне .NET? кнопке! Аналошчно для элементов L a b e l , Toolbox?
P i c t u r e B o x , S t a t u s S t r i p . . . практи­
чески все элементы в окне Toolbox обладают
Q lfla , еспи вы уверены, что вам никогда ^ I Это зависит от назначения вашего
объектом G r a p h i c s .
не потребуется рисунок большего раз­ элемента управления. Если его функции
мера. Уменьшать изображение намного сходны с функцией уже существующего
проще, чем увеличивать его. 3 * Я думал, что оператор using ис­ в Toolbox элемента, используйте на­
В большинстве случае лучше менять раз­ пользуется при работе с потоками. За­ следование. Но в большинстве случаев
мер графического фрагмента программ­ чем он нужен при работе с графикой? приходится иметь дело с полностью
ными средствами. В этом случае вам не пользовательскими элементами управле­
придется сталкиваться с ограничениями, Q j Ключевое слово u s i n g применя­ ния. Зато вы можете перетаскивать на
накладываемыми внешними программами, ется не только для потоков, но и для них элементы Toolbox, созданный вами
например, получая файлы, предназначен­ любого класса, реализующего интерфейс элемент прекрасно справляется с ролью
ные только для чтения. I D i s p o s a b l e . Создав экземпляр контейнера.
такого класса, вы должны вызвать метод
Метод CreateGraphics О D i s p o s e ( ) , как только работа с объек­
использует объект Graphics для том завершится. В случае потоков метод
рисования на форме, а зачем метод D i s p o s e ( ) гарантирует закрытие всех
Fromlmage () вызывает метод открытых файлов.
Resizelmage О ? Объекты G r a p h i c s , Р е п И B r u s h Пользовательский
таюке подлежат удалению. Они требуют
I F r o m l m a g e () восстанавливает места в памяти и других ресурсов и не
всегда освобождают их. Когда вы рисуете
элемент управле­
ъект G r a p h i c s для объекта B i t m a p .
И так же как вызванный для формы метод
C r e a t e G r a p h i c s ( ) , возвращает рису­
что-то один раз, это не имеет особого зна­
чения. Но в большинстве случаев код, ра­
ния может служить
ющий на этой форме объекг G r a p h i c s ,
метод F r o m l m a g e ( ) восстанавливает
ботающий с графикой, вызывается снова
и снова, например обработчиком события
контейнером для
P a i n t он может вызываться много раз
объект G r a p h i c s для рисования на объ­
екте B i t m a p . в секунду. В таких случаях не обойтись других элементов.

616 глава 13
элементы управление и графические фрагменты

Я зам етил ка ко е -то м е р ц а н и е в о кр у г м о е го


элем ента уп р а вл е ни я Zoom er. Вы с то л ько
го в о р и л и о ко н тр о л е над гр а ф и ко й , ч то я уверен,
вы знаете, как реш ить эту м а л е н ь ку ю
пробл ем у.

Рисование изображения на форме


занимает время, даже если размер
изображения не меняется.
А представьте, что в симуляторе ме­
няется разм ер каждого изображ ения.
Рисование всех этих пчел, цветов и са­
мого улья требует времени, и когда вы
видите конец процесса визуализации,
вы восприним аете его как м ерцание.
П роблем а состоит в слишком большом
объеме рисуемой граф ики. Такая же
проблем а встречается и в некоторы х
лю бительских ком пью терны х играх.

1
UTVPM
Как избавиться от этого м ерцания? Что делать, если
вы д олж ны нарисовать на ф орм е м нож ество граф и­
че ски х ф р агм ентов? В едь им енно это является при­
чиной.

Отмасш табированная графика будет выглядеть лучше, если перед вы зовом метода
D r a w l m a g e () объекта G raphics свойству InterpolationlWlode этого объекта присвоить
значение i n t e r p o l a t i o n M o d e . H i g h Q u a l i t y B i c u b i c . (В верхню ю часть кода при этом
нужно добавить строку u s i n g S y s t e m .D r a w i n g .2 D ;)

дальше > 617


сглаживание анимации

двойная буферизация
В ернитесь к масштабируемому изображ ению и подвигайте ползунки. О братили внимание на ноявляю-
ш,ееся мерцание? Д ело в том, что п ри каждом перемеш;ении ползунка обработчик собы тия P a i n t ри ­
сует белый прямоугольник, а уже на нем - изображ ение. Эта процедура происходит несколько раз в
секунду и им енно она и нтерпретируется человеческим глазом как мерцание. И збеж ать м ерцания мож­
но с помощью д во й н о й б у ф ер и зац и и (d o u b le b u fferin g ). Каждый кадр или ячей ка анимации сначала
рисую тся в буфер, и новы й кадр отображ ается только после его полной прорисовки. Рассмотрим п рин ­
цип работы этой техники на прим ере объекта Bitm a p .

О Вот типичная программа, рисующая на ф орм е при помощ и объекта Graphi сs.

© Добавим в программу объект B i t m a p , которы й сы грает роль буфера. Теперь именно на


нем, а не непосредственно на ф орм е будут рисоваться граф ические фрагм енты.

Теперь, когда изображение


u s in g (g ra p h ic s g = рисуется на невидимом
D-, ......... объ-
UUO
G r a p h ic s . F ro m lm a g e (b itm a p )) { ект.е Bitm ap, пользователи
D ra w O n e F ra m e (g ); перест анут наблюдать
мерцание. Им сразу пред­
ставится окончательный
^^<о^ирован-
ньш на форму из буфера

О М етод D r a w I m a g e U n s c a l e d () скопирует рисунок с объекта B i t m a p н а объект G r a p h i c s


ф ормы . К опирование п роизой дет мгновенно, что избавит нас от проблемы мерцания.

Объект
System.V'A*'

618 глава 13
элементы управление и графические фрагменты

двойная буферизация Встроена В формы


U элементы управления
Используя со­
Включить двойную буферизацию можно вручную с помощью объекта
Bitm a p , но в C# и .NET существует встроенная поддержка этой функ­
бытие Paint при
ции. Д о с т а т о ч н о п р и с в о и т ь с в о й с т в у D o iib le B u f f e r e d зн а ч е н и е
tr u e . Выделите ваш элемент управления Zoomer, в окне Properties при­
работе с графи­
свойте свойству D o u b l e B u f f e r e d значение true, и мерцание прекра­
тится! П р о д е л а й т е э т о и д л я э л е м е н т а B e e C o n t r o l . Всех проблем это
кой, вы можете
не решит, но разницу вы увидите сразу.
включить двой­
Д ля решения проблем с графикой в нашем симуляторе все готово!
ную буфериза­
Капитальный ремонт симулятора цию, изменив
в следующем упраж нении вы полностью п ерестроите симулятор. Н а­
верное, имеет смысл создать новы й п роект и воспользоваться коман­ всего одно свой­
дой A dd » Existing Item ... для добавления всех необходимы х файлов.
(Н е забудьте отредактировать пространство имен!) ство.
Вот что мы будем делать:

Q У д а л и м пользовательский элем ент управления B e e C o n t r o l


Н а поле и в улье не должно остаться элементов управления. Пчел, цветы и улей мы нарису­
ем средствами GDI-t-. Поэтому щелкните правой кнопкой мыши на строчке B e e C o n t r o l .es
в окне Solution E xplorer и вы берите команду Delete.

О Д ля управления пчелины м и крыльями вам потребуется тайм ер


П челы двигаю т кры льями намного медленнее, чем обновляю тся кадры симулятора, по­
этому вам потребуется более медленный счетчик. Это неудивительно, ведь элемент
B e e C o n t r o l был снабжен для этой цели встроенны м таймером.

О Реконструкция визуализатора
Вам больше не понадобятся словари, так как элементов P i c t u r e B o x и B e e C o n t r o l уже нет.
Вместо них фигурирует метод D r a w H i v e ( g ) , рисующий форму H i v e на объекте Graphics,
и метод D r a w F i e l d ( g ) , рисующий форму Field.

И напосл ед ок, по д кл ю чи м новый визуализатор


Формам H i v e и F i e l d нужны обработчки собы тия Paint. Каждый из них вы зы вает ме­
тод D r a w F i e l d (g) или D r a w H i v e (g) объекта R e n d e r e r . Таймеры же вызы ваю т метод
I n v a l i d a t e () , обновляю щ ий содерж им ое форм. П ри этом связанны е с ними обработчи­
ки собы тия P a i n t будут визуализировать кадр. I
уаЧнем ! ------------------------------- ►

дальше ► 619
перестраиваем визуализатор

Устраним проблемы с графикой. Воспользуемся объектами СгарИ1с8 и двойной


;нение буферизацией.

О тред актируем м етод R unFram e О основной <рорми


Нужно убрать вызов метода R e n d e r e r . R e n d e r () и добавить два оп ератора I n v a l i d a t e ()

p u b l i c v o id R u n F ra m e (o b je c t s e n d e r, E v e n tA rg s e) {
fra m e s R u n + + ; Уберем, вызов renc^erer.Ren^^er()^
w o r l d . Go ( ra n d o m ) ; так как эт от метод нам
end = D a te T im e .N o w ; больше не нужен.
T im e S p a n fra m e D u ra tio n = end - s ta rt;
s ta rt = end;
U p d a te S ta ts (fra m e D u ra tio n ) Sb( регулярно обновляете м ир, а обе
h iv e F o r m .In v a lid a t e 0 ;
формы им ею т ссылку на визуализатор,
значит нужно всего лишь анимировать
f ie ld F o r m .In v a lid a t e 0 ; их, оызвав соответствуюи^ие методы
InvalidateQ. Об остальном позаботятся
их обработчики события Paint.

О Д обавим к основной ф орме второй тайм ер


П рисвойте свойству I n t e r v a l нового тай м ера зн ачение 150ms, а свойству E n a b le d - зна­
чен ие true. Д войны м щ елчком добавьте обработчик событий;
p riv a te v o id tim e r2 _ T ic k (o b je c t s e n d e r, E v e n tA rg s e) { '
re n d e re r.A n im a te B e e s ( ) ;
}

Добавьте метод A n im a te B e e s (), управляю щ ий движ ением кры льев пчел:


p riv a te in t c e ll = 0;
p riv a te in t fra m e = 0; Нужно задать значения поля
p u b lic v o id A n i m a t e B e e s () Cell, чтобы пользоваться им для
fra m e + + ; рисования пчел в визуализаторе.
if (fra m e >= 6) BeeAnimationLargelCell] должно
fra m e = 0; отображаться в форме Hive,
S w itc h (fra m e ) { а BeeAnimationSmall[Cell] —
case 0: c e ll 0; b re a k
в форме Field. Таймер будет
case 1: c e ll
все время вызывать метод
1; b re a k
AnimateBeesQ, меняюш,ий
case 2 : c e ll 2; b re a k состояние поля Cell. В результ ат е
case 3 : c e ll 3; b re a k крылья пчел начнут дв^игаться.
case 4: c e ll 2; b re a k
case 5: c e ll 1; b re a k
d e fa u lt: c e ll = 0 b re a k Если ваши пчелы летят не туда,
} проверьте правильность ввода
h i v e F o r m . I n v a l i d a t e () координат! Для определения координат
fie ld F o rm .In v a lid a te ( пользуйтесь событием M o u s e C l i c k из
} предыдущей главы.

620 глава 13
элементы управление и графические фрагменты

О fOpMOM H ive и Field требуется откры тое свойство R e n d e r e r


Д обавьте свойство общ его доступа R e n d e r e r к обеим формам:
, , ______ А оёавьт е эт о свойст во к обеым
public Renderer Rend e r e r { get; set; } 'форМаМ-
He за - f О т*,^
р е д а к тXи р у ЖЛ.
й тж ^е о б ъ я в -л—
е— класса R e n d e r e r следующим
н и е -------
----- V образом;
м. public clas
5ydt>me
бадЬ т е\ ^ _ _______________________ Г.Т _______
R e n d e r e r . Т о ж е с а м о е нужно сделать для классов W o r l d , H i v e , B e e и F l o w e r и для п ер е­
доба­
вить ч ислени я Beestate.
эти
моди­ Э кземпляр Renderer {) создается в коде кнопки O p en и в методе ResetSimulator ().
ф ика­
торы У дал и те в с е в ы з о в ы м е т о д а r e n d e r e r - R e s e t О . Затем обновите конструктор объекта
до­ Renderer, чтобы задать свойство Renderer для обеих форм:
ступа! = th is ;
h iv e F o rm .R e n d e re r Метод ResetQ удалял с формы элементы
fie ld F o rm .R e n d e re r = th is ; управлений. HO теперь ему нечего удалять.
Настройте двойную буцїеризацию цюрм H ive и Field
Удалите из конструктора формы Hive код задания фонового изображения. Затем удалите все
элементы управления из обеих форм и п р и с в о й т е с в о й с т в у D o u b le B u f f e r e d зн а ч е н и е t r u e .
Добавьте к формам обработчик собы тия Paint. Он записан для формы Hive, для формы Field
он отличается вызовом R e n d e r e r .P a i n t F i e l d {) вместо R e n d e r e r .P a i n t H i v e ():
p riv a te v o id H iv e F o rm _ P a in t(o b je c t s e n d e r, P a in tE v e n tA rg s e) { J
R e n d e re r. P a in tH iv e (e . G ra p h ic s ) ; ggsдвойной буферизации ваши
} формы будут мерцать!

О У д а л и м из визуализатора код на основе элементов управления и добавим граф ику


Вот что вам нужно сделать:
★ Удалите словари. Также вам больше не понадобятся элемент BeeControl и методы
R e n d e r (),D r a w B e e s () и D r a w F l o w e r s ().
★ Д ля хран ен и я изображ ений добавьте поля В і t m a p с именами H i v e I n s ide. H i v e O u t s i d e
и Flow e r . Создайте два массива B i t m a p [] B e e A n i m a t i o n L a r g e и B e e A n i m a t i o n S m a l l .
В каждом из них будут хранится по четы ре изображ ения пчелы: больш ие 40x40 и ма­
ленькие 20x20. Создайте метод I n i t i a l i z e l m a g e s О , масштабирующий ресурсы, по­
мещающий их в эти ПОЛЯ и вызы ваю щ ий их из конструктора класса R e n d e r e r .
★ Добавьте метод P a i n t H i v e О , которы й берет в качестве парам етра объект G r a p h i c s
и рисует на нем улей. Н ачн ите с голубого прямоугольника, затем методом D r a w -
I m a g e U n s c a l e d () нарисуйте улей изнутри, а методом D r a w I m a g e U n s c a l e d () - нахо­
дящ ихся внутри улья пчел.
★ Добавьте метод P a i n t F i e l d ( ) , рисую щ ий голубой прямогоульник в верхней части
ф орм ы и зелены й — в ниж ней. Свойства C l i e n t S i z e и C l i e n t R e c t a n g l e укажут раз­
мер каждой области. М етодом F i l l E l l i p s e {) нарисуйте ж елтое солнце, методом
D r a w L i n e () - ветку, с которой свисает улей, а методом D r a w I m a g e U n s c a l e d {) - вид
улья снаружи. Н арисуйте цветы, а перед ними - пчел (используйте для этого маленькие
картинки).
★ П о м н и т е , что метод A n i m a t e B e e s {) задает зн ачение поля cell.

дальше ► 621
решение упражнения

Избавимся от артефактов графики в нашем симуляторе улья. Используйте двойную


, 'ажнение буферизацию, чтобы сделать изображение четким.

решение Это готовый класс Renderer, включающий


--------- метод AnimateBeesQ. Убедитесь, что
u s in g S y s te m .D ra w in g ; вы отредактировали все три формы,
особенное внимание уделите обработчикам
p u b lic c la s s R en d e re r { события Paint для форм Hive и Field. Эти
p riv a te W o rld w o r ld ; обработчики вызывают методы PaintHiveQ
p riv a te H iv e F o rm h iv e F o r m ; и PaintFieldQ визуализатора, которые
p riv a te F ie ld F o rm fie ld F o r m ;
и выполняют всю анимацию.

p u b l i c R enderer( W o r ld T h e W o r ld , H iv e F o rm h iv e F o rm , F ie ld F o rm fie ld F o rm ) {
t h i s . w o r l d = T h e W o rld ;
t h is . h iv e F o r m = h iv e F o rm ; * He забудьте внести изменения в файл
t h i s . fie ld F o rm = fie ld F o rm ; Renderer.cs, объявив класс Renderer, как
fie ld F o rm .R e n d e re r = t h is ; public. Проделайте аналогичную опе-
h iv e F o rm .R e n d e re r = t h i s ;
рацию для классов World, Hive, Flower
In itia liz e lm a g e s {);
и Bee, в противном случае вы получите
ошибку построения. у'^игле

p u b l i c s t a t i c B i t m a p R e s i z e lm a g e ( I m a g e I m a g e T o R e s i z e , in t W id th , in t H e ig h t) {
B itm a p b itm a p = new B itm a p ( W id th , H e ig h t ) ;
u s in g (G ra p h ic s g ra p h ic s = G r a p h ic s . F ro m lm a g e (b itm a p )) {
^ g ra p h ic s .D ra w lm a g e (Im a g e T o R e s iz e , 0, 0, W id th , H e ig h t)

re tu rn b itm a p ;
} “ сохийняет их в полях
Bitm ap обьекта Renderer. В результ ат е
B itm a p H iv e ln s id e ; метоЬы PaintHiveQ и PaintFormQ
B itm a p H iv e O u ts id e ; 1 ^ ^ ^ ° ^ ^ о с т ь рисовать
B itm a p F lo w e r; немасштабированные изображения при
B i t m a p [] B e e A n im a tio n S m a ll; омощи методов PrawlmaaeUnscaledf)
обьекта Graphics формы.
B i t m a p [] B e e A n im a t io n L a r g e ;
p r i v a t e v o i d I n itia liz e lm a g e s 0 {
H i v e O u t s i d e = R e s i z e l m a g e ( P r o p e r t i e s . R e s o u r c e s . H i v e ___o u t s i d e _ , 8 5 , 1 0 0 ) ;
Flower = R e s i z e l m a g e ( P r o p e r t i e s .R e s o u r c e s .Flower, 75 , 75); ~
H iv e ln s id e = R e s iz e lm a g e (P ro p e rtie s .R e s o u rc e s .H iv e in s id e
h iv e F o r m . C lie n t R e c t a n g le .W id th , h iv e F o r m . C lie n t R e c t a n g le .H e ig h t ) ;
B e e A n im a tio n L a rg e = new B it m a p [ 4 ] ;
B e e A n i m a t i o n L a r g e [ 0] = R e s i z e l m a g e ( P r o p e r t i e s . R e s o u r c e s . B e e _ a n i m a t i o n 1, 40, 40)
B e e A n i m a t i o n L a r g e [ 1] = R e s i z e l m a g e ( P r o p e r t i e s . R e s o u r c e s . B e e _ a n i m a t i o n ~ 2! 40, 40)
B e e A n im a t io n L a r g e [2 ] = R e s iz e lm a g e ( P r o p e r t ie s . R e s o u r c e s . B e e _ a n im a t io n ~ 3’ 40, 40)
B e e A n im a t io n L a r g e [3 ] = R e s iz e lm a g e ( P r o p e r t i e s . R e s o u rc e s . B e e _ a n im a tio n ~ 4 ,' 4 0 , 40)
B e e A n im a tio n S m a ll = new B it m a p [ 4 ] ; “
B e e A n im a tio n S m a ll[0 ] = R e s iz e lm a g e ( P r o p e r t ie s . R e s o u rc e s . B e e _ a n im a tio n _ _ l, 20, 20)
B e e A n im a tio n S m a l l [ 1] = R e s i z e l m a g e ( P r o p e r t i e s . R e s o u r c e s . B e e _ a n i m a t i o n _ 2 ,' 20, 20)
B e e A n im a tio n S m a l l [2 ] = R e s iz e lm a g e (P r o p e r t i e s . R e s o u rc e s . B e e _ a n im a tio n _ 3, 20, 20)
B e e A n im a tio n S m a ll[3 ]
= Resizelmage(Properties.Resources.Bee_animation 4' 2 0 , 2 0 )

622 глава 13
элементы управление и графические фрагменты

p u b l i c v o i d PaintHive ( G r a p h i c s g ) {
g .F illR e c ta n g le (B ru s h e s .S k y B lu e , h iv e F o rm .C lie n tR e c ta n g le );
g .D ra w Im a g e U n s c a le d (H iv e ln s id e , 0, 0 ) ;
f o r e a c h (B e e b e e i n w o r ld . B e e s ) {
if (b e e . In s id e H iv e )
g .D ra w Im a g e U n s c a le d (B e e A n im a tio n L a rg e [c e ll],
b e e .L o c a tio n .X , b e e .L o c a tio n .Y ) ;

^ Свойсмво формы ClientSize — ЭМ0 объект


J Rectangle, определяющий размер предназна-
. , т, •
p u b l i c v o i d PaintField ( G r a p h i c s g) 1
I ченной для рисования
^ области. j /
u s i n g (V P----
e n ----------
b ro w n P e n = new P e n ( C o lo r . B ro w n , 6 . OF)) { ^
g . F illR e c t a n g le ( B r u s h e s . S k y B lu e , 0, 0,
fie ld F o rm .C lie n tS iz e .W id th , fie ld F o rm .C lie n tS iz e .H e ig h t / 2 )
g . F ill E lli p s e ( B r u s h e s . Y e ll o w , new R e c ta n g le F (5 0 , 15 , 70 , 7 0 ) ) ;
g .F illR e c ta n g le (B ru s h e s .G re e n , 0, fie ld F o r m .C lie n tS iz e .H e ig h t / 2,
fie ld F o rm .C lie n tS iz e .W id th , fie ld F o rm .C lie n tS iz e .H e ig h t / 2)
g .D ra w L in e (b ro w n P e n , new P o i n t (5 9 3 , 0 ) , new P o i n t (5 9 3 , 3 0 ) ) ;
g .D ra w Im a g e U n s c a le d (H iv e O u ts id e , 5 5 0 , 2 0 ) ;
fo r e a c h (F lo w e r f l o w e r i n w o r ld .F lo w e r s ) {
g . D r a w I m a g e U n s c a le d ( F l o w e r , f l o w e r . L o c a t i o n . X , f l o w e r . L o c a t i o n . Y)

f o r e a c h (B e e b e e i n w o r l d . B e e s ) {
if ( !b e e . In s id e H iv e )
g .D ra w Im a g e U n s c a le d (B e e A n im a tio n S m a ll[c e ll],
b e e . TL o c a t i o^ nVI . X"V , b i—
e^ Лe . TL Лo c a ^t i o n . Y )

}
У К Метод ^ координатам р и с у -
в классе WoИd ^ не5о и земная
е т поле. Сначала Р “ ^ д м е л е эт о го
[ поверкност ь, зат ем
p riv a te in t c e ll = 0;
p riv a te in t fra m e = 0;
^ улей. ^ .. .„ в а н и я о ёье к т о в и м е ет
1^елы. нарисовать пчел
p u b lic v o id A n im a te B e e s 0 {
fra m e + + ; г;«“ » . " ™
if ( f r a m e >= 6)
fra m e = 0;
s w itc h (fra m e ) {
case 0 : c e ll 0; b r e a k ;
c a s e 1: c e l l 1; b r e a k ;
case 2 : c e ll 2; b r e a k ;
case 3 : c e ll 3; b r e a k ;
case 4: c e ll 2; b r e a k ;
case 5: c e ll 1; b r e a k ;
d e fa u lt : c e ll = 0; b r e a k ;
3. и заилем ^»ова Z заилем z . затолл
h iv e F o rm .In v a lid a te О ;
f i e l d F o r m . I n v a l i d a t e ()

дальше > 623


вывод графических фрагментов

Выбод на печать
М етоды объекта G r a p h i c s , которы м и вы пользовались для рисования
на ф орм е, п о д х о д ят и д л я в ы в о д а н а п еч ать. И нструменты .NET для % %
этой процедуры находятся в пространстве имен S y s t e m . D r a w i n g .
P r i n t i n g . Вам нужно только со зд ать о б ъ е к т P rin tD o c u m e n t. С ним а п е Ч а щ а й т е
связано собы тие P r i n t P a g e , которое прим еняется так же, как и со­
бы тие T i c k таймера. Затем требуется вы звать метод P r i n t () этого э т о
объекта и распечатать документ. Вот как это выглядит.

Г '
С о зд ай те п р и л о ж е н и е W indow s и добавьте к ф орм е кнопку В верхню ю часть кода
ф орм ы добавьте строку u s in g S y s te m .D ra w in g .P rin tin g , Дважды щ елкните на
кнопке и введите код. Вот что появится, когда вы введете +=:
private v o i d buttonl_C l i c k ( o b j e c t sender, EventArgs e) {
PrintDocument document = ne w P r i n t D o c u m e n t {);
do c u m e nt.PrintPage +=
j~7i ^ P r l n t P a g e E v e r r t H a n d l e r ( d o c u m e n t . P r l n t P B g e ) r ^ Т р г ё з Г т А В t o in s e T t)

о Наж мите Tab, и нужный код появится автоматически. И м енно так вы добавляли
обработчики собы тий в главе 11:
private v o i d buttonl_C l i c k ( o b j e c t sender, EventArgs e) {
PrintDocument document = n e w P r i n t D o c u m e n t ();
d o c u m e n t .PrintPage += n e w PrintPageEventHandler(document_I»rlntPage);
P f e s s TAB t o g e n e r a t e h a n d l e r 'd o c u m e n tjF rin tp ia B e * i n t h l s ~ e i a ^

О ИСР сгенерирует метод обработки собы тий и добавит его к ф орме.


Вместо исключения
8ы можете ввести
vo i d d o c u m e nt_PrintPage (object sender, P rintPageEventArqs e) f код обработки
thr o w n e w N o t l m p l e m e n t e d E x c e p t i o n О ; графики... что именно
, напечатать в данном
^ /V .______________ случае, вы увидите
П арам етр е свойства P r i n t P a g e E v e n t A r g s обладает свойством Graphics^. Заме­
н ите последнюю строку кодом, вызываю щ им методы объекта е .G r a p h i c s .

Заверш ите обработчик собы тия b u t t o n l _ C l i c k , вызвав метод d o c u m e n t.


P r i n t О . П ри этом объект P r i n t D o c u m e n t создает объект G r a p h i c s и, взяв его
в качестве парам етра, вы зы вает собы тие P r i n t P a g e . Все, что обработчик собы тия
рисует на объекте G r a p h i c s , передается на принтер.
priv a t e v o i d buttonl_C l i c k ( o b j e c t sender, EventArgs e) {
PrintDocument document = n e w P r i n t D o c u m e n t ();
d o c u m e n t .PrintPage += n e w P r i n t P a g e E v e n t H a n d l e r ( d o c u m e n t _ P r i n t P a g e ) ;
d o c u m e n t .P r i n t ();
}

624 глава 13
элементы управление и графические фрагменты

Окно предварительного просмотра и окно диалога Print


Д обавление окна предварительного просм отра или окна диалога P rin t напо­ При наличии объекта
м инает процедуру добавления окон диалога O p en и Save. Вам нужно создать P rin tD ocu m en t и со-
объект P r i n t D i a l o g или P r i n t P r e v i e w D i a l o g , п рисвоить его свойству отве.тст6уюш,его
D o c u m e n t ваш объект D o c u m e n t и вы звать метод S h o w (). Документ на пе­
обработчика событии
для вызова окна пред­
чать отп равит окно диалога, вам не потребуется вы зы вать метод P r i n t (). варительного про­
Добавим его к кнопке, созданной вами на шаге 1; смотра достаточ­
но создать объект
p riv a te v o id b u tto n l_ C lic k (o b je c t s e n d e r, E v e n tA rg s e) { PrintPreviewD ialog.
^ P rin tD o c u m e n t d o c u m e n t = new P r in tD o c u m e n t( ) ;
^ d o c u m e n t.P rin tP a g e += new P r in tP a g e E v e n tH a n d le r(d o c u m e n t_ P rin tP a g e );
P rin tP re v ie w D ia lo g p re v ie w = new P rin tP re v ie w D ia lo g ();
p re v ie w .D o c u m e n t = d o c u m e n t;
p re v ie w .S h o w D ia lo g (th is );

v o id docum ent P rin tP a g e (o b je c t s e n d e r,


~ P rin tP a g e E v e n tA rg s e) {
D ra w B e e ( е .G ra p h ic s , n e w R e c t a n g l e (О , О, 300, 300)

^ Мы воспользуемся уже написанным


методом DrawBeeQ.

Печать многостраничного документа


Ч тобы распечатать несколько страниц, нужно присво­
ить свойству e . H a s M o r e P a g e s обработчика собы тий
P r i n t P a g e значение true. В результате объект D o c u m e n t
узнает о дополнительны х страницах. О бработчик собы тий
будет вы зы ваться снова и снова, до тех пор пока он присва­
ивает свойству е .H a s M o r e P a g e s зн ачение true. О тредакти­
руем его таким образом, чтобы распечатать две страницы:

b o o l firs tP a g e = tru e ;
v o id d o c u m e n t_ P rin tP a g e (o b je c t s e n d e r, P rin tP a g e E v e n tA r g s e) {
D ra w B e e (e .G ra p h ic s , new R e c ta n g le (0 , 0, 300, 300));
© u s in g (F o n t fo n t = new F o n t( " A r i a l " , 36, F o n tS ty le .B o ld )) {
if (fir s tP a g e ) {
e . G r a p h ic s .D r a w s t r in g ("П е р в а я стра ниц а". F o n t, B ru s h e s .B la c k , 0, 0 );
e .H a sM o re P a g e s = tr u e ; присвоить свойству e.HasMorePages значение true.
firs tP a g e = fa ls e ; D ocum ent снова вызовет обработчик событий
} e ls e { и от правит на печать следующую страницу.
е .G r a p h ic s .D r a w s tr in g ( "В тор ая стра ниц а". F o n t, B ru s h e s .B la c k , О, 0 );

firs tP a g e = tru e ; Запчст ит е программу и убедитесь.


, ^ чило в окне предварительного
J ^■ просмотра появляются две страницы.

дальше > 625


вывод мира

Напишите код для кнопки Print, которая вызывает окно предварительного просмотра со

^ажнение статистикой пчел и изображениями улья и поля.

П усть кнопка вызывает окно предварительного просмотра


Добавьте к собы тию Click кнопки обработчик, преры ваю щ ий работу симулятора, он
будет вызы вать окно предварительного просм отра и после его закры ти я снова за-
пускать симулятор.

е Создайте оброботчик события P r i n t P a g e


О н долж ен создавать показанное на следующей странице окно:
p r i v a t e v o id d o c u m e n t_ P r in tP a g e ( o b je c t s e n d e r, P rin tP a g e E v e n tA r g s e) f
G ra p h ic s g = e .G r a p h ic s ;
S iz e s t r in g S iz e ;
u s i n g ( F o n t a r i a l 2 4 b o l d = n e w F o n t ( " A r i a l " , 24, F o n t S t y l e . B o l d ) ) {
Овал С т екстом s trin g S iz e = S iz e .C e ilin g (
5ыл создан при g . M e a s u r e S t r i n g ( " В е е s i m u l a t o r " , a r i a l 2 4 b o l d ) );
noMOUiU метода / g - F i l l E l l i p s e ( B r u s h e s . G r a y ,
M e a s u re S trin g Q j/ n e w R e c t a n g l e ( e . M a r g i n B o u n d s . X + 2 , e.M a r g i n B o u n d s . Y + 2 ,
возвращающего s tr in g S iz e .w id th + 30, s trin g S iz e .H e ig h t + 3 0 ) ) ;
значение Size, mo g .F illE llip s e ( B r u s h e s .B la c k ,
есть размер cm po- new R e c ta n g le ( e .M a r g in B o u n d s .X , e .M a rg in B o u n d s .Y ,
Ku, о в а л и т екст s t r in g S iz e .w id th + 30, s t r in g S iz e . H e ig h t + 3 0 ) ) ; '
были наридованы g . D r a w s t r in g ("В е е S im u la t o r " , a r ia l2 4 b o ld ,
два раза для созда- B r u s h e s . G r a y , e . M a r g i n B o u n d s . X + 1 7 , e . M a r g in B o u n d s Y + 17) •
НКЯ э ф ф е к т а т е н и . g . D r a w S t r i n g ( " B e e S i m u l a t o r " , a r i a l 2 4 b o l d ,
^ B ru s h e s .W h ite , e .M a rg in B o u n d s .X + 15, e .M a rg in B o u n d s .Y + 15) ;

f i n t t a b le X = e .M a rg in B o u n d s .X + ( i n t ) s t r i n g S i z e . W i d t h + 5 0 ;
Процедура ) t a b le W id t h = e .M a rg in B o u n d s .X + e .M a rg in B o u n d s .W id th - t a b le X - 20-
построения Л f ir s tc o lu m n x = ta b le X + 2 ;
таблицы. in t s e c o n d C o lu m n X = t a b l e X + ( t a b l e W i d t h / 2) + 5 ;
_ in t t a b le Y = e .M a rg in B o u n d s .Y ;
/ / Вам н уж н о н а п и с а т ь остальную часть метода, ра спеча ты ваю щ его окно

О Используйте м етод P r in tT a Ы e R o w ()
Э тот метод пригодится для создания таблицы со статистикой в верхней части окна.
p riv a te in t P rin tT a b le R o w (G ra p h ic s p r in t G r a p h ic s , i n t ta b le X ,
i n t t a b l e W i d t h , i n t f i r s t C o l u m n X , i n t s e c o n d C o lu m n X ,
i n t t a b l e Y , s t r i n g f i r s t C o l u m n , s t r i n g s e c o n d C o lu m n )
{
Font a r i a l l 2 = new F o n t ( " A r i a l " , 1 2 ) ;
S iz e s trin g S iz e = S iz e . C e ilin g (p rin tG ra p h ic s .M e a s u re S trin g (firs tC o lu m n , a r ia ll2 ) )
t H D lc Y += 2;
p r in tG r a p h ic s .D r a w S tr in g ( fir s tC o lu m n , a r i a l l 2, B ru s h e s .B la c k ,
firs tc o lu m n x , ta b le Y ) ;
p rin tG ra p h ic s .D ra w S trin g (s e c o n d C o lu m n , a r i a l l 2 , B ru s h e s .B la c k ,
s e c o n d C o lu m n X , t a b l e Y ) ;
ta b le Y += ( i n t ) s t r i n g S i z e . H e i g h t + 2 ;
p rin tG r a p h ic s .D ra w L in e ( P e n s .B la c k , ta b le X , ta b le Y , ta b le X + ta b le W id th , ta b le Y ) -
a r i a l l 2 .D is p o s e 0 ; ^ / //
r e tu r n ta b le Y ; V При каждом вызове мет од PrintTableRowQ добавляет
} высоту распечапланной строки в переменную tableY
и возвращает новое значение,
626 глава 13
элементы управление и графические фрагменты

В н и м ател ьно п р очитайте при м ечани я, которы м и мы снаб дили этот скринш от!

•І? Print preview

# Р - | И Ш В Ш В и Close: Page і 1

Д ля печати таблицы исполь­


зуйт е метод PrintTableRowQ.

Bees 6
Flowers 10
Honey in Hive 0.2Ш
N ectar in Flowers 2S.300
I
, Информация о левом поле Frames Run 286
хранится в переменной Frame Rate 16 (62.5ms)
e.MarginBounds. Эллипс начинает
ся в точке e.MarginBounds.X + 2.
.------ -а

Воспользуйтесь визуализат о- сделайте то же са­


ром для отображения ф ор­ мое для формы Field: ,
мы Hive. Нарисуйте вокруг ее ширину сделайт!
черную рамку с шириной Z. равной ширине cmpti ^
Используйте свойство W idth n. НиЦЫ с ПОМОШрУО ПС-‘ч ' ч
переменной e.MarginBounds. X ы Y параметра
чтобы получит ь окно в п о ­ e.MarginBounds. По­
ловину ширины страницы. старайтесь npudai V-
илд нужные пропорции. 1-%

Определив высоту изображения, выровняйте


его относительно нижней части страницы.
’4

Подсказка: Чтобы вычислить высоту каждой формы в окне предварительного просмотра, у м ­


ножьте соотношение высоты и ширины на итоговую ширину формы. Координата верхней части
формы Field рассчитывается по формуле: (e.MarginBounds.Y + e.MarginBounds.Height - fieldHeight).

дальше > 627


решение упражнения

Вот код кнопки P r i n t , щелчок на которой вызывает окно предварительного просмотра


ражненке со статистикой и формами Hive и Field.

решение
o " Z 2 T e Z lto p L T ^ -
u s in g S y s te m .D ra w in g .P rin tin g ; /

p r i v a t e v o id d o c u m e n t_ P rin tP a g e (o b je c t s e n d e r, P rin tP a g e E v e n tA rg s e) {
G ra p h ic s g = e .G r a p h ic s ;

S iz e s t r in g S iz e ;
u s in g (F o n t a r ia l2 4 b o ld = new F o n t( " A r ia l , 24, F o n tS ty le .B o ld )) {
s trin g S iz e = S iz e .C e ilin g ( Э ту часть
g . M e a s u r e S tr in g ( "B ee S im u la to r " a ria l2 4 b o ld )); кода М 1?1 б я М
g .F illE llip s e (B ru s h e s .G ra y , ы ж е давали.
new R e c ta n g le (e .M a rg in B o u n d s .X + 2, e .M a rg in B o u n d s .Y + 2, Здесь рисце<м-
s trin g S iz e .W id th + 30, s tr in g S iz e .H e ig h t + 3 0 ) ) ; ся заголовок
g .F illE llip s e ( B r u s h e s .B la c k , б овале м зада­
new R e c ta n g le ( e .M a r g in B o u n d s .X , e .M a rg in B o u n d s .Y , ются п а р а т
s tr in g S iz e .W id th + 30, s t r in g S iz e . H e ig h t + 3 0 ) ) ; 11плрь1 т й о л и ц к ’!
g .D ra w S trin g ("B e e S im u la to r " , a r ia l2 4 b o ld . со ст ат ист и-
B r u s h e s . G r a y , e . M a r g i n B o u n d s . X + 1 7 , e . M a r g i n B o u n d s . Y + 17) ; ,кой.
g .D ra w S trin g ("B e e S im u la to r " , a r ia l2 4 b o ld .
B r u s h e s . W h i t e , e . M a r g in B o u n d s . X + 1 5 , e . M a r g i n B o u n d s . Y + 15) ;
}

in t ta b le X = e .M a rg in B o u n d s .X + ( i n t ) s t r i n g S i z e . W i d t h + 5 0 ;
in t t a b le W id t h = e .M a rg in B o u n d s .X + e .M a r g in B o u n d s .W id th - ta b le X - 20-
in t fir s tc o lu m n x = ta b le X + 2 ;
in t s e c o n d C o lu m n X = t a b l e X + ( t a b l e W i d t h / 2 ) + 5;
in t t a b le Y = e .M a rg in B o u n d s .Y ; уже поняли, как

ta b le Y = P rin tT a b le R o w (g , ta b le X , ta b le W id th , firs tC o lu m n X ,
s e c o n d C o lu m n X , ta b le Y , "B e e s", B e e s .T e x t); v C r"" »Ужно вызывать ^
ta b le Y = P rin tT a b le R o w (g , ta b le X , ta b le W id th , firs tC o lu m n X , - каждой строки
s e c o n d C o lu m n X , ta b le Y , " F lo w e rs " , F lo w e rs .T e x t ) ;
ta b le Y = P rin tT a b le R o w (g , ta b le X , ta b le W id th , firs tC o lu m n X ,
s e c o n d C o lu m n X , t a b l e Y , " H o n e y i n H i v e " , H o n e y ln H iv e . T e x t) ;
ta b le Y = P rin tT a b le R o w (g , ta b le X , ta b le W id th , firs tC o lu m n X ,
s e c o n d C o lu m n X , t a b l e Y , " N e c t a r i n F l o w e r s " , N e c t a r l n F l o w e r s
ta b le Y = P rin tT a b le R o w (g , ta b le X , ta b le W id th , firs tC o lu m n X ,
s e c o n d C o lu m n X , ta b le Y , "F ra m e s R u n ", F ram esR un . T e x t) ;
ta b le Y = P rin tT a b le R o w (g , ta b le X , ta b le W id th , firs tC o lu m n X ,
s e c o n d C o lu m n X , ta b le Y , "F ra m e R a te ", F ra m e R a te .T e x t);

H e забудьтенарисовать
g .D r a w R e c ta n g le ( P e n s .B la c k , t a b le X , e .M a rg in B o u n d s .Y ,
ta b le W id th , ta b le Y - e .M a rg in B o u n d s .Y ); границу таблицы «
разделяющую столбцы.
g . D r a w L in e ( P e n s . B la c k , s e c o n d C o lu m n X , e .M a rg in B o u n d s .Y ,
s e c o n d C o lu m n X , t a b l e Y ) ;

628 глава 13
Для рисования рамки вокруг сним ­
ков экрана вам потребуемся черная
ручка с толш,иной линии 2- пиксела.
/
fu s in g (P en b la c k P e n = new P e n ( B r u s h e s .B la c k , 2 )) Размер объектов
/^ ^ u s in g (B itm a p h iv e B itm a p = new B itm a p (h iv e F o rm . C lie n t S i z e .W id th , Bitmaps должен
/ / h iv e F o rm .C lie n tS iz e .H e ig h t)) совпадать с од
I \u s in g (B itm a p fie ld B itm a p = new B itm a p ( fie ld F o r m .C lie n tS iz e .W id th ,
^ластью формы,
i ^реЭнйЗначеннои
Так как fie ld F o r m .C lie n tS iz e .H e ig h t]_
Зля рисования, <ло
объек- эт ому вам
ты Pen { u s in g (G ra p h ic s h iv e G ra p h ic s = G r a p h ic s . F ro m lm a g e (h iv e B itm a p )) годится объект
и Bitmap CUentSbe.
после {
r e n d e r e r . P a in tH iv e ( h iv e G r a p h ic s ) ;
прим ене­
ния нужно }
будет
удалить, i n t h iv e W id th = e .M a rg in B o u n d s .W id th / 2
мы п о ­ f l o a t r a t io = (flo a t)h iv e B itm a p .H e ig h t / (flo a t)h iv e B itm a p .W id th ;
местили in t h iv e H e ig h t = ( in t) ( h iv e W id th * r a t i o ) ;
их в блок in t h iv e X = e .M a rg in B o u n d s .X + ( e .M a r g in B o u n d s .W id th - h iv e W id th ) / 2;
using.
i n t h iv e Y = e .M a rg in B o u n d s .H e ig h t / 3 ;
g .D ra w lm a g e (h iv e B itm a p , h iv e X , h iv e Y , h iv e W id th , h iv e H e ig h t);
g .D ra w R e c ta n g le (b la c k P e n , h iv e X , h iv e Y , h iv e W id th , h iv e H e ig h t);

u s in g (G ra p h ic s fie ld G ra p h ic s = G ra p h ic s . F ro m lm a g e (fie ld B itm a p ))

{ ^'^^^"S’nBounds.SAJidth
r e n d e r e r . P a i n t F i e l d ( f i e l d G r raphics)
a p h i c s ) ;; ласт и, которая бидет
6 ц д е т Т ь 7 .7 ^ '^ .ширину об-
Именно ^ а к ^ шиТиТи ! . ! '''' печать.
i n t f i e l d w i d t h = e . M a r g i n B o u n d s3.w id th ;
. Width; снимки экрана. <^меть ваши
r a t io = ( flo a t) fie ld B itm a p .H e ig h t / ( flo a t)fie ld B itm a p .W id th ;
in t fie ld H e ig h t = (in t) ( fie ld w id th * ra tio ) ; соотношения высоты и ширины ф ор-
in t f i e l d x = e .M a rg in B o u n d s .X ; м ы в ы ч и с л я е м высоту сниМка экрана.
in t fie ld Y = e .M a rg in B o u n d s .Y + e .M a rg in B o u n d s .H e ig h t - fie ld H e ig h t;
g .D ra w lm a g e (fie ld B itm a p , fie ld X , fie ld Y , fie ld w id th , fie ld H e ig h t);
g .D ra w R e c ta n g le (b la c k P e n , fie ld x , fie ld Y , fie ld w id th , fie ld H e ig h t);

p riv a te v o id p rin tT o o lS trip B u tto n l_ C lic k (o b je c t s e n d e r, E v e n tA rg s e) {


bool stoppedTimer = false; Код кнопки Print прерывает работу сим улят о-
if (timerl.Enabled) { pa, создает объект PrintPocum ent, связывает его
timerl.stop О ; ^ обработчиком события PrintPage, вызывает окно
^ StoppedTimer = true; диалога и возобновляет работу симулятора.
P r in tP r e v ie w D ia lo g p r e v ie w = new P r in tP r e v ie w D ia lo g О ;
P rin tD o c u m e n t d o cu m e n t = new P r in tD o c u m e n t( ) ;
p re v ie w .D o c u m e n t = d o c u m e n t;
d o c u m e n t.P r in tP a g e += new P r in t P a g e E v e n t H a n d le r ( d o c u m e n t _ P r in t P a g e ) ;
p re v ie w .S h o w D ia lo g (th is );
if (s to p p e d T im e r)
tim e rl.S ta rt0 ;

дальше > 629


мини-л0боратория

А еще мо)кно было бы...


Вы построили небольш ой, но симпатичны й симулятор, так зачем останавливаться? Вот
идеи, которы е п ри ж елании вы мож ете реализовать самостоятельно.

Добавьте панель управления


Превратите константы классов World и Hive в свой­
ства. Затем добавьте новую форму с панелью управления
и ползунками.
Создайте врагов
Пусть улей атакуют враги. Чем больше цветов, тем
больше должно быть врагов. Вам потребуются Sting
Patrol для борьбы с врагами и Hive Maintenance для
восстановления улья. И все эти пчелы потребляют мед.
Усовершенствуйте улей
При наличии достаточного количества меда улей растет. Хороший
симулят ор
Чем больше улей, тем больше в нем пчел. Но при этом позволяет
растет потребление меда и учащаются атаки врагов. При пользовате -
значительном ущербе улей снова должен стать меньше. ли> выбирать,
каким спосо­
Пусть пчелиная матка откладывает яйца бом пойдет
Для заботы о яйцах вам потребуются рабочие пчелы Baby развитие
Вее Саге. Чем больше меда в улье, тем больше яиц от­ системы.
кладывает матка. А значит, требуются дополнительные
пчелы-няньки, которые едят мед.
Добавьте анимацию
Анимируйте фоновое изображение формы Hive, заставив
солнце двигаться по небу. Пусть ночью становится тем­
но и появляются луна и звезды. Добавьте перспективу:
пусть пчелы, сидящие на ближних цветах, имеют больший
размер, чем пчелы, расположенные дальше.
Используйте свое воображение!
Придумайте собственные способы сделать симулятор ин­
терактивным и более интересным.

Вы создали удачную версию симулятора? Покажите


ее нам, загрузите код на форму Head First C# www.
headfirstlabs.com/books/hfcsharp /

630 «rraea 13
Head First
глава
$2.98
14
O b je c tv ille
Hom e of
преступление повт орилось

^Возьм
Возьмиі 8 руку кйрандаш-
V В о т код, о п и сы в а ю щ и й б и тв у м е ж д у В е л и ко л е п ны м и Ж у л и ко м (а та кж е
с а р м и е й кл о н о в ). Н а р и суй те , ч то п р о и с хо д и т в куче при созд ани и
экзе м п л я р о в кл а сса F i n a l B a t t l e . Можно предположить, что
клоны были созданы при
c la s s F in a lB a ttle { помощи инициализаторй
pu b l i c C lo n e F a c t o r y F a c t o r y = new C lo n e F a c t o r y ( ) ; коллекции. I
p u b l i c L is t < C lo n e > C lo n e s = new L i s t < C lo n e > 0 { .
p u b l i c S w in d le rs E s c a p e P la n e e s c a p e P la n e ;

p u b l i c F i n a l B a t t l e () { Р'^сунок
V i l l a i n s w in d le r = new V i l l a i n ( t h i s ) ;
u s in g (S u p e rh e ro c a p ta in A m a z in g = new S u p e r h e r o O ) {
F a c to r y . P e o p le ln F a c to ry .A d d (c a p ta in A m a z in g );
F a c to r y . P e o p le ln F a c to ry .A d d (s w in d le r)
c a p t a in A m a z in g .T h in k ("Я у н и ч т о ж у с сы л ки на кл о н ы ,
одну за о д н о й ");
c a p ta in A m a z in g .Id e n tify T h e C lo n e s (C lo n e s ) ;
c a p ta in A m a z in g .R e m o v e T h e C lo n e s (C lo n e s ) ;
s w in d le r . T h in k ("Ч е р е з н е с к о л ь к о м инут моя армия с та н е т м усо р о м ");
s w in d le r . T h in k (" (б уд е т собр ана
e s c a p e P la n e = new S w in d le r s E s c a p e P la n e ( s w in d le r ) ; 1
s w in d le r.T ra p C a p ta in A m a z in g (F a c to ry ); Нарисуйте, что
M e s s a g e B o x .S h o w ("Ж ул и к у б е ж а л " ) ; происходит о м о ­
м е н т создания эк­
Как 5ydem выглядеть куча после земпляра ооьекта
] e
[S e ria liz a b le ]
зітуска конструктора FinalBattle
(финальная 5итваУ
SwindlersEscapePlane
(План побега Жулика).
c la s s S u p e rh e ro : ID is p o s a b le {
p r i v a t e L is t < C lo n e > c lo n e s T o R e m o v e = new L i s t < C lo n e > ( ) ;
p u b l ic v o id Id e n t ify T h e C lo n e s ( L is t< C lo n e > c lo n e s ) {
f o r e a c h (C lo n e c lo n e i n c lo n e s )
C lo n e s T o R e m o v e .A d d (c lo n e );
}
p u b l i c v o id R e m o v e T h e C lo n e s (L is t< C lo n e > c lo n e s ) {
f o r e a c h ( C lo n e c lo n e i n c lo n e s T o R e m o v e )
c lo n e s . R em ove(c lo n e );

, ■■■ ^2>&cb должен 5ыть дополнительный код (вклю -


^ т ю щ Т м е т о д PisposeQ . реализующии ин-
, ■■■ \ тер%ейс IDisposable). Но он скрыт, так как не
^ имеет значения Эля решения задачи.
c la s s V i l l a i n {
p r iv a te F in a lB a ttle fin a lB a tt le ;
p u b lic V illa in ( F in a lB a t t le fin a lB a tt le ) {
t h is . fin a lB a ttle = fin a lB a ttle ;
}
p u b lic v o id T r a p C a p ta in A m a z in g (C lo n e F a c to ry f a c t o r y ) {
f a c t o r y . S e l f D e s t r u c t . T i c k += new E v e n t H a n d le r ( S e lf D e s t r u c t _ T ic k ) ;
fa c to r y . S e lfD e s tr u c t. In te r v a l = 600; ~
fa c to r y . S e lfD e s tru c t. ^ t a r t ();
}
p r iv a te v o id S e lfD e s tru c t_ T ic k (o b je c t s e n d e r, E v e n tA rg s e) {
f in a l B a t t le .F a c to ry = n u ll;
}

634 глава t4
смерть объекта

c la s s S w in d le rs E s c a p e P la n e {
p u b lic V i l l a i n P ilo ts S e a t;
p u b lic S w in d le rs E s c a p e P la n e (V illa in escapee)
Класс Clone также
P ilo ts S e a t = escapee; не показывается
} как н е с у щ е с т б е н Не забывайте про
} и ш для ременыя мет ки объектов,
данной задачи- указывающих на
c la s s C lo n e F a c to ry { ссылочные пере­
p u b l i c T im e r S e l f D e s t r u c t = new T i m e r ( ) ;
менные.
p u b l i c L i s t < o b j e c t > P e o p l e l n F a g t o r y = n e w L i s t < o b j e c t > ()

}
Мы начали отвечать на первый
вопрос. Линии демонстрируют
архит ект уру. Скажем^ мы прове­
ли линию между фабрикой клонов
и объектом Villain, так как ф а­
брика ссылается на эт от объект
(через свое поле PeoplelnFactory).

Здесь нмисовано
не осе. Остальные
объекты вставьте
самостоятельно.

Нарисуйте, что
'происходит в куче
о остальных двцх
е точках. ^

В ка ко м м есте кода у м и р а е т ка п и та н В е л и ко л е п ны й ?

О ставьте со о тв е тс тв у ю щ и й ко м м е н та р и й на д и а гр а м м е .

дальше ► 635
что бы могли означать эти циф ры

Ьзьми в руку карандаш В от ка к вы гл я д и т куча, по м ере вы п о л н е н и я програм м ы

Решение FinalBattle.

указывает на объект
Superhero, а ссылка
swm dfer ~ на объект Villain
>^яшеи фабрики клонов
o s lS r ““ “
Э то о б ъ ект ,
который оы
Эолжнь»! Ь ы т
Эобабиил» к Эйд
граМЛЛ£-

Теперь ссылка
escapePlane у к а ­
О зывает на новый
экземпляр объекта
SwindlersEscapePlane
а поле PilotSeat ~ на
Пока сущ ествует ссылка объект Villain.
escapePlane на oovckvsa
sv-j'mditr, э т о т oovcK m не
будет отправлен в мусор-
При появлении события selfDestruct
ьГ - ссылка factory начинает указывать на
значение nullj а зн ачит^ отправляется
в мусор и исчезает с нашей диаграммы.

е escape
Plane
Вслед за ссылкой factory в мусор
отправляется объект CloneFactory,
это приводит к исчезновению
^ J- объекта List... а это было послед­
ним, что сохраняло наш объект
SuperHero. Он исчезнет при следу­
ющем проходе сборщика мусора.

В от м о м е н т и сч е зн о ве н и я на ш е го суп е р ге р о я :
void SelfPestruct_Tick(ohJect sender^ EventArgs e) {
finalBattle.faciory = nciK;.................................................
fin a iB a ttie .

Так как экземпляр Superhero не


имеет клонов, на которые мог бы
С сы л ка f i n a l B a t t l e F a c t o r y с та л а ука зы в а ть на null, и это*^ сослаться объект factory, он пред­
п р и в е л о к и сч е зн о в е н и ю п о сл е д н е й ссы л ки на ка п и та н а ! назначается для сборщика мусора.

636 глава 14
это твое последнее слово?

Вам не придется писать метод заверш е­


Метод забершения объекта ния для объектов, обладаю щ их управ­
ляем ы м и ресурсам и. Все, с чем вы
сталкивались в этой книге бы ло управ­
И ногда нужно, чтобы некие действия произош ли до того,
ляем ы м — оно управлялось CLR. Но
как объект отправится в мусор, наприм ер вы свобож дение бы ваю т случаи, когда программистам
неуправляемых ресурсов. требуется доступ к базовым ресурсами
W indow s, которы е не являю тся частью
Так назы ваемы й метод заверш ения объекта (finalizer) по­
■NET Fram ew ork. С кажем, код с атрибу­
зволяет написать код, которы й будет вы полняться перед
том [ D l l I n ç ) o r t ] может использо­
уничтож ением объекта. Его можно представить, как персо­
вать неуправляем ы е ресурсы . Если их
нальны й блок f i n a l l y : он всегда вы полняется последним. воврем я не удалить (например, соответ­
Вот п рим ер такого метода для класса C lo n e: ствую щ им методом), эти ресурсы могут
повлиять на стабильность системы.
И м енно для такой цели требуется метод
[S e r ia liz a b le ] Это конструкт ор­ заверш ения объекта.
c l a s s C lo n e { ом заполняет поля
s t r i n g L o c a tio n ; ClonelP и Location при
каждом создании оОъ-^
i n t C lo n e lD ; екта Clone.

p u b l i c C lo n e ( i n t c l o n e l D , s t r i n g lo c a tio n ){
t h is .C lo n e lD = c lo n e lD ;
t h is .L o c a t io n = lo c a t io n ;
}

p u b lic v o id T e llL o c a t io n (s t r in g lo c a t io n , i n t c lo n e lD ) {
C o n s o l e . W r i t e L i n e ("Мой н о м е р { 0 } и " +
"ты н а й д еш ь м е н я т у т : { 1 } ClonelD, location)
}
Знак ~ указывает, ч т о код в
, эт ом блоке выполняется, когда
p u b lic v o id W reakH avoc( ) { объект отправляется о мусор. Зт о мет од завер­
шения объекта. Он
one О { отправляет н е-
T e llL o c a t io n (t h i s . L o c a tio n , t h is .C lo n e lD ) ; Ю Г“ ^°общение с
C o n s o le .W r ite L in e (" { 0 } h a s b e e n d e s tr o y e d " C lo n e lD ) несчастного клона.
Но только после
объект
онает помечен для
с-оорки Мусора.

М етод заверш ения объекта отли­ Часть кода в книге предназначена


чается от конструктора тем, что только для учебных целей.
вместо м одиф икатора доступа Р руды пе
перед именем класса помещ ается о а ц ь р о ж н ь їі
Пока вы только слышали, что объект
знак И .NET вы полнит этот код отправляется в мусор, как только исчезает ссылка на него.
в прямо перед отправкой объекта Мы готовы показать вам код, автоматически запускающий
в мусор. сборку мусора при помощи метода GC. C o l l e c t () и вы ­
зы ваю щ ий M s s s a g e B o x в методе завершения объектов.
Э тот метод не им еет парам етров, Эти вещи затрагивают «сердце» CLR. Мы показываем их,
так как .NET долж на уничтож ить чтобы объяснить принцип сборки мусора. Никогда не ис­
объект. пользуйте их в рабочих программах.

638 глава 14
637
смерть объекта

Когда запускает метод завершения объекта


М етод заверш ения объекта запускается по­ Это ваш объект
Д ругой обтгект в памяти. ■
сле исчезновения всех ссылок, но до отправ­ С с ы л а е т с я на
ки объекта в мусор. Ведь сборка мусора не вам объект-
всегда запускается сразу после и счезновения
ссылок.
П редполож им, у нас есть объект и ссылка
на него. Запущ енный .NET сборщ ик мусора
п р о вер яет его состояние. Обнаружив ссылку,
сборщ ик игнорирует этот объект, и он оста­
ется в памяти.
Затем последний объект, ссылавш ийся на
ваш объект, удаляется. Доступ к нему пропада­ Ваш объ ект все --■НО ссылок на него
еш,& в куче... уже не осталось.
ет. П о сути объект умер.
Н о дело в том, что сборкой мусора управляет » к “
.NET, а не объекты. П оэтому до запуска п ро­
цедуры сборки объект ж ивет в памяти. Его
/
невозмож но использовать, но он есть. И его
метод завершения объекта еще не начал
свою работу.
И вот .NET в очередной раз запускает сбор­ ?\^ К у ч а
щ ик мусора. Запускается и метод заверш ения
И дкоиец л о я б л я е т -
объекта... возможно, ч ерез несколько ми­
нут после исчезновения последней ссылки,
и только после этого объект окончательно в к<ррз»из.
исчезает.

принудительная сборка мусора

.NET п озволяет вам предложить запуск сбор­ Куча


ки мусора. По большому счету пользовать­
ся этой возможностью вряд ли стоит, так
public void RemoveTheClones(
как сборка мусора отвечает множеству
List<Clone> clones) {
условий CL R — и принудительный ее вы­
foreach (Clone clone in clonesToRemove)
зов — не очень хорошая идея. Н о чтобы по­
clones.Remove(clone);
см отреть на работу метода заверш ения объ­
ектов, вы мож ете воспользоваться методом GC.Collect();
GC.Collect О . Мы не можем еще р а з не подчеркнуть, н а -
\ сколько плохой идеей является вызов м ет ода
Будьте внимательны . Этот метод не застав­ ' ac.CollectQ в серьезны х программах, так как вы
ляет .NET немедленно приступить к сборке мож ет е сбить настроенную процедуру сб о р ­
ки мусора. Но для обучения нет ничего лучше,
мусора. О н только говорит: «Выполните эту п о эт о м у мы создадим игровую npozpaM M yj
процедуру как можно быстрее». в которой воспользуемся эт им методом.

дальше > 639


собираем мусор

ЯВное U неявное Высвобождение ресурсов Как вы уже видели, м е ­


тод DfsposeQ работаем
и дез оператора using. Его
Метод Dispose о запускается, когда объект, с о з д а н н ы й п р и п о м о щ и многократное применение
не имеет побочных эф ­
о п е р а т о р а usin g , получает з н а ч е н и е null. Е с л и о п е р а т о р u s i n g н е фектов.
п р и м е н я л с я , с с ы л к а н а з н а ч е н и е null н е станет п р и ч и н о й в ы з о в а м е т о ­
да D i s p o s e () — в а м п р и д е т с я в ы з ы в а т ь его вручную. М е т о д з а в е р ш е ­
н и я объекта работает со с б о р щ и к о м мусора. П о с м о т р и м на практике,
ч е м эт и м е т о д ы о т л и ч а ю т с я друг от друга:
|п ] ^ а ж н е н и е

Создайте класс C l o n e , р еал и зую щ и й интерсрейс I D i s p o s a b l e


Кла с с д о л ж е н и м е т ь ав т о м а т и ч е с к о е свойство I d т и п а int, а т а к ж е
конструктор, м е т о д D i s p o s e () и м е т о д з а в е р щ е н и я объекта:

class Clone : IDisposable {


public int Id { get; private set;

Так как класс реализует


public Clone(int Id) { интерфейс IDisposable, в нем
this.Id = Id; должен присут ст воват ь
} метод DisposeQ.

Напоминаем: public void Dispose 0 {


Вызов окна
MessageBox M e s s a g e B o x .S h o w ( " I ' v e been disposed!",
в методе за - "Clone #" + Id + " says...");
вериления объ­
екта м о ж е т
внести п и т а -
ницу в работу
CLR. Приме­ M e s s a g e B o x .S h o w ( " A a a r g h ! Y o u got me!",
нять его в це­ "Clone #" + Id + " says...");
лях, отличных }
от цчебных, не
следует.
во т так вы­
глядит вама ^
О Создайте qx)pMy с трелля кнопкам и -
ф орМ й-

П р и п о м о щ и о п е р а т о р а u s i n g создайте э к з е м п л я р C l o n e в о б р а б о т ­
aone#i
Метод ч и к е с о б ы т и я Click. Э т о п е р в а я часть кода кнопки:
создает ,aone#2
экземпляр
Clone и не-
меделенно private void clonel_Click(object sender, EventArgs e) { GC
убивает u s i n g ( C l o n e c l o n e l = n e w C l o n e (1)) {
его, убирая --- ^ // Н и ч е г о не делайте!
все ссылки.
}
} У
Так как cionei был объяв­
лен при помощи оператора
using, запускается его метод
DisposeQ. остается I
cSo/ku

640 глава 14
о П о д к л ю ч и те д в е д р у ги е кнопки
С оздайте ещ е один экземпляр C l o n e в обработчике собы тия Click
второй кнопки и присвойте ему зн ачение null:

private void clone2_Click(object sender, EventArgs e) {


Clone clone2 = new C l o n e (2);
clone2 = null; Так как в данном случае, от сут ст вует
} оператор using, метод DisposeQ з а ­
пущен не будет. Но сработает метод
завершения объекта.
Для тр етьей кнопки вы зовите метод GC.Collect о , чтобы запу­
стить процедуру сборки мусора.

private void gc_Click(object sender, EventArgs e) {


GC.Collect 0 ; Помните, что подобного
} — Эта строчка ■*£--------— делать не следует. В дан­
принудительно ном случае мы делаем это
запускает сбор исключительно для учеб­
З а п у с т и те п роградш у мусора. ных и демонстрационных
целе/А.
Щ елкните на первой кнопке, чтобы вы звать метод D i s p o s e ().
Хотя объекта С!опе±
Clone#1 says,«
“1 If вт ван
I’vebeendisposed! он все
em в куче и ожидает
сборщика мусора.
OK

Мусор в итоге собран. В больш инстве случаев вы не


увидите окна с сообщ ением об этом, так как между
присвоением объекту значения null и сборкой мусо­
Куча
ра проходит некоторое время.
Щ елкните на второй кнопке. Н ичего не произойдет,
так как мы не использовали оп ератор using. О кно
об ъект
диалога с сообщ ением от метода заверш ения объек­ С\опеЯ- ' ~
та появится только после сборки мусора. илакже пока 'Опгг
остаг,тся
Щ елкните на третьей кнопке, чтобы принудительно К уч а
б к у ч е , но
запустить эту процедуру. П оявятся два окна диалога: ссылок Нй
н е го уже нет.
для объектов C l o n e l и С1оп е 2 .

СЬпе*2:^. ИММ Done#1says... 1т^Й


АвагдЫYoujotme! Aaargh!Yougotme! \ I /
\ I / —- Бах! —
- Бах! —
/ 1 \
i <ж 1 i OK 1 / I \
Куча

Вызов метода С}С.Со11е^0 запускает метод окончания


для обоих объектов, и они исчезают из кучи.
П о и г р а й т е с п р о гр ам м о й . Н есколько раз по очереди щ елкайте н а кнопках. Иногда пер­
вым будет исчезать клон #1, в других случаях —клон #2. А иногда сборка мусора будет запу­
скаться ещ е до вы зова метода G C .C o l l e c t ().
нестабильная среда

П р едполо ж и м , у вас есть два


Возможные проблемы объ екта, ссы лаю щ иеся д р у г
на д р у га ...
Вы не мож ете зависеть от запуска метода завер­
ш ения объекта в произвольны й момент. Даже
вызов метода G C . C o l l e c t O —которы м не стоит
пользоваться, если у вас нет веских на то п ричин —
только предлагает запустить сборш,ик мусора. И
нет гарантии, что это случится немедленно. Более
того, узнать, в каком порядке будут удаляться объ­
екты, невозможно.
Ч то это означает на практике? П редставим, что у
вас есть ссылаюш;иеся друг на друга объекты. Если
объект #1 будет удален первым, ссылка объекта #2
начнет указывать в никуда. И наоборот. Другими
...о н и оба пом ечены д л я сборки мусора,
словами, вы не можете полагаться на ссылки в мето­
но о б ъ ект #1 удаляется п ер в ы м ...
де завершения объекта. То есть помещ ать в метод
заверш ения объекта операции, зависящ ие от ссы­
лок, явно не стоит.
Х орош им прим ером процедуры, которая н и в
к о е м с л у ч а е н е д о л ж н а о к а за т ь с я в н у т р и м е т о ­
д а з а в е р ш е н и я о б ъ е к т а , является сериализация.
Если объект ссылается на множ ество других объ­
ектов, сериализации подвергается вся цепочка. Н о
-Бах! —
если сборка мусора уже произош ла, вы мож ете н е ­ / 1 \
д о с ч и т а т ь с я важных частей программы , так как
н екоторы е объекты могут быть отправлены в му­
сор до запуска метода их заверш ения.
К счастью, C# предлагает удачное реш ение данной ...хо тя первы м м ог бы ть удален
проблемы: и н терф ей с I D i s p o s a b l e . Все редак­ и о б ъ ект #2. П о ряд ок вы полнения этой
ти ровани я клю чевых данны х или данных, зави­ проц ед уры узнать нев о зм о ж но ...
сящ их от находящ ихся в памяти объектов, нужно
делать частью метода D i s p o s e ().
И ногда пользователи считаю т метод заверш е­
ния объекта более надежным вариантом метода
D i s p o s e О . И не без оснований — на прим ере
объектов C l o n e вы уже видели, что реализация
и н терф ей са I D i s p o s a b l e не означает вы зова ме­
тода D i s p o s e {). П ри этом если метод D i s p o s e ()
зависит от объектов, находящ ихся в куче, его вы­
зов в методе заверш ения объекта мож ет привести
к проблемам. Лучше всего в с е г д а и с п о л ь з о в а т ь
о п е р а т о р u s i n g для создания объектов, реализу­ ...и м е н н о поэтом у метод
ющих и н т е р ф е й с I D i s p o s a b l e . зав ерш ени я од но го об ъ екта не
м ож ет полагаться на объ екты , д о
си х пор при сутств ую щ и е в куче.

642 глава 14
смерть объекта

Сериализуем объект 6 методе Disposed


Теперь, когдавы понялиразницум еж ду методом D i s p o s e ()
и методом заверш ения объекта, напиш ем объект, автома­
У праж нение
тически сериализуюш;ий себя перед удалением.

С делаем сериализуем ы м класс C l o n e (со с . 6 4 0 )


П росто добавьте в верхню ю часть класса атрибут Serializeable.

[S e r ia liz a b le ]
class Clone : IDisposable

о О тредактируем м етод D i s p o s e ()
Воспользуемся классом B i n a r y F o r m a t t e r для записи объекта C l o n e в файл
внутри метода D i s p o s e О : _
^ ^ ^ ___________ Аля доступа
using S y s t e m . 10; " " ■— к мтользуел№1М^
using System.Runtime.Serialization.Formatters.Binary; ^
H&CKOAbKO dupCK-
// существующий код using.

public void Dispose 0 {


string filename = 0 " C :\Temp\Clone.dat"
string dirname = @"C:\Temp\";
if ( F i l e . E x i s t s ( f i l e n a m e ) = = false) {
Directory.CreateDirectory(dirname);
}
BinaryFormatter bf = n e w B i n a r y F o r m a t t e r (); Имя файла было
using (Stream output = File.OpenWrite(filename)) включено в код в виде
b f .S e r i a l i z e ( o u t p u t , this); строковой констан­
ты. Д ля учебной
} программы это нор -
M e s s a g e B o x .S h o w ( " Д о л ж е н .. . сери али зовать. . . объект!
м алш о. но может
"Clone #' + Id + " г о в о р и т . сопровождаться п р о ­
} блемами. Понимае­
те ли вы, какие это
проблемы и видите
о Запусти те прилож ение
Вы увидите ровно то ж е самое, что и несколькими страницам и ранее... но те­
ли пут и их решенияР

перь перед удалением объект C l o n e l будет сохранен в файл. О ткрой те этот


файл, и вы увидите двоичное представление объекта. Правда ли, что метод
DisposeQ не имеет
побочных эффектов?
Что произойдет, если
его вызвать более
ШТУРМ одного раза? Реализця
интерфейс IDisposable,
им еет смысл зара­
Как, по-вашему, выглядит полный код объекта нее думать о таких
веш,ах.
SuperHero? Часть его показана на с. 634. Вы
можете написать остальное?

дальше > 643


что случилось с капитаном?

Беседа у камина
М етод Disposée) и м е т о д завершения
объекта сп о р ят о т о м , к т о из них
ценнее.

Disposée) М е т о д завер ш ен и я о б ъ е к т а
Ч естно говоря, приглаш ение сюда меня удивило.
Я думал, программисты уже приш ли к соглаше­
нию. О том, что я более ценны й. Ты выглядишь
жалко. Ты не в состоянии сериализовать себя
или отредактировать клю чевые данные. Ты же
нестабилен, разве не так?

П отрясаю щ е! Я жалок... хорош о. Я не хотел пе­


реходить на личности, но после такого выпада...
мне, по крайней мере, не требуется интерф ей с
для работы . А ты без и н терф ей са I D i s p o s a b l e —
не более, чем ещ е один бесполезны й метод.
И н терф ейс существует и м ен н о потом у, что я
очень важен. Б олее того, я единственны й метод К онечно, конечно... продолж ай утеш ать себя.
этого интерф ейса! А что произойдет, если пользователь при созда­
нии экзем пляра забудет оп ератор u s in g ? Тебя
даже никто не найдет!

Да, если программисты не используют оператор Обработчики используются


программами для непосред -
u s i n g , они долж ны вы звать меня вручную. Н о ственного взаимодействия с
они всегда знают, когда я запускаюсь, и могут вы­ Windows. Так как .NET о них не
знает, она не может удалить
зы вать меня, когда необходимо удалить объект. их за вас.
Я мощ ный, надеж ны й и легкий в прим енении. Я
универсален. А ты? Н икто не знает, когда ты по­
явиш ься, и в каком состоянии в этот момент бу­ Хорош о. Н о если нужно сделать что-то в самый
дет прилож ение. последний момент перед отправкой объекта на
удаление, без меня не обойтись. Я освобождаю ,
сетевы е ресурсы, а такж е обработчики и п о т о к и /
\Vindows и все, что мож ет стать п ричи ной про­
блем, если его вовремя не удалить. Я гарантирую
аккуратное удаление объектов, и на это ты ниче­
го возразить не сможешь.
П о сути ты не делаеш ь ничего, чего не мог бы
сделать я. Н о ты считаеш ь себя важным только
потому, что запускаешься при сборке мусора.
Ты слишком много мнишь о себе, приятель.

644 глава 14
Часзг>°
задаваем ы е
БоПр>ос;ь1

Может пи метод завершения пользоваться полями


и методами объектов?
Б t Как часто происходит автоматическая сборка мусора?

Q ; На этот вопрос нет ответа. Не существует постоянного


Q ; Конечно, вы не можете передавать ему параметры, но цикла, и вы никак не можете контролировать этот процесс. Он
можете пользоваться полями объектов как напрямую, так и при гарантированно запускается при выходе из программы или после
помощи ключевого слова t h i s . Но будьте аккуратны в случаях, вызова метода GC . C o l l e c t ().
когда поля ссылаются на другие объекты. Можно также вызывать
другие методы для утилизируемых объектов. Как быстро начинается сбор мусора после вызова мето­
да G C . C o l l e c t () ?
Что случается с исключениями, появившимися в методе
завершения объекта? j; Метод G C . C o l l e c t {) просит .NETосуществить
cbop мусора как можно быстрее. Обычно .NET приступает
^ ! Действительно, ничто не мешает поместить в этот метод к этой процедуре после завершения текущих заданий. То
блок t r y / c a t c h . Создайте исключение «деление на ноль» есть мусор убирается довольно оперативно, но вы не можете
в блоке t r y написанной нами программы для клонов. Пусть контролировать этот процесс.
окно с сообщением «I just caught ап exception» появляется перед
сообщением «...I’ve been destroyed». Запустите программу и Если мне обязательно нужно что-то запустить, имеет ли
щелкните на первой и третьей кнопках. По очереди появятся смысл поместить это в метод завершения объекта?
оба окна диалога. (Разумеется, вызывать окна диалога в методе
завершения объектов ни в коем случае не нужно.) ! Код этого метода может и не быть запущен. Но в общем
случае да, метод завершения объекта обязательно запускается.

дальше ► 645
смерть объекта

Структура напоминает объект...


О д н и м и з т и п о в .N E T , о к о т о р ы х м ы ещ е н е у п о м и н а л и , я в ­ м о гу т
л я ю т с я с т р у к т у р ы ( s t r u c tu r e ) . О н и к а к и о б ъ е к т ы о б л а д а ю т
п о л я м и и с в о й с т в а м и . И х м о ж н о даж е п е р е д а ть методу, п р и ­ и н терф ей са., но
н и м а ю щ е м у п а р а м е тр ы т и п а с Ь j ect: не могцт быть
'унаследованы.

public struct AlmostSuperhero IDisposable {


public int SuperStrength; Стриктуры м огут
. , одладать свойствами
public int SuperSpeed { ge t ; private set ' i3 и полями...

public void RemoveVillain(Villain villain)


,,.и определять
{ методы.
Console-WriteLine("OK, " + villain.Name +
" сдавайся и прекращай б е з у м и е !");
if ( v i l l a i n .S u r r e n d e r e d ) Достоинством
v i l l a i n .G o T o J a i l ();
else объектов является
v i l l a i n .K i l l ();
их умение имити­
public void Dispose 0 { ... } ровать поведение
}
реальных сущно­
...HO объектом не ябляется
стей нри помощи
С т р у к т у р ы м о гу т и м е т ь п о л я и м е то д ы , н о для н и х н е в о з м о ­
ж е н м е то д з а в е р ш е н и я о б ъ е к та . Н а с л е д о в а н и я для н и х т а к ж е наследования и
невозм ож но.

Все ст рукт уры явля­


полиморфизма.
ются производными от
класса 5^&Ыт.Уа1иеТиюе,
который в свою очередь
Вместо отдельных
наследует от класса
Ву5Ъет.дЬ]ес±. Поэто­ объектов можно ис­
пользовать ст рцк-
Структуры же
му каждая ст рукт ц-
1^ у р ы , но они не в
ра обладает методом
ТоЗЬппдО). Но эт им состоянии участ во- лучше всего ис­
способность к насле- оать в построении
Ообанию у ст рукт ур
и исчерпывается
иерархий. пользовать для
Именно поэт ому t/C'j
вы хранения данных,
чаще пользуетесь клас-
о т объектов. несмотря на их
ст рукт уры не имею т
<лрименения. ограниченность.
Н о о с н о в н ы м о т л и ч и е м с т р у к т у р о т кл а с с о в я в л я е тс я т о т ф акт, ч т о
структуры являются типами значений, в то время как классы относят­
ся к ссылочным типам. Р а с с м о тр и м н а п р и м е р е , ч т о э т о о значает...
дальше > 647
создаем копию

Значения копируются, а ссылки присбаиВаются


В ы уж е знаете, чем о д и н т и п о тл и ч а е т с я о т д р у го го . С о д н о й с т о р о н ы и м е ­
ю т с я значимые типы, т а к и е к а к int, b o o l и d e c i m a l . С д р у г о й с т о р о н ы —
Вспомним, чем
объекты, т а к и е к а к List, S t r e a m и E x c e p t i o n . И о н и р а б о т а ю т по-разном у. от личают ся зна­
чимые т ипы от
В случае з н а ч и м ы х т и п о в о п е р а т о р п р и с в а и в а н и я копирует значение. П е ­ одьектов.
р е м е н н ы е п р и э т о м н и к а к н е св я за н ы д р у г с д р у го м . В случае ж е с с ы л о к о п е ­
р а т о р п р и с в а и в а н и я нацеливает обе ссылки на один и тот ж е объект.

О О бъявление перем енны х и операция присваи вания одинако-


Помните, мы в ы для о б о и х т и п о в ; у ^ qqI — э т о значимые т ипы ,
говорили, что g врем я как List ы Exception
методы и on e- h o w M a n v = 25- принадлеж ат т и п у object,
рат оры ВСЕГДА ^ ^ ^ ,
находятся внутри b o o l S c a r y - t r u e ; Присвоение н а­
классов?Эт о не L i s t < d o u b l e > t e m p e r a t u r e s = n e w L i s t < d o u b l e > (); чальных значение^
совсем так, ведь E x c e p t i o n e x = n e w E x c e p t i o n 'Does n o t c o m p u t e " ) осуществляется
они м о гут нахо- ^ стандарт ным
диться и внутри способом.
ст рукт ур.

О Р а з л и ч и я н а ч и н а ю т с я п р и п р и с в о е н и и з н а ч е н и й . Д ля з н а ч и м ы х т и п о в э та о п е р а ц и я осу­
щ е с тв л я е тс я м е то д о м к о п и р о в а н и я : Значение переменной
г1ПеепМоге копирцет ся й
lnt flfteenMore = howMany; ре м е н н у ю
Изменение зна- fifteenMore -1-= 15; к нему прибавляет ^
ченыя перемен ^ ■
ной Р1Н:еепМоге Console.WriteLine("howMany has {0}, fifteenMore has {!]",
никак не влияет howMany, fifteenMore);
НЙ переменную^,
howM any. Результат д е м о н с т р и р у е т, ч т о п е р е м е н н ы е f i f t e e n M o r e и h o w M a n y ни-
______ ксцсне св я за н ы :
howMany has 25, fifteenMore has 40

О В случае о б ъ е к т о в п р и с в а и в а ю т с я с с ы л к и , а н е з н а ч е н и я :

t e m p e r a t u r e s . A d d (56.5 D ) ;
Q t e m p e r a t u r e s . A d d (27.4 D ) ;
r^List<float> differentList = temperatures;
d i f f e r e n t L i s t . A d d ( 6 2 . 9D) ;
H a O b e ссылки
ы к я з ы б я ю т НД
один объект .
И з м е н е н и е о б ъ е к та L i s t м е н я е т з н а ч е н и я о б е и х
с сы л о к :

Console.WriteLine("temperatures has {0}, differentlist has {1}",


temperatures.Count0, d i f f e r e n t L i s t .C o u n t ());

Р езультат д е м о н с т р и р у е т, ч т о обе с с ы л к и н а ц е л е н ы М ет од
н а один и тот же о б ъ е к т: нГ который ц>^а-
temperatures h a s J., differentList has 3

648 глава 14
смерть объекта

Структуры — это значимые; а объекты — ссылочные типы


С оздавая стр уктур у, в ы создаете значимый тип. Э т о означает,
ч т о о п е р а ц и я п р и с в а и в а н и я п р е д с т а в л я е т с о б о й копирование
стр уктур ы в н о вую перем енную . Так ч т о х о тя структура выгля­
дит к а к об ъ ект, т а к о в ы м о н а н е я в л я е тся . У з 1 ]^ а ж н е н и ^

Создайте структуру Dog


В о т п р о с т а я с т р у к т у р а для х р а н е н и я д а н н ы х о со б а ке . Д о б а вьте ее в новое
консольное приложение.

"р;ьиГ:Г.1Па1; \ А«, кл«« н.


p u b lic s trin g B re e d ; С

p u b l ic D o g ( s t r in g name, s trin g b re e d ) {
th is .N a m e = nam e;
th is .B re e d = b re e d ;
}

p u b lic v o id Speak 0 {
C o n s o le . W r it e L in e ("М еня зовут {0 } и я {1 }.", Name, B re e d );
}
}

О Создайте класс C anine


С к о п и р у й т е с тр у к т у р у Dog, заменив s t r u c t на c l a s s , а Dog — заменив на
Canine. (Н е забудьте п е р е и м е н о в а т ь и к о н с т р у к т о р .) Т е п е р ь у вас е сть класс
C a n i n e , п р а к т и ч е с к и и д е н т и ч н ы й с т р у к т у р е Dog.

О Добавьте метод M ain(), делающий копии Dog и Canine


В о т к о д э т о г о метода:

C a n in e s p o t = new C a n in e ( " S p o t' "pug '


C a n in e b o b = s p o t ; Вы уже работали со
b o b .N a m e = " S p i k e " ;
структурами. Помните Point
b o b .B re e d = " b e a g le " ;
из глав 12 и 13 и DateTime из
s p o t.S p e a k 0 ;
главы 9 ? Это были структуры!
Dog ja k e = new D o g ("J a k e ", "p o o d le ");
Dog b e t t y = ja k e ;
b e tty .N a m e = " B e t t y " ;
b e tty .B re e d = " p it b u ll" ;
ja k e .S p e a k 0 ; возьми В руку карандаш
C o n s o le . R e a d K e y 0 ;

Перед запуском проградАмы...


З а п и ш и т е результат, к о т о р ы й , с в а ш е й т о ч к и з р е н и я , будет в ы в е д е н н а к о н с о л ь :

дальше ^ 649
1 ^ з ь м и в руку карандаш На консоли будет написано:
Решение Му name is Spike and j'm a beagle.
My name is Jake and I'm a poodle.
В от, что произошло...
С с ы л к и b o b и s p o t у к а з ы в а л и н а о д и н и тот ж е объект, Создан о б ьек т
соответственно, м е н я л и о д н и и те ж е ПОЛЯ и об е и м е ­
Canine, на кот орый
т еп ер ь указы вает Spot
л и доступ к м е т о д у S p e a k (). Н о стру к т у р ы р а б о т а ю т ссылка spot.
н е так. Соз д а в структуру bett y , в ы с к о п и р о в а л и в нее
д а н н ы е из струк т у р ы j аке. П р и э т о м друг о т друга эти
струк т у р ы не зависят.

Canine spot = new C a n i n e ("Spot", "pug");(i)

Canine bob = spo t ; Выла создана новая


ссылочная перем енная hob,
bob.Name = "Spike"; но нового объект а в куче не
появилось — перем енны е bob
bob.Breed = "beagle"; и s p o t указы ваю т на один
и т о т ж е объект .
s p o t .S p e a k ( ) ; fg Так как перем енны е s p o t и bob
ук а зы ва ю т на один объект
spot
записи spot.SpeakQ и bob. b o b I Spike
SpeakQ вы зы ваю т один и т о т
ж е м е т о д с одним и т ем ж е
р е зу л ь т а т о м — Spike и beagie.

Dog jake = new Dog("Jake", "poodle")

Dog betty = jake;^^ Создание с т р у к т у р ы


напом инает создание --- poodle I
betty.Name = "Betty"; объект а — вы получает е
п ерем ен н ую для дост упа
betty.Breed = "pit bull"; к полям и м ет одам .
jake
jake.Speak 0

\
Операция присваивания /Добавив
вот и отличие.
перем ен -
Jake
! poodle
А \
j
Jake
poodle
ную ЬеН у, вы полу-,
в случае структур при­ чили новое значение
betty jake
водит к появлению неза­
висимой копшданньк. нГЙКТзГнение ^
Ведь струк™а — это полей с т р у к т у р ы jake. ® ' ' pitBetty
bull
\
j
Jake
poodle
ЗНАЧИМЫМ ТИП. '•■Ч__ . '
betty jake
смерть объекта

Сравнение стека и кучи


сД еной /
П о н я т ь , чем о тл и ч а е т с я с т р у к т у р а о т о б ъ е к та , н е с л о ж н о . Н о к а к о п е р а ц и я
к о п и р о в а н и я в ы гл я д и т и з н у тр и ?
П омнит е, чт о в п р о ­
.N E T C L R п о м е щ а е т д а н н ы е в р а з н ы е о б л а с ти п а м я ти . В ы у ж е знаете, ч т о цессе работы в а ­
шей програм м ы CLR
о б ъ е к т ы ж и в у т в куче. Д р у га я ч а с т ь п а м я т и — стек — х р а н и т все о б ъ я вл я е ­ уп равляет пам ят ью ,
м ы е ва м и в м е то д а х л о к а л ь н ы е п е р е м е н н ы е и п ередаваем ы е э т и м м етодам выделяя м ест о в куче
п а р а м е тр ы . С те к м о ж н о п р е д с т а в и т ь в в ид е н а б о р а м ест, в к о т о р ы е в ы п о ­ и собирая мусор.
м ещ аете з н а ч е н и я . П р и в ы зо в е м етод а C L R д о б а в л я е т в с те к д о п о л н и те л ь -
1ы е м еста. П о с л е з а в е р ш е н и я р а б о т ы о н и удал яю тся.
ГНесмот ря на
возмож ность Вот как выглядит С тек
назначит ь Код ст ек после вы пол-
ст рукт уру п е­
ременной т ипа Это код, который вы можете строк ^ д а ^ находятся структуры
object, с т р у к - обнаружить в программе. и локальны е переменные.
т уры и объекты
от личаю т ся.
Canine spot = new Canine("Spot", "pug");

Dog jake = new Dog("Jake", "poodle");

Canine spot = new Canine("Spot", "pug");

Dog jake = new Dog{"Jake", "poodle");

Dog betty = jake;

При создании новой ст р укт ур ы - или


драгой переменной значимого илилд
в ст еке появляет ся новое мест о. Сюда
копирует ся указанное вами значение.

Canine spot = new Canine("Spot", "pug");

Dog jake = new Dog("Jake", "poodle");

Dog b etty = jak e ;

S p e a k T h r e e T i m e s (j a k e ) ;

public S p e a k T h r e e T i m e s (Dog dog) {


----------------- — ------— _ При Oi?l300t'
вызове мет ода
CLR пом ещ ает его
for (i = 0; i < 5; i++) локальные переменные
наверх стека. И цда-
d o g .S p e a k () ; после заЬер -
J шения работы метода.
дальше > 651
не ограничивайте меня

А зачем мне все это знать? Ведь я же не могу


никак повлиять на эти процессы, разве не так?

И все-таки желательно понимать, как копируемая по значению


структура отличается от копируемого по ссылке объекта.
И н о гд а б ы в а е т н е о б х о д и м о н а п и с а т ь м е то д , к о т о р ы й р а б о та е т и л и со
з н а ч и м ы м т и п о м или со с с ы л о ч н ы м т и п о м . Н а п р и м е р , м е то д , р а б о т а ю ­
щ и й со с т р у к т у р о й D o g и л и с о б ъ е к то м C a n i n e . В э то м случае п р и м е н я ­
е тс я к л ю ч е в о е с л о в о obj ect:

public void WalkDogOrCanine(object getsWalked) { ... }

П е р е д а н н а я э то м у м е то д у с т р у к т у р а упаковывается в с п е ц и а л ь н у ю « о б о ­
л о ч к у » , п о з в о л я ю щ у ю е й н а х о д и т ь с я в куче. В э т о вр е м я р а б о та ть со
с т р у к т у р о й н е в о з м о ж н о . Ее тр е б у е тс я сна ча л а « р а сп а ко ва ть» . К с ч а с т ь ю ,
э т о п р о и с х о д и т автоматически п р и пе р е д а че м е то д у в м е с то о б ъ е к та зна­
ч и м о го типа.
Чтобы определить,
ст рукт ура ли перед
вами или другой зна­
чимый т ип, упако­ В о т к а к с т е к и к у ч а в ы гл я д я т п о с л е со з д а н и я п е р е м е н н о й т и п а
ванный в «оболочку» obj e c t и п р и с в о е н и я е й с т р у к т у р ы Dog.
и помещенный в кучу
используйте клю че­ Dog s i d = new D o g ("S id ", "h u s k y");
вое слом !5-
O b je c t obj = s id ;

- В Л -

Sid
husky /
После упаков­
ки ст рукт уры Dog Sid (упакован)
появляются две
копии данных: —
в стеке и в
куче.
О О б ъ е к т д о с т а т о ч н о п р и в е с т и к п р а в и л ь н о м у ти п у , и о н будет р а с п а к о ­
ван а в т о м а т и ч е с к и . Со значимыми типами использовать ключевое
слово a s нельзя, п о э т о м у п р и в е д е м е го к Dog.
D o g h a p p y ^&=T(Dog)

После этой
строчки вы
получаете
т рет ью ко
паю даш ы^
Pj,empyKmype
карру, которая
получает свое
собственное. Dog sid (упакован)
м ест о в clwKe.

652 глава 14
смерть объекта

сД енои
ъ
Когда вызывается метод, он ищет свои аргументы в стеке.
С т е к и г р а е т в а ж н у ю р о л ь в сп о с о б е , к о т о р ы м C L R запускает в а ш и п р о гр а м м ы . М ы п р и н и м а е м к а к
д о л ж н о е т о т ф акт, ч т о о д и н м е то д м о ж е т в ы з ы в а ть д р у г о й и далее п о ц е п о ч к е . М е т о д м о ж е т даж е
в ы з ы в а ть сам себя (э т о н а з ы в а е тс я рекурсия). Э ту в о з м о ж н о с т ь м ы и м е е т б л а го д а р я стеку.

В о т п а р а м е то д о в и з с и м у л я то ­ p u b lic v o id F e e d D o g (C a n in e dogToFeed, B o w l d o g B o w l) {
ра с о б а ч ь е го п и т о м н и к а . О н и d o u b le e a te n = E a t(d o g T o F e e d .M e a lS iz e , d o g B o w l);
п р о с т ы : м е тод F e e d D o g O в ы ­ re tu rn e a te n + ,0 5 d ; // Н ем ного всегд а разливается
зы ва ет м е тод E a t () , к о т о р ы й
в с в о ю о ч е р е д ь в ы з ы в а е т м е то д
C h e c k B o w l (). p u b lic v o id E a t(d o u b le m e a lS iz e , B o w l d o g B o w l) {
d o g B o w l.C a p a c ity -= m e a lS iz e ;
C h e c k B o w l(d o g B o w l.C a p a c ity );
Н апом ним
}

p u b lic v o id C h e c k B o w l(d o u b le c a p a c ity ) {


if ( c a p a c ity < 1 2 .5d) {
s t r i n g m e ssa g e = "М оя м и с к а почти п у с т а !'
Ш - s s F - C o n s o le .W rite L in e (m e s s a g e ) ;
лдетоЭу-
}
}
В о т ка к в ы гл я д и т стек, когд а ме­
то д F e e d D o g () вы зы ва е т м етод
E a t ( ) , к о т о р ы й вы зы в а е т м етод
C h e c k B o w l О , в ы з ы в а ю щ и й ме­
то д C o n s o l e .W r i t e L i n e ():

Метод F e e d D o g () Q Метод F e e d D o g () О По мере вызова


методов размер
После завершения
метода C o n s o l e .
имеет два параметра: должен передать
методу E a t О два стека увеличивается. W r i t e L i n e О его
ссылку C a n in e
аргумента, которые аргументы удаляются
и ссылку B o w l. При
его вызове в стеке также оказываются из стека. Метод E a t ()
оказываются два в стеке. продолжает работу,
переданных ему как будто ничего не
аргумента. случилось. Вот как
полезен стек!

дальше ► 653
ссылки по запросу

Модификатор out
С у щ е с тв у ю т р а з л и ч н ы е с п о с о б ы п о л у ч е н и я з н а ч е н и й о т п р о гр а м м ы . О н и р е ­
а л и зую тся п р и п о м о щ и д о б а в л е н н ы х к о б ъ я в л е н и ю м етод а модификаторов,
в ч а с т н о с т и , м о д и ф и к а т о р а out. В о т к а к о н р а б о та е т. С о зд а й те н о в о е п р и ­
л о ж е н и е W in d o w s F o rm s и д об авьте к ф о р м е п у с то е о б ъ я в л е н и е метода. О б а
п а р а м е тр а п о м е ть те к л ю ч е в ы м сл о в о м o u t :
Параметр out
p u b lic in t R e t u r n T h r e e V a l u e s (out d o u b l e h a lf, out i n t tw ic e )

re tu rn 1;
дает методу
} возможность
До пе­
П о п ы т а в ш и с ь п о с т р о и т ь ко д , в ы п о л у ч и т е два с о о б щ е н и я о б о ш и б к е :
редачи управления из текущего метода параметру, помеченному ключе­
вернуть бо­
вым словом out, ‘h a lf должно быть присвоено значение (а н а л о ги ч н о для
п а р а м е тр а ‘tw ic e ’ ). Р аботая с к л ю ч е в ы м с л о в о м o u t , в ы всегдад,олжек задавать
лее одного
п а р а м е тр д о в о з в р а щ е н и я м е то д о м з н а ч е н и я . В о т к а к в ы гл я д и т м е то д ц е л и ­
ко м :
значения.
R andom ra n d o m = n e w R a n d o m ( ) ;

p u b l i c i n t R e t u r n T h r e e V a l u e s ( o u t d o u b le h a lf, out in t tw ic e ) {
i n t v a lu e = r a n d o m .N e x t( 1 0 0 0 ) ;
h a l f = ( ( d o u b le ) v a lu e ) / 2 ;
t w ic e - v a lu e * 2; П
t арам ет рам , eпомеченным ГЧ./V
Ul/V\J ключевым
r V / C - U C « '! /V i
r e t u r n v a lu e ; / 'Д Л К Л Д Л
словом o^ u
f .-i- --Ч
t. нужно заранее ^
присвоит ..........
ь
} значения, иначе код ком пилироват ься
не будет . ^
В о сп о л ь зуе м ся з а д а н н ы м и п а р а м е тр а м и . Д о б а вьте к н о п к у со сл е д ую щ и м о б р а б о т ч и к о м с о б ы т и й :

p r iv a te v o id b u tto n l_ C lic k ( o b je c t s e n d e r, E v e n tA rg s e) {

d o u b le b;
in t c;
a = R e tu r n T h r e e V a lu e s ( b , c)
C o n s o le .W r it e L in e ( " v a lu e = {0}, h a lf = { 1}, d o u b le = {2}", b, c) ;

О и ! С н о в а о ш и б к и п о с т р о е н и я ; Аргумент 1 д о л ж е н б ы т ь передан с к л ю ч е в ы м словом out. В п р о ц е с ­


се в ы зо ва м етод а с п а р а м е тр о м o u t н у ж н о и с п о л ь з о в а т ь э т о к л ю ч е в о е с л о в о п р и п е р е д а ч е ему а р гум ен -
та. В о т к а к э т о д о л ж н о в ы гл я д е ть : ^ ^ j

а = R e t u r n T h r e e V a l u e s (out b , ou t с ) ;

Т е п е р ь п р о гр а м м у м о ж н о п о с т р о и т ь и з а п у с ти ть . М е т о д R e t u r n T h r e e V a lu e s () задает и в о зв р а щ а е т
т р и з н а ч е н и я : а п о л у ч а е т в о звр а щ а е м о е з н а ч е н и е м етод а, Ь - з н а ч е н и е , возвр а щ а е м о е п а р а м е тр о м
h a l f , а с - з н а ч е н и е , возвр а щ а е м о е п а р а м е тр о м t w i c e . ^

654 глава 14
смерть объекта

Модификатор ref
Вам с н о в а и сн о в а п р и д е т с я с та л к и в а ть с я с те м , ч т о п р и п е р е д а ч е м е то д у з н а ч е н и й т и п а
i n t , d o u b l e , s t r u c t и л и д р у г о го з н а ч и м о го т и п а в ы ф а к т и ч е с к и п е р е д а е те к о п и ю э т о ­
го з н а ч е н и я . П р о ц е д у р а т а к и н а зы в а е тся ; передача по значению.
Н о а р гу м е н т ы м о ж н о п е р е д а в а ть и м е то д о м , к о т о р ы й н а з ы в а е тс я передача по ссылке.
Э т о р е а л и зуе тся п р и п о м о щ и м о д и ф и к а т о р а r e f . К а к и м о д и ф и к а т о р o u t , о н и с п о л ь ­
зуется п р и о б ъ я в л е н и и и п р и в ы зо в е м етода. З н а ч и м о м у и л и с с ы л о ч н о м у т и п у п р и н а д ­
л е ж и т п е р е м е н н а я , в д а н н о м случае н е и м е е т з н а ч е н и я , п о с к о л ь к у л ю б а я п е р е м е н н а я
с м о д и ф и к а т о р о м r e f будет р е д а к т и р о в а т ь с я н е п о с р е д с т в е н н о м ето д о м .
В отличие от а р ­
Д об авьте к п р о гр а м м е м е то д , ч т о б ы п о с м о т р е т ь , к а к э ^ о р а б о та е т; гум ент а, помечен­
ного м одиф икат о­
public void ModifyAnlntAndButton(ref int value, ref Button button) ром ref, аргумент ,
int i value; помеченный м оди­
ф икат ором out, не
i *= 5; т ребует инициа­
value = i - 3- Задавая значение
? Задавая значение и
иппарам ет рры
а р а м ет кнопки,
ы кнопки,
мет од меняет переменные а и Ь лизации перед его
button = buttonl; в вызвавшем его методе ЬиПопг^Сиск{). переоачей.
}
Д о б а в и м к н о п к у с о б р а б о т ч и к о м с о б ы т и я для в ы зо в а метода;

private void button2_Click{object sender, EventArgs e) { быбоЭытся d? = 4^7,


int q - 100; b.Text = buttonl-, так как
Button b = buttons; Метод поменял Значения
ModifyAnlntAndButton(ref q, ref b); переменных q и b.
Console.WriteLine("q = {0}, b.Text = {1}' q, b .Text);

П р и в ы зо в е о б р а б о т ч и к о м b u t t o n 2 _ C l i c k {) м е то д а M o d i f y A n l n t A n d B u t t o n () п е р е м е н н ы е q и b
п е р е д а ю т с я п о ссы л ке . М е т о д M o d i f y A n l n t A n d B u t t o n ( ) р а б о та е т с н и м и , к а к с л ю б ы м и д р у ги м и п е ­
р е м е н н ы м и . Н о б л а го д а р я пе р е д а че п о с сы л ке о н все вр е м я о б н о в л я е т э т и п е р е м е н н ы е , а н е п р о с т о
к о п и р у е т и х . П о с л е з а в е р ш е н и я м етод а q и Ь будут и м е т ь о т р е д а к т и р о в а н н ы е з н а ч е н и я .

З а п у с ти т е р е ж и м о тл а д к и и д об авьте к п е р е м е н н ы м q и Ь к о н т р о л ь н ы е з н а ч е н и я , ч т о б ы п о н я т ь , к а к всё
рабо тает.

Рассмотрим парамет р out. встроенный в значимый тип. Иногда строку вида ’"5S.&7”
нужно преобразовать в значение типа double. Это можно сделать при помош,и м е ­
тода double.Parse("3S,(p7”). Но запись double.ParseC'xyz“) приведет к исключению
FormatEKception. Иногда требуется именно такой результ ат , а иногда требуется
проверить возможность преобразования строки в значение. Здесь вам пригодится
мет од TryParseQ: запись double.TryParse("xyz'’, out d) вернет значение false и п р и ­
своит парамет ру i значение Oj в то время как запись doub(e.TryParseC'3S.t>7”, out d)
вернет значение true и присвоит переменной d значение 35.<Ь7.
Помните, как 6 главе <? при помощи оператора switch мы преобразовывали Spades
в Suits.Spades? Так вот . сущ ест вую т статические методы Enum.ParseQ и Епит.
TryparseQj которые делают т о же самое!

дальше > 655


необязательные аргументы

Необязательные параметры
Б ы в а е т та к , ч т о м е то д с н о в а и с н о в а в ы з ы в а е тс я с о д н и м и и т е м и ж е а р гу м е н т а м и , н о и н о гд а ем у тр е б у­
е тс я д о п о л н и т е л ь н ы й п а р а м е тр . Н а п р и м е р , п р и н а л и ч и и з н а ч е н и й п о у м о л ч а н и ю а р гу м е н т ы указы ва­
ю т с я , есл и п р и в ы зо в е м етод а ч т о -т о п о м е н я л о с ь .

в та ко й с и т у а ц и и вам н а п о м о щ ь п р и д у т н е о б я з а те л ь н ы е п а р а м е тр ы . В о б ъ я в л е н и и м етод а за т а к и м и
п а р а м е тр а м и следует з н а к р а в е н с тв а и з н а ч е н и е п о у м о л ч а н и ю . К о л и ч е с т в о н е о б я з а т е л ь н ы х парам е­
тр о в м ож ет бы ть произвольны м .

В о т п р и м е р м етод а, в к о т о р о м с п о м о щ ь ю н е о б я з а т е л ь н ы х п а р а м е тр о в п р о в е р я е т с я , н е т л и у ч е л о ве ка
в ы с о к о й тем пературы :

v o id C h e c k T e m p e ra tu re (d o u b le te m p e ra tu re , d o u b le to o H ig h = 99.5, d o u b le to o L o w = 96.5)

i f (te m p e ra tu re < to o H ig h && t e m p e r a t u r e > to o L o w )


C o n s o le .W rite L in e ("Ч увств ую себя х о р о ш о !");
e ls e
C o n s o le . W r it e L in e (" О й -о й ! В ы зовите в р а ч а !"); I
}

Н е о б я з а т е л ь н ы й п а р а м е тр t o o H i g h и м е е т з н а ч е н и е п о у м о л ч а н и ю 99.5 , а н е о б я з а т е л ь н ы й п а р а м е тр
t o o L o w - з н а ч е н и е п о у м о л ч а н и ю 96.5. П р и в ы зо в е м етод а C h e c k T e m p e r a t u r e () с о д н и м а р гу м е н т о м
для п а р а м е тр о в t o o H i g h и t o o L o w и с п о л ь з у ю т с я и м е н н о э т и з н а ч е н и я . Е с л и ж е в в ы зо в е м е то д а ука­
зать два а р гум е н та , в т о р о й а р гу м е н т будет п р и с в о е н п е р е м е н н о й t o o H i g h , в т о вр е м я к а к п е р е м е н н а я
t o o L o w о с та н е т с я с за д а н н ы м п о у м о л ч а н и ю з н а ч е н и е м . Е с л и ж е ука за ть т р и а р гу м е н та , и х з н а ч е н и я
будут п е р е д а н ы всем тр е м п а р а м е тр а м .

Д ля п е р е д а ч и з н а ч е н и я о п р е д е л е н н о м у п а р а м е тр у сущ е ств уе т и та к а я ф у н к ц и я , к а к и м е н о в а н н ы е а р г у ­
м е н т ы . Д о с т а т о ч н о п р и с в о и т ь п а р а м е тр у и м я , указав че р е з д в о е т о ч и е е го з н а ч е н и я .

Д о б а в и м к ф о р м е м е то д C h e c k T e m p e r a t u r e () и к н о п к у с о сл е д ую щ и м о б р а б о т ч и к о м с о б ы т и я . В о с­
п о л ь з у й те с ь п р о ц е д у р о й о тл а д к и , ч т о б ы п о н я т ь , к а к все э т о р а б о та е т:

p riv a te v o id b u tto n 3 _ C lic k (o b je c t s e n d e r, E v e n tA rg s e) Для методов


{
/ / Такова тем пература с р е д н е с та ти сти ч е ско го человека С О З Н Я .Ч С Н И Я ]У в И
C h e c k T e m p e ra tu re (1 0 1 .3 );
ПОумолчанию
/ / Т е м п ер атура с о б а ки должна бы ть м ежду 1 0 0 .5 и 1 0 2 .5 по Ф а р е н ге й ту „ « „ л
C h e c k T e m p e ra tu re (1 0 1 .3 , 1 0 2 .5 , 1 0 0 .5 ) ; llC lIU J lb o y il“

/ / У Б оба в с е гд а слиш ком н и з к а я те м п е р а т у р а , поэтому присвоим Н в О б Я З Я “


/ / п е р е м е н н о й to o L o w з н а ч е н и е 9 5 .5
C h e c k T e m p e ra tu re (9 6 .2 , to 6 L o w : 9 5 .5 ); тельные па-
ф ^ раметры и
именованные
656 ..а..» аргументы.
смерть объекта

Вам не показалось
Типы, gonyckaiouiue значение null странным, что даже
в столбце Client мы до­
пуст или присутствие
H a м и н у т у ве р н е м ся к к а р т о ч к а м с к о н т а к т н о й и н ф о р м а ц и е й , к о т о р ы е в пустых значений? А ведь
главе 1 с та л и о с н о в о й для базы д а н н ы х . П о м н и т е , к а к и м о б р а зо м в ы б и р а ­ человек может или быть
л и с ь п а р а м е тр ы т а б л и ц ы , ч т о б ы п о з в о л и т ь п р и с в о е н и е в к а ж д о м с то л б ­
клиентом или не быть
им, не так ли? Но нет
це з н а ч е н и я n u ll н а сл уч а й, е с л и и н ф о р м а ц и я н е будет введена и л и будет никакой гарантии, что
введ ена н е к о р р е к т н о . К с о ж а л е н и ю , стр уктур а м и д р у ги м зн а ч и м ы м т и п а м поле Client заполнено на
всех карточках. Соот­
н е в о зм о ж н о п р и с в о и т ь зн а ч е н и е n u ll. В о т та к и е о п е р а то р ы . ветственно, значение
null требуется на случай,
bool myBool = null; когда мы vwocmo не зна­
DateTime myDate = null; ем, какие данные вводить.
на с т а д и и к о м п и л я ц и и с та н у т п р и ч и н о й с о о б щ е н и я об о ш и б к е !
N ullable<D ateTim e>_
П р е д п о л о ж и м , вам н у ж н ы е д а н н ы е о дате и в р е м е н и . Д ля э т о г о и с п о л ь ­ Value: DateTime
зуется п е р е м е н н а я D a t e T i m e . Н о ч т о делать, е с л и е й н е в о в се х сл уч а ях HasValue: bool
тр е б у е тс я п р и с в а и в а т ь зн а че н и е ? В о с п о л ь з у й те с ь т и п а м и , д о п у с к а ю щ и м и
з н а ч е н и е n u ll. Д о с т а т о ч н о п о с т а в и т ь з н а к (?) п о с л е у к а з а н и я т и п а ;
GetValueOrDefault(): DateTime
bool? myNulableInt = null;
DateTime? myNullableDate = null;
С в о й с т в о V a l u e ука зы в а е т н а т и п п р о в е р я е м ы х з н а ч е н и й . К п р и м е р у, для
D a t e T i m e ? с в о й с т в о V a l u e р а в н о D a t e T i m e , для i n t ? - с о о т в е т с т в е н н о in t
и т. п . С в о й с т в о H a s V a l u e в о з в р а щ а е т з н а ч е н и е tr u e , е с л и п а р а м е тр н е р а в е н n u ll. Стриктура
Ыи[1аЫе<Т> позво­
З н а ч и м ы й т и п всегда м о ж н о п р е о б р а з о в а ть к ти п у, д о п у с к а ю щ е м у з н а ч е н и е n u ll: ляет хранить как
значим ы е типы, так
DateTime myDate = DateTime.Now; и значение п^лИ. На
DateTime? myNullableDate = myDate; диаграмме вы оиди
те методы и свой­
H o об ратное преобразование сопровож дается о п е р а ц ие й привед ения; ства с т р у к у щ р ы
ЫullableФaterlme>.
myDate = (DateTime) myNullableDate;

Е сл и с в о й с т в о H a s V a l u e и м е е т з н а ч е н и е false, с в о й с т в о V a l u e в ы з ы в а е т и с к л ю ­
ч е н и е I n v a l i d O p e r a t i o n E x c e p t i o n , к а к и о п е р а ц и я п р и в е д е н и я (ведь о н а
п р о и з в о д и т с я с и с п о л ь з о в а н и е м с в о й с т в а Value).

После добавления к любому к а к ст р ук т у-


decimal?) компилятор Д о ^ й б ь т е к п р о г р а м м е переменную
ру Nulkble<T> (Nu{lahle<,nt> ™ м создайте контрольное значение.
Nullable<PateTime>. помест итье пример псевдонима (alias). Наведите
В окне w a t c h отобразится преобразовано в ст рукт уру System .lnt3Z :
курсор на любое значение типа mt. Оно дуает пре.^^ у
Struct System. Int32
■Represents a 32-bit signed integer.
intParseO и intrryParseO - илени э т о й ст рукт уры ................. ........

дальше ► 657
почувствуй вкус надежности

Типы, допускаюи^ие значение null, убеличиВают робастность программы


П о л ь з о в а т е л и п о р о й д е л а ю т сум асш ед ш ие в е щ и . О н и м о гу т щ е л к а ть н а к н о п к а х в А ^Ш ив ш -
н е в е р н о м п о р я д к е , в в о д и т ь п о 256 п р о б е л о в в т е к с т о в о е п о л е и л и п р и п о м о щ и Д ис-
п е т ч е р а задач п р е р ы в а т ь п р о гр а м м у н а се р е д и н е п р о ц е с с а з а п и с и в ф айл. В главе 10 B%thdau\/aluf^^n/;
м ы г о в о р и л и о т о м , ч т о п р о гр а м м ы , у м е ю щ и е о б р а б а т ы в а т ь н е к о р р е к т н ы е д а н н ы е внимание
н а з ы в а ю тс я р о б а с т н ы м и (r o b u s t) . У в е л и ч и т ь р о б а с т н о с т ь п р о гр а м м ы п о з в о л я ю т ё '’ IntelliSense.
и т и п ы , д о п у с к а ю щ и е з н а ч е н и е n u ll. У б е д и те с ь в э т о м сам и: с о з д а й т е к о н с о л ь н о е '
ТТГ)ИЛГП'Ж'^‘НИ<^
п ттг\?ча
р и л о ж е н и е JиA доб 1эх.т '^1 т>
авьте -------
в н е го класс R e b ______
u s ^tG uy:

c la s s R o b u s tG u y {
p u b lic D a te T im e ? B i r t h d a y { g e t; p riv a te s e t;
p u b lic in t ? H e ig h t { g e t; p riv a te s e t; }
Ш ую строку.
p u b lic R o b u s tG u y (s trin g b irth d a y , s trin g h e ig h t) |
D a te T im e te m p D a te ; ilfT c k s
Восполь­ T meOfDay
зуйтесь if (D a te T im e .T ry P a rs e (b irth d a y , out te m p D a te ))
типами B ir t h d a y = te m p D a te ; ToBinary
РліеТіте e ls e Ф ToFileTime
и int 6 B irth d a y = n u ll; ToFHeTimeUtc
методе ToLocalTlme
■• jc -ji in t te m p in t;
~ out te m p in t) _____
Ф ToLongTimeString
e t"» ^
польза- e ls e
-# ToO ADate
Ф ToShortDateString
вателем H e ig h t = n u l l ;
данных Ъ Ф ToShortTimeStnng
значения . , , . j -# ToString
p u b lic o v e r r id e s t r in g T o S trin g O { : # ToUniversalTime
s tr in g d e s c rip tio n ; Year
^ ^ i f ( B ir t h d a y != n u l l )
При вводе d e s c rip tio n = "Я р о д и л с я + B i r t h d a y . V a l u e . T o L o n g D a t e S t r i n g () ;
некоррект - e ls e
н и х дан­
ных метод d e s c r ip t io n - "Я не знаю д а т у с в о е го р о ж д е н и я ";
HasValueQ i f ( H e ig h t != n u l l )
Поэкспериментируй -
вернет зна d e s c r ip t io n += во мне " + H e ig h t + " дюймов р о с т а " ; те с другими м е ­
чение False. e ls e тодами, связанными
d e s c r ip t io n += я не знаю свой рост'
с т ипом DateTime,
re tu rn d e s c rip tio n ;
которые начинаются
с То, чтоды понять,
как они влияют на
конечный результ ат .
А в о т ко д м етод а M a in () .О н и с п о л ь з у е т м е то д C o n s o le . ReadLine () для ввода д а н н ы х :
s ta tic v o id M a in ( s t r i n g [] a rg s) {
C onso le . W r i t e (" У к а ж и т е д а т у р о ж д е н и я :, Запустите программу
s trin g b ir t h d a y = C o n s o le . R e a d L in e ( ) ; и попробуйте вводить различные
C onso le .W r ite ("У ка ж и те р о с т в дюймах: ') ; данные. Многие из них будут
s trin g h e ig h t = C o n s o le . R 6 a d L in e ( ) ;
распознаны методом D ateT im e.
R o b u s tG u y g u y = new R o b u s tG u y ( b ir th d a y , h e ig h t);
C o n s o le .W rite L in e (g u y . T o S tr in g ( ) ) ;
T ryP arse О . Если сделать это
C o n s o le . R e a d K e y ( ) ; не удастся, свойство B ir th d a y
класса RobustGuy не будет иметь
^ Метод Console.ReadLineQ позволяет пользователю
вводить информацию в консольное окно. После нажа­ значения.
тия клавиили Enter метод возвращаем строку.
смерть объекта

Б бассейне
p u b lic T a b le {
Возьмите фрагменты кода из бассей­ p u b lic s trin g s ta irs ;
на и заполните пробелы. Один и p u b lic H in g e f l o o r ;
тот же фрагмент может исполь­ p u b lic v o id S e t(H in g e b) {
зоваться несколько раз. В бас­ flo o r = b;
сейне есть и лишние фрагмен­ }
ты. Получите код, выводящий p u b lic v o id L a m p (o b je c t o il) {
на консоль запись «вернусь i f ( o i l ______ in t)
через 20 минут» при со зд а н и и
___________ . b u l b = (in t) o il;
э кз е м п л я р а кл а с с а Faucet:
e ls e i f ( o i l ______ s t r i n g )
s ta irs = ( s tr in g ) o il;
p u b lic c la s s Faucet {
p u b l i c F a u c e t () { e ls e i f ( o i l ______ H i n g e ) {
T a b le w in e = new T a b le ( ) ; ___________ v i n e = o i l ______ ,
H in g e b o o k = new H in g e ( ) ; C o n s o l e . W r i t e L i n e ( v i n e . T a b l e ()
w in e . S e t(b o o k ) ;
+ " " + __________. b u l b + " " + s ta irs )
b o o k .S e t(w in e );
w in e .L a m p (1 0 );
b o o k .g a rd e n .Lam p{"b a c k in " );
b o o k .b u lb *= 2;
w in e .L a m p ("m in u te s " );
w in e . L a m p (b o o k); p u b lic H in g e {
p u b lic i n t b u lb ;
p u b l ic T a b le g a rd e n ;
}
p u b l i c v o id S e t (T a b le a) {
g a rd e n = a;
}
При создании объекта Faucet p u b lic s trin g T a b le 0 {
появляется строка: re tu rn ___________ . s t a i r s ;
}
С^ а с к i n 20 m in u te s ^
Дополнительное задание;
Обведите строчки, в которых
получить- происходит упаковка.

Ф р агм ен ты
можно ис­
по л ь зо в а ть public
бо л е е о д н о го private if
раза. class or g ard en
new is flo o r s tru c t
Brush a b s tra c t on W in dow
Lam p string
interfac e as D oor int
bulb oop H inge
Table flo a t
stairs single
double

QrnBem Ha c. 668 -

дальше >
надежность структур
Часто

^аД аБ аеМ ы е
B o n j^ o C b i Также вы сталкивались с такой полезной
Какая мне разница, что происходит
в стеке? структурой как S i z e . С ее помощью
fi ! Это поможет вам, к примеру, при
вы определяли размер строки в методе
I нкапсуляции. Посмотрите на уже
MeasureStringО .
S Так как понимание различий между знакомый вам код класса, определяющего
стеком и кучей позволяет корректно местоположение:
пользоваться ссылочными и значимыми Как определить, что мне нужно в
типами. Легко забыть, что структуры и p r iv a te P o in t lo c a tio n ; текущий момент — класс или структура?
объекты функционируют по-разному, ведь p u b lic P o in t L o c a tio n {
операция присваивания для них выглядит get { re tu rn lo c a tio n ; ) ^ ! в большинстве случаев програм­
одинаково. Представление о том, какие } мисты работают с классами, потому что
процессы происходят в .NET и CLR по­ Если бы P o i n t был классом, инкапсу­ структуры имеют слишком много ограни­
зволяет понять, чем именно отличаются ляция не сработала бы. Закрытость поля чений. Они не поддерживают наследова­
ссылочные и значимые типы. l o c a t i o n не имела бы значения, ведь ние, абстракции и полиморфизм, а вы уже
вы создали открытое, предназначенное знаете, насколько важны эти вещи при
А зачем нужна информация по по­ только для чтения свойство, возвращаю­ создании больших приложений.
воду упаковки? щее ссылку на это поле, то есть дали
Структуры же полезны при повторяю­
доступ другим объектам.
щейся работе с ограниченными типами
Q ; Потому что нужно понимать, когда К счастью для нас, P o i n t — это струк­ данных. Хорошим примером служат
действие переходит в стек, и когда тура. И открытое свойство L o c a t i o n прямоугольники и точки — они применя­
данные начинают копироваться туда возвращает копию переменной. Работа­ ются только в определенных ситуациях,
и обратно. Упаковка требует места в ющий с ней объект может делать, что хо­ зато с удивительной регулярностью. При
памяти и занимает время. Разумеется, чет — это никак не скажется на состоянии наличии у вас небольших групп разнород­
вы не заметите особой разницы, если ных данных, которые требуется сохранить
закрытого поля l o c a t i o n .
эта процедура выполняется редко. Но в поле или передать методу в качестве
представьте программу, выполняющую параметра, скорее всего, имеет смысл
однотипные действия много раз в секунду, Если P o i n t — это структура, то, воспользоваться структурой.
как это делал, к примеру, симулятор улья. может быть, я уже работал и с другими
Работа такой программы будет требовать структурами, сам того не зная?
все больше ресурсов, программа будет
замедляться, поэтому, наверное, имеет ; Да! При изучении графики вы стал­
Структура позво­
смысл избегать упаковки в часто повто­
ряющейся части кода.
кивались со структурой R e c t a n g l e .
Она снабжена полезными методами,
ляет улучшить ин­
позволяющими указать границы области,
Я понял, что при операции при­ и проверит, попадает ли в них выбранная капсуляцию класса,
сваивания одна структурная перемен­ точка. Достаточно указать местополо­
ная копируется в другую. Но как я могу жение и размер структуры, и программа так как возвраща­
использовать эти знания? автоматически рассчитает ее остальные
параметры. ющее ее, предна­
Возьми Вруку карандаш значенное только
Предполагалось, что этот метод убьет объект
для чтения свой­
Clone, но он не работает Почему?
ство, всегда созда­
p r i v a t e v o id S e tC lo n e T o N u ll(C lo n e c lo n e ) {
c lo n e = n u l l ;
ет новую копию.
-- О т в ет на с. 6 6 2 .
смерть объекта

Ч то осталось о т Великолепного
П о с л е р а з го в о р а о б у п а к о в к е , в ы д о л ж н ы с о о б р а ­
зить, почем у ка п ита н В е л и ко л е п н ы й п о те р я л свою л е г­
В озм ож ность
ко получать копии
силу. В се дело в т о м , ч т о э т о у ж е н е о н , а у п а к о в а н ­ является боль­
ная с т р у к т у р а шим преим ущ е­
ством ст рукт ур
и других значимых
типов.
; s tг u c t I сравнение

Структуры не наследуют Вы не можете создать копию


от классов и не реализуют объекте
интерцзейсов в процессе оп е р а ц и и присваивания
И м е н н о п о э т о м у к а п и т а н т а к ослабел. вы копируете ссылку н а ту же самую
В едь о н б о л ь ш е н е наслед ует н у ж н о г о перем енную .
поведения. © Вы можете пользоваться
О Структуры копируются по значению
Э т о о д н о и з с а м ы х б о л ь ш и х и х п р е им у-
ключевым словом аз
О бъ екты м о гу т ф ункционировать,
ш;еств, н е з а м е н и м ы х для и н к а п с у л я ­ к а к и х р о д и т е л и , т о е сть для н и х д о ­
ции. пустим полим орф изм .
расширь это
Помните модификатор доступа sealed из
. главы 7? С его помош,ью создаются классы,
Методы расширения не допускающие расширения.

И н о гд а тр е б уе тс я р а с ш и р и т ь класс, о т к о т о р о г о н е в о з м о ж н о н а сл е д о в а н и е (к п р и м е р у, м н о ги е классы
•N E T п о м е ч е н ы м о д и ф и к а т о р о м se a le d ). Н а э т о т с л уч а й в C # и м е ю т с я м е т о д ы р а с ш и р е н и я (extension
methods). О н и п о з в о л я ю т добавить м е т о д ы в с у щ е с т в у ю щ и е классы. В ам н у ж н о т о л ь к о создать ста ­
т и ч е с к и й класс и д о б а в и т ь туда с т а т и с т и ч е с к и й м е то д , в к а ч е с тв е п е р в о го п а р а м е тр а п р и н и м а ю щ и й
эк з е м п л я р э т о г о класса.

П р е д п о л о ж и м , у вас е сть п о м е ч е н н ы й м о д и ф и к а т о р о м s e a l e d класс O r d in a r y H u m a n :

О т класса OrdinaryHuman (Обычный человек)


s e a j^ c la s s O rd in a ry H u m a n { ^ невозможно наследовать^. Но что если
p riv a te in t age; добавить в него метод?
in t w e ig h t;

p u b lic O rd in a ry H u m a n (in t w e ig h t){


“ « .» e ig h t -

I моад к лт е ’вогс Z t ilT s^ при no -

p u b lic v o id G oT o W orkO { /* код похода на работу * / } J


p u b lic v o id P a y B ills O { /* код оплаты счетов * / } Р асш и ш т ь и

' “О
•Д обавим м ето д р а с ш и р е н и я S u p e r S o ld i e r S e r u m (С у п е р с о л д а т ); ^ Словом
s ta tic c la s s S u p e rS o ld ie rS e ru m {
p u b lic s j^ tic s trin g B r e a k W a l l s (this OrdinaryHuman h > d o u b l e w a llD e n s ity ) {
re tu rn ("Я сломал стену плотностью " + w a llD e n s ity + ".");

T Z Z lltT a & S S r r
^0 тек nop, пока у него
П р и д о б а в л е н и и класса S u p e r S o ld i e r S e r u m класс O r d in a r y H u m a n доступ к классу
п о л у ч а е т м е то д B r e a k W a ll s , к о т о р ы й м о ж е т и с п о л ь з о в а т ь с я ф о р м о й : ^^^P^'^°^d.ierSerum.

s ta tic v o id M a in ( s t r i n g [] a rg s) ( эт о }делат Ы
O rd in a ry H u m a n s te v e = new O rd in a ry H u m a n (1 8 5 ) ; ><0ИС0ЛЬН0е прилож е-
c o n s o le . W r ite L in e (S te v e .B re a k W a lls (8 9 .2 ) ) ;
ла(7ки и посмот рит е, что про-
, ^ з ь м и в руку карандаш_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ вплщйо.
Рршацир Почему этот метод не уничтожил объект C lo n e ?
p r i v a t e v o i d S e t C lo n e T o N u ll (C lo n e c lo n e ) {
Так как парам ет р Clone c lo n e = n u l l ;
находится в стеке, j
присвоение ему значения
nuil никак не скажется мет ра п^исоаирм nuti, но данный
на состоянии к у Ч (^ ^ парамет р является всего лишь ссылкой на объект Clone.

662 глава 14
смерть объекта
Часто
<аДаБаеМые
В опросы

Почему бы вместо методов расширения не добавить Q ; Если вы можете расширить класс, это нужно сделать —
код нужного метода непосредственно в класс? методы расширения не являются заменой наследования. Но
существуют и классы, для которых это невозможно. Методы
расширения позволяют менять поведение групп объекгов
I ! Именно так и нужно поступать, если речь идет о до­ и даже добавлять функциональность к базовым классам .NET
бавлении метода в один класс. Методы расширения следует
Framework.
использовать аккуратно и только в случаях, когда по каким-то
При этом, чтобы воспользоваться новым поведением, нужно
причинам вы не можете отредактировать нужный вам класс
работать с новым производным классом.
(например, потому что он является частью .NET Framework).
Методы расширения таюке применяются для редактирования
поведения сущностей, ».который отсутствует доступ, I Методы расширения влияют на все экземпляры
например, типа или объекта из .NET Framework или другой 1асса или только на некоторые?
библиотеки.
Г ; Они влияют на все экземпляры расширенного вами
класса. Более того, созданный метод расширения будет по­
А зачем нужны методы расширения, если класс
можно расширить при помощи наследования? казываться ИСР вместе с обычными для рассматриваемого
кпасса методами.

Н мжно пОАЛнить, ч т о
метод расширения не
Я понял! Методы расширения позволяют дает доступа к в н у ­
т р е н н е м у коду класса.
отредактировать поведение встроенных
классов .N ET Framework.

Именно так! Есть классы, от которых вы не можете наследовать.


О т к р о й т е л ю б о й п р о е к т и в в е д и те в л ю б о й класс в о т т а к о й код:

c la s s X : s trin g { }

П р и к о м п и л я ц и и п о я в и т с я с о о б щ е н и е об о ш и б к е . П о т о м у ч т о н е к о т о р ы е
к л а ссы .N E T п о м е ч е н ы м о д и ф и к а т о р о м s e a le d , з а п р е щ а ю щ и м н а сле д о ва н и е .
М е т о д ы р а с ш и р е н и я д а ю т в о з м о ж н о с т ь п о м е н я т ь п о в е д е н и е т а к о г о класса.

Н о этим и х ф ун кц иональность не и счерпы вается. О н и п о зво ляю т расш и рять


и н т е р ф е й с ы . Д о с т а т о ч н о п о с л е к л ю ч е в о го слова t h i s п о д с т а в и т ь и м я и н ­
те р ф е й с а в м е с то и м е н и класса. В р е зультате м е то д р а с ш и р е н и я д о б а вля е тся
в о в с е к л а с с ы , р е а л и з у ю щ и е д а н н ы й и н т е р ф е й с . П о м н и т е ко д L IN Q , д о ­
б а в л е н н ы й к с и м у л я т о р у в главе 12? L IN Q , б ы л создан п у те м р а с ш и р е н и я и н ­
т е р ф е й с а I E n u m e r a b l e < T > . ( Н о о б э т о м м ы п о г о в о р и м в сл е д ую щ е й главе.)
Надеюсь,, вы
помните, что
обычным образом
наследовать от
интерфейса невоз­
можно?

дальше У 663
лучше быстрее сильнее

Расширяем фундаментальный тип: string


Н е о б х о д и м о с т ь м е н я ть п о в е д е н и е т а к о г о ф у н д а м е н та л ь н о го т и п а
к а к s trin g s в о з н и к а е т н е ч а с т о . Н о в ы в п о л н е м о ж е т е э т о сделать! С оз­
д а й те н о в ы й п р о е к т и доб авьте к н е м у ф айл H u m a n E x t e n s i o n s .cs.

О Поместим методы расширения в отдельное пространство имен


С о х р а н и т ь р а с ш и р е н и я о тд е л ь н о го о т о с н о в н о г о ко да — х о р о ш а я идея. Э т о
п о з в о л и т л е гк о о б н а р у ж и т ь и х п р и н е о б х о д и м о с т и :
Отдельное пространство имен это хо-
> рош ий о р г а н и з а ц и о н н ы й и н с т р у м е н т .
namespace MyExtensions { ^ г г ’ i^
public static class HumanExtensions {
Методы расширения должны н а ­
ходиться в статическом к л а с с е .
О Создайте статический метод расширения, определите его первый
параметр ключевым словом t h is и укажите, что вы расширяете
О б ъ я в л я я м е то д р а с ш и р е н и я , у к а ж и т е р а с ш и р я е м ы й и м класс в ка че с тв е п е р ­
в о го п а р а м е тр а . .
«г--------- -
________ чтп г
public static bool IsDistressCall { t h i 7 ~ s t r i n g ' S) { Х-^асс
--------------- Метод расширения также
О Поместите в метод код. оценивающий строку должен быть статическим.
public Static class HumanExtensions {
public static bool IsDistressCall(this string s){
Нам нужен доступ i f ( s .C o n t a i n s ( " П о м о г и т е !"))
к эт ому классу r e t u r n true;
из других про­
странст в имен, ^ ^ ЗЭесь проверяется, не содержит
поэт ому и с п о л ь ­ return false; ли строка определенного значения
зуй т е м одиф ика­ } которое от сут ст вует в исходном
т о р p ub lic!
}
классе string.
Создайте форму и добавьте к ней строку
В п и ш и т е в в е р х н ю ю часть кода ф о р м ы с тр о ч к у u s i n g M y E x t e n s i o n s ; . К ф орме добавьте
к н о п к у М ы п р о в е р и м д е й ствие н а ш е го метода р а с ш и р е н и я в н у т р и о б р а б о тч и к а со б ы тия .
Т еперь п р и работе с классом s trin g в ы п о л уч и те д оступ к методу р а сш и р е н и я . В ведите им я
с т р о к о в о й п е р е м е н н о й и п е р и о д и убедитесь сами:

string message = "Клоны разрушают фабрику. Помогите!";


message.
Сразу после ввода ^ Ф IndexOf

точки появится окно Ф IndexOfAny


Insert чезнет и з о к н а Inte lliS en se.
с перечнем методов % lo t e r s e c t o
класса string... в их
число включен и в а ш -^ • % d Z
Ф bN o rm a lired
= l
Этот пример продемонстрировал вам
метод расширения. ^4 )oin<> синтаксис методов расширения. В сле­
% la s to
LastlndexOf
дующей главе вы получите намного
LastlndexOfAny больше информации о них. Ведь имен­
^4 L astO rD efaulto но при помощи этих методов реализо­
Length
% LongC oont< > ван LINQ.
% Мг«<>
664 глава 14
смерть объекта

^ /[а Г н и ш ы J ^ a c m u J ’ e H u ff

Расположите магниты таким образом, чтобы на выходе получилась


строка:

а buck begets more bucks (деньги к деньгам)

using Upside;
namespace Upside { namespace Sideways {

public static class Margin { |


class Program {
public static void Sendit
)


public static string ToPrice

public static string Green

" if In == 1) b .Green().Sendit(); static void Main(string[] args) {


return "a buck strxng s = i.ToPriceO ;
Д .Sendit0 ; J

дальше * 665
капитан жив!

^ а Г н и ш ы ]= » а с :Ш и р * е н и я

Вот как нужно было расположить магниты, чтобы получить


на выходе поговорку:
а buck begets more bucks

Расширения содерж атся


в п р о ст р а н ст ве имен Upside. К л а с с Margin расширяет строку пут ем
Точка входа находится б п р о ­ добавления метода SendltQ, который выво -
ст р а н ст ве имен Sidew ays. дит содержимое строки на консоль. Тип int
он расширяет при помощи метода ToPriceQ
возвращающего значение а buck при равенстве
целой переменной X и тоге bucks в остальных
случаях.
namespace Upside {
3
Точка входа использует
p u b ^ ^ s t a ti^ c la s s ite x ^ n расширения, добавленные
Margin.
в класс Marqin.
*^ ^ u b ^ ^ ^ t a ^ ^ v ^ d Send^

using Upside;
namespace Sideways {

class Program {
I

рглЬ11с static string Green (this bool b) {

if (b == true)
Метод Green расширяет
return "be" класс bool — OH возбраила-
else em ст р о к у be, если б ул ев­
ская перем енная и м еет
return "gets' / значение true, и g e t —

\ »
У в случае значения false.

Здесь класс Margin расширяет


булевы переменные пут ем до-
я вле н и я к ним класса QreenQ-

666 глава 14
м ы П ЕРЕ С ТРО И Л И КЛАСС
SU PER H ER O . Н О КАК ВЕРНУТЬ
Я проанали зировала\ .
КАПИТАНА НАЗАД?
КОД: ВЕЛИКОЛЕПНЫЙ \
и спол ьзовал св о ю ]

Смерть — это не конец!


Статья Баки Барнса
корреспондента В С Е Л Е Н Н О Й
О бъектвиль

Д ~ .р „ ™ ,ц „ я „ В е л н к о л е „„„„

в прошо?|1™Ль“" » Обмтиль.
« м о казал ась стр а„„а^ Т „ “ а ^ ™
екта C aptain А ш а" in g - Д Н К с б .-
ДВОИЧНОМ формате. ** значения, записанные в

исто чн и ке “ " Р ” ' °<*

= Г т—
Гв™ елГ“ ^ Великолепный вернулся!
решение ребуса

і^еїИение р*е^са Б ёаосейне

p u b lic Struct T a b l e {

p u b lic s trin g s ta ir s ;

p u b lic H in g e flo o r;

p u b lic v o id S e t(H in g e b) {

м . » » г и т ) » 0 ^ г « ' - / » п р Г в “« о в Ж flo o r = b;
м п е р е т н н т ^1 м и ^ ■ f ^ кйчестб е п я -
цглочисленнои g ^оде Bulb ссыл- }

“*Т*” p u b lic v o id L a m p (o b je c t o i l ) {

Результат после создания Метод Lamp


i f (o il is i n t ) •помещает
объекта Faucet: »переданную
b a c k i n 20 m in u te s flo o r . b u l b = (in t) o i l ; Ш у строку
о поле Stairs.
p u b lic c la s s Faucet { e ls e i f ( o i ljs . s trin g )
І
p u b lic Faucet 0 { s ta irs = ( s t r i n g ) о И ;"^

T a b le w in e = new T a b le {); e ls e i f (o il is H i n g e ) {

H in g e book = new H in g e ( ) ;
Hinge v i n e = o il as H inoe;
w in e . S e t(b o o k );
C o n s o l e . W r i t e L i n e ( v i n e . T a b l e ()
b o o k .S e t(w in e );
" "•+ flo o r - b u l b + " " + s ta irs );
V w i n e . Lam p П ;
}
C^^^ o o k . g a r d e n . L a m p ( " b a c k ir? ^ T T ~ -^
Помните, что J
b o o k .b u lb *= 2; ключевое слово as
работает только
(wine . L a m p ( " m in u te s ^ p; ^ с классами? Класс Hinge
и ст рукт ура Table
w in e . L a m p (b o o k); обладают м ет о ­
p u b l i c class H i n g e { дом SetQ. В классе
) , , . ^ Hinae эт от r e ­
Bom почему Table - это p u b lic i n t b u lb ; mod задает поле
iJP y x m y p a . Если бы это был кла-^г p u b lic T a b le g a rd e n ; j[ т урьГт М е.^ А у
Ч ^°°^-^лгоіеп " ^
p u b lrc v o rd se t (T a b le a"; ( Т е^ ^ Т зШ ^ ^ Г
g a rd e n = a; F^00r в классе
Hinge.
Обведены строчки, в которых ^ }
происходит упаковка. ^ p u b lic s trin g T a b le 0 {

Метод LampQ использует в качестве


параметра ооьект. Значит, упаков­ re tu rn g a rd e n . s t a i r s ;
ка будет автоматически осущ ест ­ }
вляться при передаче ему значений
типа int или string.

668 глава 14
^5LINQ
Управляем данными

Этот мир управляется данными...


вам лучше знать, как в нем жить.
В рем ена, когда можно б ы л о п р огр ам м и ровать дн ям и и д аж е неделями, не
касаясь множества данных, д а в н о позади. В н аш и дни с данными связано
все. Ч а с то приходится р аб отать с д а н н ы м и из р азн ы х источников и даж е раз­
ны х ф орм атов. Б а зы данных, X M L , коллекции из других программ... все это
д а в н о с та л о ч ас тью р аб оты програм м и ста на С#. В этом ем у пом огает LIN Q .
разбивать данные на
Э т а ф ункция не только у п р о щ а е т запросы , но и ум еет
группы и, наоборот, соединять данные из различных источников.
дьявол в деталях

Простой проект...
О б ъ е к т в и л ь с к а я к о м п а н и я п о п р о и з в о д с т в у б ум а ги р е ш и л а сделать с о в м е с тн у ю
п р о м о -а к ц и ю с к о ф е й н ы м м а га з и н о м . Э т о т м а га з и н и м е е т с п е ц и а л ь н у ю п р о гр а м м у,
о т с л е ж и в а ю щ у ю , к а к о й ко ф е п о к у п а е т к а ж д ы й к л и е н т и к а к ч а с т о о н э т о делает.
Б ум аж ная к о м п а н и я х о те л а б ы у зн а ть, кто из ее клиентов регулярно посещает ко­
ф е й н ы й магазин, ч т о б ы о т п р а в и т ь и м б е с п л а тн у ю к р у ж к у и к у п о н н а п о к у п к у и х
л ю б и м о го ко ф е ... Вам н у ж н о с к о м б и н и р о в а т ь д а н н ы е и создать с п и с о к к л и е н т о в
для р а с с ы л к и к р у ж е к и к у п о н о в .

Клиенты бумажной компании,


регулярно посещающие кофейный
магазин, получат бесплатную кружку
Скажите только, кому разослать эти
кружки и какие сорта кофе эти лю ди
предпочитают.

670 глава 15
UNQ

...НО сначала ну)кно собрать данные


К о ф е й н ы й м а га зи н S ta rb u zz х р а н и т д а н н ы е в кла сса х, с г р у п п и р о в а н н ы х в б о л ь ш о й с п и с о к List. А в о т
б ум аж н ая к о м п а н и я и м е е т базу д а н н ы х (с о з д а н н у ю в главе 1). Н у ж н о н а й т и п о с е т и т е л е й м а га зи н а , п о ­
т р а т и в ш и х б олее $90, с р а в н и т ь со с п и с к о м к о н т а к т о в б у м а ж н о й к о м п а н и и и с ф о р м и р о в а т ь с в о й с п и ­
с о к , в к о т о р о м будут у к а з а н ы с л е д ую щ и е д а н н ы е ; имя человека, фирма, где он работает, его любимый
сорт кофе.

Данные магазина в коллекции List<T>



П р о гр а м м и с т ы и з S ta rb u zz н а п и с а л и п р о ­ c la s s S ta rb u z z D a ta
грамм у, св я з а н н у ю с и х с а й то м , и п о м е с т и л и {
все д а н н ы е в с п и с о к L i s t < S t a r b u z z D a t a > . p u b lic s t r i n g N am e { g e t ; s e t ; }
p u b lic D rin k F a v o r ite D r in k { g e t; s e t; }
p u b lic i n t M oneySpent { g e t; s e t; }
p u b lic in t V is it s { g e t; s e t; }

Эт о класс м
»^.еречисленме из
1л.йогрйММК>| ко enum D r in k {
т фе-йного м а га зи ­ B o rin g C o ffe e ,
на Starbuzx- C h o c o R o c k o L a tte ,
T rip le E s p re s s o ,
З а м н у ж н о получить Z e s ty L e m o n C h a i,
данные о т кофеино D o u b le C a p p u c c in o ,
го м агазина и наити H a lfC a fA m e ric a n o ,
т а м клиент ов, к о ­ C h o c o M a c c h ia to ,
т оры е т а к ж е пользу - B a n a n a S p litln A C u p ,
ю т ся услуга м и б у ­
мажной компании. }

У вас уже есть данные о заказчиках


С п и с о к к о н т а к т о в для о б ъ е к т в и л ь с к о й б у м а ж н о й к о м ­
п а н и и в ы создали ещ е в главе 1, о н с о д е р ж и т ч а с ть
н уж н ы х данны х.

ШТУРМ
База данных Как бы вы ском бини ровали д а н н ы е
ContactDB от д вух организаций д л я получения
единого списка кон тактов?
Все данные о клиент ах ф и рм ы
по і^ о и зво д с т ву бум аги вы
найдет е в базе.

дальше ► 671
UNQ для спасения

Сбор данных из разных источников


Вас сп а се т L IN Q ! Э та а б б р е в и а ту р а р а с ш и ф р о в ы в а е тс я ка к
L a n g u a g e In te g ra te d Q u e ry (В с т р о е н н ы й я з ы к з а п р о с о в ). В ы уж е
и с п о л ь з о в а л и э т у т е х н о л о г и ю в с и м у л я то р е улья дл я о тс л е ж и в а ­
н и я з а н я т и й г р у п п п че л . П р о с т ы е з а п р о с ы п р е д о с т а в л я л и вам В главе 1 2 вы вое-
д а н н ы е и з к о л л е к ц и и . .А н а л о ги ч н ы м с п о с о б о м L I N Q м о ж е т ра­ пользовались готовым
б о т а т ь с д а н н ы м и и з к о ф е й н о г о м а га зи н а . Э ту т е х н о л о г и ю м о ж ­ вариантом кода для
н о п р и м е н ять к лю бы м коллекциям , реализую щ им ин терф ейс же мы подробно рас­
IE n u m e r a b le < T > . смотрим,, как это
р£^оот.аеп\.
L I N Q п о з в о л я е т р а б о та ть и с н а б о р а м и к о л л е к ц и й . Те ж е самы е
з а п р о с ы и з в л е к у т д а н н ы е и з базы и л и и з д о к у м е н т а X M L .

Вот запрос, при помощи


которого в симулятор
ульй мы группировали
и сортировали пчел.

var beeGroups =
from bee in world.Bees
group bee by bee.Currentstate
into beeGroup
orderby beeGroup.Key
select beeGroup;

t
Аналогичный запрос позволит нам
получит ь данные о клиентах из
коллекции кофейного магазина.

рдии и те же запросы
UNQ работают как
сбаз1м и Эйнных, т а к
L I N Q р а б о та е т с л ю б ы м и с т о ч н и к о м д а н н ы х в .N E T . Д о с т а т о ч н о (Д с документами X M L .
в с т а в и т ь в в е р х н ю ю ч а с т ь ф айла с к о д о м с т р о ч к у u s i n g S y s te m .
L i n q ; Б ол ее т о г о , И С Р а в т о м а т и ч е с к и п о м е щ а е т в в е р х н ю ю
ч а с т ь создаваем ы х ф а й л о в кл а ссо в с с ы л к у н а L IN Q .

672 глава 15
UNQ

Коллекции .NET y>ke настроены nog LINQ


В се к о л л е к ц и и .N E T р е а л и з у ю т и н т е р ф е й с I E n u m e r a b l e < T > , с
к о т о р ы м в ы п о з н а к о м и л и с ь в главе 8. В с п о м н и м , к а к о н ф у н к ц и о ­
нирует: вв е д и те в код строчку System.Collections.Generic.
IE n u m e r a b l e < i n t > , щ елкни те на н е й п р а во й к н о п к о й м ы ш и и вы бе­
р и т е к о м а н д у G o to D e fin itio n (и л и н а ж м и т е к л а в и ш у F I 2 ). В ы у в и д и т е ,
интерф ейс ф ейс
ч т о и н т е р ф е й с l E n u m e r a b l e о п р е д е л я е т м е то д G e t E n u m e r a t o r ():

a o to D e U b o n .

namespace S y s t e m . C o l l e c t i o n s .G e n e r i c {
interface IEnumerable<T> : lEnumerable
11 Резюме:
// Осуществляет простой перебор элементов коллекции.
//
11 Возвращает:
11 S y s t e m . C o l l e c t i o n s .G e n e r i c .I E n u m e r a t o r < T > , который
11 и перебирает элементы коллекции.
IEnumerator<T> GetEnumeratorO
Это единственный м ет од и н ­
К . т ерф ей са. Его р еа л и зуе т каж­
дая коллекция. Вы мож ете
создават ь и собст венные o6v ^
ект ы , реализую щ ие инт ерф ейс
IEnumerable<T>... а за т е м Рабо­
т а т ь с ними при помощ и UNQ.
М е т о д тр е б у е т указа ть с п о с о б п е р е м е щ е н и я о б ъ е к та
о т о д н о г о э л е м е н та к о л л е к ц и и к другом у. Э т о ус л о в и е
л ю б о г о з а п р о с а L IN Q . Е с л и в ы м о ж е т е п р о с м а т р и в а т ь
к о л л е к ц и ю э л е м е н т за э л е м е н то м , з н а ч и т, м о ж е т е и
реализовы вать ин те р ф е й с IE n u m e r a b l e < T > . С о о т­
ве тстве н н о L IN Q в с о с то я н и и п осы лать ко л л е кц ии
запросы .

а сЦ еной
Д ля з а п р о с о в , с о р т и р о в к и и о б н о в л е н и я д а н н ы х L I N Q и с п о л ь з у е т м е т о д ы
р а с ш и р е н и я . У б е д и те с ь в э т о м сам и. С о з д а й те м а сси в т и п а i n t с и м е н е м
l i n q t e s t , п о м е с т и т е в н е го ч и с л а и в в е д и те э ту с т р о ч к у : Теперь вы видит е,
почем у М ет о ­
IE n u m e ra b le < in t> re s u lt = fro m i in lin q te s t w h e re i < 3 s e le c t i; ды расш ирения, с
кот оры м и вы п о ­
A т е п е р ь п р е в р а т и т е B к о м м е н т а р и й с т р о ч к у u s i n g S y s t e m . L i n q ; в з а го ­ знаком ились в главе
14^ т ак важны...
л о в к е ф айла. Т е п е р ь р е ш е н и е п о с т р о и т ь н е удастся. В едь и м е н н о те м е то д ы , они позволят .NET
к о т о р ы е в ы в ы зы в а е те , р а б о та я с L I N Q , и с п о л ь з о в а л и с ь для р а с ш и р е н и я мас­ (а заодно и вам )
сива. м ен ят ь поведение
• сущ ест вую щ их
типов.

дальше > 673


простые запросы

Простой способ сделать запрос


П е р е д вам и п р и м е р с и н т а к с и с а L IN Q . О н в ы б и р а е т и з м а сси в а т и п а i n t ч и с л а м е н ь ш е 37 и р а с п о л а га е т
и х в возрастаю ш ;ем п о р я д к е . Д л я э т о г о и с п о л ь з у ю т с я ч е т ы р е п р е д л о ж е н и я (c la u s e s ), у к а з ы в а ю щ и е п о ­
р я д о к за п р о с о в , к р и т е р и и в ы б о р а , к р и т е р и и с о р т и р о в к и и с п о с о б в о з в р а щ е н и я результата.

int[] values = new int[] {О, 1 2 , 44, 36, 9 2


» f
, 54,
I r
13,~ 8 } ;
Это предложение замещает
var result = from v in values оукоу V (переменную диапазона)
значениями элеМентоб массиба.
сначала v равно о, зат ем — 1 2 ,
зат ем — 4 4 , зат ем — 3 6 ... и т. д..
Этот запрос where V < 37 < — V
Это предложение выбирает
состоит из все переменные диапазона у,
не превышающие 3 7 .
Т о 7 Х й : Iro m , \ o r d e r b y V ^
where, orderby j ^ат ем эти значения
и select. I И'^орядочиваются по
select v; возрастанию.
. Пользователям, знакомым с SQL,
может показаться странным
foreach(int i in result) конечное положение предложения
select, но именно т а к о й
синтаксис используется в LINQ-
Console.writ e ("{0} ", і);
^ .л п ^н о по одному
Console.ReadKey () ;
Результат:
О 8 12 13 36

Клю чевое слово v a r за ст а вляет ком пилят ор о п р е Э е л я т ь т и ^ ,


.N B T диагност ирует его по т и п у локальной перем енной, ко
м е нн оЙ .
т о р ую вы использовали для запроса UN 0.
В рассм ат риваем ом п р им ере при ком пиляции эт ой строки:
v a z result =s frcMB v in values
ключевое слово var зам еняет ся на:
IEnumerable<int>
И раз иж МЫ коснулись инт ерф ейсов, работ аю щ ий с коллекциям ,
вспом ним о т о м , чт о инт ерф ейс ШпитегаЫе<Т> позволяет п р о ­
см а т р и ва т ь элем ент т один за другим. Ш ь ш и н с т в о запросов UNQ
реализованы при пом ощ и м ет одов, расилиряюи^их э т о т инт ерф ейс.
Т а к чт о ст алкиват ься с ним вам придет ся доволш о часто.

Вернитесь к главе 8 для повторения материала о работе интерфейса 1 Е п ш а е г а Ы е < Т > .

674 глава 15
UNQ

САО)кные запросы
Д ж и м м и продал сво ю недавно созданную ф ирм у кр уп н о м у и н в е с то р у и х о ч е т п о т р а т и т ь часть п р и б ы л и
н а п о к у п к у с а м ы х д о р о г и х к о м и к с о в п р о к а п и т а н а В е л и к о л е п н о го , к о т о р ы е т о л ь к о с м о ж е т н а й т и . К а ­
к и м о б р а зо м L I N Q м о ж е т п о м о ч ь ему в п о и с к е с а м ы х д о р о г и х ко м и к с о в ?

С са й та ф а н а то в В е л и к о л е п н о го Д ж и м м и ска ча л с п и с о к в с е х в ы п у с к о в и п о м е с т и л и х в к о л л е к ц и ю
L i s t < T > о б ъ е к та Comic. Э т о т о б ъ е к т и м е е т два п о л я N a m e (Н а з в а н и е ) и I s s u e (В ы п у с к ).

c l a s s C o m ic {
p u b lic s tr in g Name { g e t; s e t; }
p u b lic in t Is s u e { g e t; s e t; }
}
Э т о и л >Л€,тод 5ыл цказан
как ст ат ический |л я т ого,
Д ля п о с т р о е н и я к а та л о га Д ж и м м и в о с п о л ь з о в а л с я и н и ц и а л а м и :
чтобы его м о ж н о оыао
p riv a te s ta tic IE n u m e ra b le < C o m ic > B u i l d C a t a l o g () ---------- - легко б м з б а т ь из м ет ода
точки входа консольного
{
приложений.
re tu rn new L is t< C o m ic > {
new C o m ic { Name = " J o h n n y A m e ric a v s . th e P in k o ", Is s u e = 6
ne w C o m ic { Name = "R o ck and R o ll ( lim it e d e d itio n ) " , Is s u e =^ 1 9 },
new C o m ic { Name = " W o m a n 's W o r k " , Is s u e = 36 },
new C o m ic ( Name = " H i p p i e M a d n e s s (m is p rin te d )", Is s u e = 57 },
ne w C o m ic { Name = " R e v e n g e o f t h e New W ave F r e a k (d a m a g e d )" , Is s u e = 68 },
new C o m ic { Nam e = " B l a c k M o n d a y " , I s s u e = 7 4 } , ---------
new C o m ic { Nam e = " T r i b a l T a t t o o M a d n e s s " , I s s u e = 8 3 } , 1
new C o m ic { N a m e = " T h e D e a t h o f a n O b j e c t " , I s s u e .= 9 7 } , }
Выпуск Ф74- комиксов
}
про капитана Великолепного
называется «Black Monday».

© К с ч а с т ь ю , сущ е ств уе т з а м е ч а те л ь н ы й к а т а л о г Грега. Д ж и м м и узн а л, ч т о в ы п у с к #57 « H ip p ie


M adness» из-за о п е ч а т о к б ы л п р а к т и ч е с к и п о л н о с т ь ю у н и ч т о ж е н изд ателем . О н о б н а р у ж и л ред­
к у ю к о п и ю , н е д а в н о п р о д а н н у ю ч е р е з к а та л о г Г рега за $13,525. П о с л е д о л ги х п о и с к о в н у ж н о й и н ­
ф о р м а ц и и Д ж и м м и с м о г создать с л о в а р ь , с о п о с т а в л я ю щ и й н о м е р в ы п у с к а и е го ц е н у

p riv a te s ta tic D ic tio n a ry < in t, d e c im a l> G e tP ric e s O


{
re tu rn new D ic tio n a ry < in t, d e c im a l> {
{ 6, 3600M },
{ 19, 500M },
Выпуск * 5 7
ст оит $13,SZS. ШТУРМ
650M },
Э тот 13525M }, У В н и м ате л ьн о исследуйте з а ­
прос на с. 674. Как им енно д о л ­
синтаксис { 68, 250M },
для иници­ жен сф о р м и р о в ать свой зап р ос
ализатора { 74, 75M },
Джимми, что бы найти сам ы й
словарей мы { 83, 2 5 .7 5 M },
изучали в д орогой вы пуск к ом и ксо в?
{ 97, 3 5 .2 5 M },
главе 8.
};
}
дальше > 675
это не sql

Д н а щ о М и я s a n j> o c a

П р о а н а л и з и р о в а т ь д а н н ы е , к о т о р ы е со б р а л Д ж и м м и , м о ж н о путе м е д и н с т в е н н о го запроса
L IN Q . П р е д л о ж е н и е w h e r e указы вает, к а к и е эл е м е н ты к о л л е к ц и и н у ж н о в к л ю ч и т ь в к о н е ч ­
н ы й результат. П р и это м м о ж н о н е о гр а н и ч и в а т ь с я п р о с т о й о п е р а ц и е й с р а в н е н и я , а в к л ю ч и т ь
л ю б ы е в ы р а ж е н и я и з С#. Н а п р и м е р , в о сп о л ь зо в а ть ся сл о в а р н ы м п о ле м v a l u e s для в к л ю ч е ­
н и я в результат к о м и к с о в , с т о я щ и х д о р о ж е $500. Затем п о л у ч е н н а я п о с л е д о ва те л ь н о с ть будет
упорядочена п р и п о м о щ и предлож ения o r d e r b y .
Запрос LINQ из-
^ влекает объекты
I Comic из пред-
IEnumerable<Comic> comics = BuildCatalog() ; ложенного списка,
^ нд основе данных

Dictionary<int, decimal> values = GetPrices () ;


• Первым в запросе идет предложение From
Ь качестве источника данных оно указывает
на коллекцию comics, а переменно 1л диапазона
var mostExpensive = присваивает имя comic.

from comic in comics Б предложения where


и orderby можно включить
АЮ5ЫЕ операторы С*,
fro m where values [co m i c .Issue] > 500 поэт ому мы используем
м ож но UC- словарные значения для
выбора комиксов, цена
orderby values Гco mi c.Issue! descending которых превышает $SOO.
Зат ем мы сортируем
Мы взяли имя
comic. select comic; 1
(, Появившееся в предложении from
имя comic зат ем используется
результ ат по убыванию.

в предложениях where и orderby. Запись « { 1 : с } » в парам е­


трах метода WriteLine
foreach (Comic comic in mostExpensive) означает, что второй
парам ет р нужно выве­
сти в формате локальной
Console.WriteLine("{0} стоиа? {l:c} валюты.

co mi c.N a m e , values[comic,Issue]);

m o S ___u..^^
m s у Г чi,AAj?uun
т о и м е н н о вош/о n
в результ ат ,
Т прЖ кило предложение select - запрос вернул
набор объектов Comic.
Результат:
H ip p ie M adness (с опечаткам и) стоит $ 1 3 ,5 2 5 .0 0
Johnny A m e r ic a v s. th e P in k o стоит $ 3 ,6 0 0 .0 0
W o m a n 's W o rk стоит $ 6 5 0 .0 0

676 глава 15
UNQ

вы ничего не
Д й ж е если
знаете об SQL. поводов для
беспокойства н е т — для
работы с LINQ вам не
поплребцюплся никакие со­
Я знаю S Q L — именно на него пут ст вую щ ие сведения.
похожи запросы L IN Q , не так л и ?

LINQ может быть и выглядит, как SQL, но работает


он по-другому.
У з н а т о к о в S Q L м о ж е т в о з н и к н у т ь со б л а зн п р о п у с т и т ь главу п р о L IN Q ,
р е ш и в , ч т о т у т все и н т у и т и в н о п о н я т н о и о ч е в и д н о , в L I N Q и в сам ом
деле и с п о л ь з у ю т с я п о з а и м с т в о в а н н ы е и з S Q L к л ю ч е в ы е с л о в а s e l e c t ,
f r o m , w h e r e , d e s c e n d in g и j o i n . Н о L I N Q п р и э т о м с и л ь н о о тл и ч а е т с я
о т S Q L , п о э т о м у к о д , н а п и с а н н ы й в п р и в ы ч н о й вам м а н е р е , с к о р е е в с е го ,
не будет работать о ж и д а е м ы м образом.

S Q L р а б о та е т с таблицами, а н е с перечислимыми объектами. Т а б л и ц ы н е


и м е ю т п о р я д к а . В ы п о л н я я S Q L -за п р о с s e l e c t , м о ж н о б ы т ь у в е р е н н ы м ,
ч т о т а б л и ц а о б н о в л я т ь с я н е будет. S Q L с н а б ж е н р а з л и ч н ы м и с р е д ства м и
о б е с п е ч е н и я б е з о п а с н о с т и д а н н ы х , к о т о р ы м в п о л н е м о ж н о д о в е р я ть .

Е с л и р а с с м о т р е т ь S Q L б олее д е та л ь н о , т о е го з а п р о с ы я в л я ю т с я о п е р а ­
ц и я м и над м н о ж е с т в а м и . Э т о о зн а ча е т, ч т о о б р а щ е н и е к с то л б ц а м т а б л и ­
ц ы н е уп о р я д о че н о . К о л л е к ц и и ж е , п р и своей сп о с о б н о с ти со хр а н я ть
что угодно—значения, с т р у к т у р ы , о б ъ е к т ы и т. п . — и м е ю т о п р е д е л е н н ы й
п о р я д о к . L I N Q п о з в о л я е т о с у щ е с тв л я т ь л ю б ы е о п е р а ц и и , к о т о р ы е п о д ­
д е р ж и в а ю т п р о и с х о д я щ и е в к о л л е к ц и и п р о ц е с с ы , — в ы м о ж е т е даж е в ы ­
з ы в а ть м е то д ы для с о д е р ж а щ и х с я в н у т р и о б ъ е к то в . П р о с м о т р эле м е н ­
т о в о с у щ е с тв л я е тс я ц и к л и ч н о , т о е сть в с т р о г о о п р е д е л е н н о м п о р я д к е .
Существуют и дру­ М о ж е т п о к а з а ть с я , ч т о все э т о н е и м е е т о с о б о го з н а ч е н и я , н о е сл и в ы
гие отличия UNO от
п р и в ы к л и р а б о та ть с S Q L , н а п и с а н н ы е в а н а л о г и ч н о й м а н е р е з а п р о с ы
мы не будем
вдаваться 8 подроб­ L I N Q дадут вам результат, д а л е к и й о т о ж и д а е м о го .
ности! Аостаточно
чтобы вы поняли, что
ожидать от
L NQ-запросов знако­
мого вам поведения.

дальше ► 677
вот почему джимми любит LINQ
Все коллекции реализуют интерфейс
IEnumerable<T> — обратное неверно.
Д ля принадлежности к коллекции
Универсальность LINQ нужно реализовывать ещ е и интерфейс
ICollection<T> , то есть методы Add (),
В ы м о ж е те н е т о л ь к о и з в л е к а ть о тд е л ь н ы е эл е м е н ­ Clear ( ) ,Contains ( ) ,СоруТо () и Remove ( ) ...
т ы ко л л е кц и и , н о и редактировать и х. С ге н е р и р о ­ Разумеется, iCollection<T> расширяет
вав результат, L I N Q п р е д о с т а в л я е т н а б о р м е то д о в 1 Е п ш п е г а Ы е < т > . LINQ ж е работает с
для р а б о т ы с н и м . То е сть в ы п о л у ч а е те и н с т р у м е н ­ последовательностями значений или объектов,
т ы для у п р а в л е н и я в а ш и м и д а н н ы м и . а не с коллекциями, а значит, вам требуется
объект, реализую щ ий 1 Е ш т е г а Ы е < Т > .
Отредактируем результаты запроса
Д о б а в и м в к о н е ц к а ж д о й с т р о к и в э то м м а сси ве д о п о л н и т е л ь н у ю
стр оку. В ы с о з д а д и т е н а б о р м о д и ф и ц и р о в а н н ы х с т р о к .

s t r i n g [] sandwiches = { "ham and cheese", 'salami w ith mayo",


"turkey and swis s ' , " c h i c k e n cutlet" };
var sandwichesOnRye = A d R
f r o m s a n d w i c h in s a n d w i c h e s P^m ow
select sandwich + on r y e '

foreach (var sandwich in sandwichesOnRye)


Console.WriteLine(sandwich); Изменения
касаются
результ ат ов
/Добавив on rye в конец Р вЗуЛ Ь ТЭ Т: запроса... но
каждой строки, мы ham and cheese on rye никак не за ­
положили все элементы ^ трагивают
сэндвичей на ржаной хлеб. salami with mayo on rye элементы ис­
turkey and swiss on rye ходной коллек­
chicken cutlet on rye ции или базы
Вычисления внутри коллекции данных.
П о м н и те , м ы го в о р и л и , ч то L IN Q обеспечивает колл екц ии
м е то д а м и р а с ш и р е н и я ? Н е к о т о р ы е и з н и х весьм а п о л е з н ы .

Random random = new R a n d o m ();


List<int> listOfNumbers = new L i s t < i n t > ();
int length = random.Next(5 0 , 1 5 0 );
for (int i = 0; i < length; i++)
l i s t O f N u m b e r s . A d d ( r a n d o m . N e x t (100));

C o n s o l e . W r i t e L i n e ("Есть {0} чисел", Ни один из этих м ет о ­


l i s t O f N u m b e r s .C o u n tо ); <r дов не имеет отношения
C o n s o l e . W r i t e L i n e ( " С а м о е м а л е н ь к о е {0}", к NET... все они опреаеле
l i s t O f N u m b e r s .Min О ); «6---- ны в UNQ. ^
C o n s o l e . W r i t e L i n e ( " С а м о е б о л ь ш о е {0}",
l i s t O f N u m b e r s .Max() ) / Э то все методы
C o n s o l e . W r i t e L i n e ("Их с у м м а р а в н а {0}", g;
ния для iBnumerab\e<Jy о
пространстве имен
l i s t O f N u m b e r s .Suni() ); Lina, где используется ста
C o n s o l e . W r i t e L i n e ("Среднее арифметическое {Ог ^ г } Тический класс ёпитегаЫе.
l i s t O f N u m b e r s .A verage О ); иХелкните на люооМ из
них правой кнопкой мыши

678 глава 15 в эт ом лично.


Последовательностью называется LINQ
упорядоченный набор объектов или
значений, которые LINQ возвраща--
ет в 1ЕпитегаЫе<Т>.
Запросы LINQ не вы­
полняются заранее!
Сохраните результат в виде последовательности
^удыпе
И н о гд а н у ж н о , ч т о б ы р е зул ь та ты в ы п о л н е н и я за п р о с а
оСГ11ор*оЖНь11 Это называ­
L I N Q б ы л и п о д р у к о й . В о сп о л ь зуе м ся для э т о г о ется «отложенными вычис­
ком андой T o L is t (): лениями» — сначала должен
сработать оператор, ис­
var underSOsorted = пользующий результаты
from number in listOfNumbers f-ia эт от запроса. Поэтому так важен
раз числа
where number < 50 сортируюі^ся метод ToListQ — он застав­
orderby number descending n o убыванию. ляет LINQ немедленно вы­
select number; полнить запрос.

List<int> newList = underSOsorted.T o L is t О ;

С п о м о щ ь ю м е то д а Т а к е () м о ж н о
■<т>.
с ф о р м и р о в а т ь п о д м н о ж е с т в о р е зульта то в:

var firstFive = underSOsorted.T ak e ( б ) ;

List<int> shortList = firstFive.T o L is t O ;


foreach (int n in shortList)
Console.WriteLine(n);

Посетите Официальную страницу 101 L IN Q Samples от M icrosoft


Здесь в ы н а й д е те д о п о л н и т е л ь н ы е м а те р и а л ы п о р а б о те с L IN Q :

h t t p ://m sd n 2 .m ic r o s o ft .c o m / e n -u s / v c s h a r p /a a 3 3 6 7 4 6 .a sp x

4acm°
ЧаДаБаеМые
B o II p o C jji
Так много новых слов: from ,
v a r u n d e rlO = Именно поэтому LINQ выглядит так стран­
w here, o r d e r b y , s e le c t . . . как
fro m num ber in n u m b e rA rra y но с точки зрения С#. Ведь многочислен­
будто совершенно другой язык. Почему
w h e r e n u m b e r < 10 ным операциям соответствует совсем
он так отличается от С#?
s e le c t n u m b e r; короткая запись.

I; Потому что он служит другой цели, Несмотря на кажущуюся простоту, это


ольшая часть синтаксиса C# предназна­ довольно сложный кусок кода. Подумай­
чена для выполнения одной небольшой
операции за один раз. Можно начать цикл,
те, сколько действий должна совершить
программа для выбора из массива
LINQ, позволяет
присвоить значение переменной, произ­
вести математический расчет или вызвать
n u m b e r A r r a y всех элементов, не
превышающих 10. Нужно циклически
коротко писать
метод... все это будут единичные операции.
Единичный же запрос LINQ может вы­
просмотреть массив, сравнить каждый
его элемент с 10 и вывести результат
очень сложные
полнять целый ряд функций. Рассмотрим
пример:
в форме, пригодной для дальнейшего
применения.
запросы.
дальше > 679
небольшой обзор

КЛЮЧЕВЫЕ I
МОМЕНТЫ
f r o m ука зы в а е т I E n u m e r a b l e < T > , ч т о м ы s e l e c t указы вает, ч т о в х о д и т в к о н е ч н ы й
о сущ е ствл яе м за п р о с . За н и м всегда следует р е зул ь та т ( s e l e c t value).
им я перем енной, потом i n и им я входны х
T a k e позволяет п ол уч ить первы е несколько
д а н н ы х ( f r o m v a l u e i n values).
р е зул ь та то в за п р о с а ( r e s u l t s . Т а к е (10)).
w h e r e в об щ ем случае следует за from. Э т о Д ля к а ж д о й п о с л е д о в а т е л ь н о с ти в L I N Q
предл ож ение использует условны е опера­ с у щ е с тв у ю т и д р у ги е м е то д ы : M i n (), М а х (),
т о р ы C # д ля ф и л ь т р а ц и и э л е м е н то в ( w h e r e S u m () и A v e r a g e ( ) .
value < 10).
П р е д л о ж е н и е s e l e c t р а б о та е т н е т о л ь к о
o r d e r b y у к а зы в а е т п о р я д о к с о р т и ­ с и м е н е м , с о з д а н н ы м в п р е д л о ж е н и и fr o m .
р о в к и результата. За н и м следует к р и ­ С к а ж е м , е с л и з а п р о с в ы б и р а е т ц е н ы из
т е р и й с о р т и р о в к и и и н о гд а к л ю ч е в о е м а сси ва з н а ч е н и й i n t , к о т о р о м у в п р е д ­
с л о во d e s c e n d i n g ( o r d e r b y v a l u e л о ж е н и и fr o m п р и с в о и л и им я v a lu e ,
descending). стр о к у с ценам и м о ж н о вернуть так: s e l e c t
S trin g .F o rm a t("{0 :с }", v a lu e .

Э то напоминает запись кот о­


рую вы использовали в главе й при
построении шестнадцатиричного
дампа. Есть еш,е варианты (0:с1}
Часгро и {0:0} для длинной и короткой
и {0:Р} или {0:Рп} для вывода процен­
<аДаБаеМые тов (с п десятичных знаков).
Б о ц |э о с ;ь 1

O h на время создает переменную i, кото­


J Как работает предложение f r o m ? рой по очереди присваиваются элементы Как LINQ определяет, что включать
коллекции val u e s . А теперь посмотрим в результат?
^ I Оно напоминает первую строчку на предложение f r o m в аналогичной
цикла f ore a c h . Запросы LINQ пона­
чалу сложно воспринимать, так как они
ситуации: Ql Это определяет предложение
select. Каждый запрос возвращает
соответствуют нескольким операциям. from i in v a l u e s последовательность из элементов одного
типа. При этом четко указывается, что она
Запрос делает одно и то же с каждым эле­ Предложение тоже создает временную должна содержать. Если запрос делает­
ментом коллекции. Предложение from, переменную i и по очереди присваивает ся к массиву или коллекции элементов
во-первых, указывает, к какой коллекции ей элементы коллекции val u e s . Цикл одного типа, результат очевиден. А что
осуществляется запрос, во-вторых, при­ f o r e a c h запускает для каждого из делать при запросе к списку объектов
сваивает этой коллекции имя. элеметов расположенный снизу блок кода C o m i c ? Можно выбрать весь класс, как
в то время как запрос LINQ применяет это сделал Джимми. А можно поменять
Новое имя создается почти так же, как к каждому элементу критерии из предло­ последнюю строчку запроса на s e l e c t
в случае цикла foreach. Вот первая жения where. Но следует помнить, что c o m i c .N a m e и получить результат
строчка этого цикла: запросы LINQ — это всего лишь методы в виде набора строк. А написав s e l e c t
foreach (int i in values) расширения. Вся работу делают вызы­ c o m i c . Issue, вы получите последо­
ваемые ими методы. Которые вы можете вательность целых чисел.
вызвать и не прибегая к LINQ.

680 глава 15
UNQ

дальше ► 681
а вы поклонник LINQ?

еШение Задачи с МаГнищаМи


UNQ начинает работу
Вот каким способом нужно расположить с некоторой последот -
магниты для получения указанного телшости,, коллекции
результата. или массива — 6 дан­
ном случае это массио
целых чисел.

но ^ ^ ^ ^ o z o T a ^ o c / l t N Q w ^ ^ головоломка,
8 };
J
<^аким: «from^badger in b a d g e T sT

var skunks
Эти операторы UNQ вы­
j t l j L , ------------------ бирают из массива числа,
from см pigeon in b a d g e r ^ ^ ^ которые меньше SO и не
равны 3 6 . Зат ем к каж­
После этого дому из них прибавляет­
оператора п о ­ ся 5, последовательность
следователь­ сортируется по убыванию
ность skunks со­ и помещается в новый
держит четыре обьект, на который ука -
числа: 4 6 , 1 3 / select \зывает ссылка skunks.
Ю и 8, pigeon + 5;

var bear
-
skunKs
\ .Take(3); I
i^4>^ks и помешаем
^оват ельност ^ Ь е ^гТ

var weasels

sparrow in bears Этот оператор вычитает i


from из каждого элемента последо-
;a вательности bears и помещ а­
select sparrow - 1; ет эти элементы в по слеШ а -
т ельност ь weasels.

Console.WriteLine("Get your kicks on route {0} 1


weasels.Sum ()
S s e l s состобАяе» b i.
4 S + I Z + <f = &£,
Результат:
Get your kicks on route 66
682 глава 15
UNQ

Группировка результатов запроса


В ы уж е знаете, ч т о L I N Q п о з в о л я е т р а з б и в а ть
р е зул ь та ты за п р о са н а гр у п п ы , т а к к а к и м е н н о
э т о в ы делали в с и м у л я то р е улья. П о с м о т р и м
б олее п о д р о б н о н а т о , к а к э т о р а б о та е т.
Н а ч а л о за п р о с а н и ч е м н е о тл и ч а е т с я о т д р у г и х —
в ы и з в л е к а е те о т д е л ь н ы х п ч е л и з к о л л е к ц и и
var beeGroups = w o r l d .B e e s о б ъ е к та L i s t < B e e > .

from bee in world.Bees © С ле дую щ а я с т р о к а за п р о с а с о д е р ж и т н о ­


вое к л ю ч е в о е с л о в о group. О н о п р и к а з ы ­
в а е т за п р о с у в о з в р а щ а ть группы п че л . То
< ^ o u p ^ e e by b e e .Currentstate е сть в м е с то о д н о й п о с л е д о в а т е л ь н о с ти
у на с будет ц е л ы й н а б о р . З а п и с ь group
b e e by b e e . C u r r e n ts ta te г о в о р и т , ч т о
пчел н у ж н о разбить на гр у п п ы в со о тве т­
into beeGroup с т в и и со с в о й с т в о м C u r r e n t s t a t e . Н у и
н а к о н е ц , м ы указы ваем и м я н о в ы х гр у п п :
i n t o beeGroup.
orderby beeGroup.Key

select beeGroup;
Т е п е р ь, ко гд а г р у п п ы г о т о в ы , и м и м о ж н о
у п р а в л я ть . Н а п р и м е р , п р и п о м о щ и п р е д л о ­
ж е н и я o r d e r b y у п о р я д о ч и т ь и х п о зн а че ­
н и я м п е р е ч и с л е н и я C u r r e n t s t a t e (Idle,
Теперь н уж но п р и пом ощ и клю че­ F l y i n g T o F l o w e r и т. п .). С т р о ч к а ord erb y
в о го сл о в а s e l e c t ука за ть, к а к о й beeG roup. Key с о р т и р у е т п о с л е д о в а те л ь н о ­
р е зульта т в о з в р а щ а е т за п р о с . В дан­ с т и п о клю чу, в к а ч е с тв е к л ю ч а в д а н н о м случае
н о м случае у ка зы в а е тся и м я г р у п п ы : будет и с п о л ь з о в а т ь с я с в о й с т в о C u r r e n t s t a t e .
s e l e c t beeG roup;
Тйк как пчелы группировались
по сосплоянию, и м е н н о его мы
укажем в качестве ключа.

j currentstate = FlyingToFlower

Обратите внимание, что


запрос возвращает не
отдельных пчел, а их грцппы.

дальше ► 683
Сгруппируем результаты Д}кимАли
Д ж и м м и п о к у п а е т м н о го д е ш е в ы х к о м и к с о в , ч у т ь м е н ь ш е к о м и к с о в с р е д н е й ц е н о в о й к а т е г о р и и и н е ­
сколько д о р о ги х , и о н х о ч е т научиться о ц е н ива ть свои ф инансовы е в о зм о ж н о сти перед п о куп ко й .
О н п о м е щ а е т ц е н ы и з к а та л о га Г рега в п е р е ч и с л е н и е D i c t i o n a r y < i n t , i n t > п р и п о м о щ и м е то ­
да G e t P r i c e s О . В о сп о л ь зуе м ся L I N Q , ч т о б ы р а з б и т ь и х н а т р и гр у п п ы : к о м и к с ы с о с т о и м о с т ь ю д о
$100, со с т о и м о с т ь ю о т $100 д о $1 ,0 0 0 и к о м и к с ы , с т о я щ и е д о р о ж е Ь ,0 0 0 . М ы создад им п е р е ч и с л е н и е
P r i c e R a n g e , к о т о р о е будет и с п о л ь з о в а т ь с я в к а ч е с тв е к л ю ч а , и м е то д E v a l u a t e P r i c e ( ) , о п р е д е л я ю ­
щ и й цену и возвращ аю щ ий перечисление PriceRange.

В качестве ключа используем перечисление


К л ю ч о м г р у п п ы в ы с ту п а е т н е к о е о б щ е е с в о й с т в о . Э т о м о ж е т б ы т ь с т р о к а , ч и с л о и л и
даж е с с ы л к а н а о б ъ е кт. В н а ш е м случае каж д ая гр у п п а , к о т о р у ю в о з в р а щ а е т за п р о с ,
будет п о с л е д о в а т е л ь н о с ть ю и з н о м е р о в в ы п у с к о в , к л ю ч о м ж е г р у п п ы с т а н е т п е р е ч и с ­
л е н и е P r i c e R a n g e . М е т о д E v a l u a t e P r i c e {) будет в о з в р а щ а ть э т о п е р е ч и с л е н и е н а
о с н о в е т а к о г о п а р а м е тр а , к а к ц е н а :

enum PriceRange { Cheap, Midrange, Expensive }

s ta tic P ric e R a n g e E v a lu a te P ric e (d e c im a l p r ic e ) {


i f (p ric e < lO O M ) re tu rn P ric e R a n g e .C h e a p ;
e ls e i f (p ric e < lO O O M ) re tu rn P ric e R a n g e .M id ra n g e ;
e ls e re tu rn P r ic e R a n g e .E x p e n s iv e ;
}

О Сгруппируем комиксы по ценовым категориям


З а п р о с в е р н е т п о с л е д о в а т е л ь н о с т ь п о с л е д о в а т е л ь н о с т е й . К а ж д а я и з н и х будет и м е ть
с в о й с т в о Key, с о в п а д а ю щ е е с э л е м е н то м п е р е ч и с л е н и я P r i c e R a n g e , в о з в р а щ е н н ы м ме­
то д о м E v a l u a t e P r i c e ( ) . О б р а т и т е в н и м а н и е н а п р е д л о ж е н и е g r o u p b y - м ы в ы б и р а е м
и з сл о в а р я п а р ы и и с п о л ьзуе м для н и х и м я pair: p a i r .K e y - э т о н о м е р в ы п у с к а , а p a i r .
V a l u e — е го ц е н а . З а п и с ь g r o u p p a i r .K e y о б ъ е д и н я е т в г р у п п ы н о м е р а в ы п у с к о в , о р и е н ­
т и р у я с ь н а и х ц е н у:

D ic tio n a ry < in t, d e c im a l> v a lu e s = G e tP ric e s O ;


Запрос определяет, к какой
v a r p ric e G ro u p s = группе относится выбранный
fro m p a ir in v a lu e s комикс, передавая его цену
методу E\ra(uatePrice(). Метод
g ro u p p a i r . K e y b y E v a l u a t e P r i c e ( p a i r . V a l u e ) же возвращает перечисле­
in t o p r i c e G r o u p ние РпсеКапде, используемое
o rd e rb y p ric e G ro u p .K e y d e s c e n d in g в качестве ключа группы.
s e le c t p ric e G ro u p ;

fo re a c h (v a r g ro u p in p ric e G ro u p s ) {
C o n s o le .W rite ( " I fo u n d {0 } {1 } c o m ic s : is s u e s ", g ro u p .C o u n tО , g ro u p .K e y );
fo re a c h (v a r p r ic e in g ro u p )
C o n s o le .W rite (p ric e .T o S trin g 0 + " ");
C o n s o le .W rite L in d 0 ; /К
} Результат:
Каждая из групп является последа
ват елш ост ьн), поэт ому мы до­ Найдены 2 дорогих комикса: выпуски 6 57
бавили внутренний цикл foreach для Найдены 3 комикса по с р е д н е й цене: выпуски 19 36 68
просмотра цен внутри группы.
Найдены 3 д е шевых комикса: выпуски 74 83 97
UNQ

^ e ^ c r Б бассейне

Поместите фрагменты кода из


бассейна на пустые строчки. from
Каждый фрагмент может быть
использован несколько раз. line by line..
Есть и лишние фрагменты.
Вам нужно получить следую­ into wordGroups
щий результат:
orderby _______
I
Horses enjoy eating carrots, but they love eating apples. select ________

= words.. .(2) ;
class Line {
public string[] Words;
foreach (var group in twoGroups)
public int Value;
{
public Line(string[] Words, int Value) {
int i = 0;
this.Words = Words; this.Value = Value;

} ' Подсказка: LINQ сорт ирует foreach (______ inner in ___ .) {


строки в алфавитном порядке.
i++;
Line[] lines = {
new Line( new string[] { "eating", "carrots,", if (i == ..Key) {
"but", "enjoy", "Horses" 1,1),
new Line( new string[] { "zebras?", "hay", var poem
"Cows", "bridge.", "bolted" } , 2) ,
word in
new Line( new string[] { "fork", "dogs!",
"Engine", "and" }, 3 ) , word descending
new Line( new string!] { "love", "they",
"apples.", "eating" }, 2 ) , word +
new Line( new string[) { "whistled.", "Bump" ), 1 )
foreach (var word in
Console.Write(word) ;

К аж д ы й ф р а гм е н т
код а м о ж е т бы ть
и с п о л ь зо в а н from
з! to LineO
+ lines
select
- new
inside int
+= line Value
outside string
group Key
in - =
orderby var
un groups Words
by into
wordGroups words D
Key output
twoGroups this [1]
Value [2]
inner

дальше * 685
последний ребус в бассейне

еШение ]=*е^са Б бассейне


c la s s L in e {
p u b lic s t r i n g [] W ord s;
p u b lic in t V a lu e ;
p u b lic L i n e ( s t r i n g [] W ord s, in t V a lu e ) {
th is .W o rd s = W o rd s; th is .V a lu e = V a lu e ;

L in e [] lin e s = {
new L i n e ( new s t r i n g [] " e a tin g " , "c a rro ts ,", "b u t", "e n jo y ", "H o rse s" } , 1 ),
new L in e ( new s t r i n g [] "ze b ra s ? ", "h a y", "C o w s", " b r id g e ." , "b o lte d " } , 2 ),
new L in e ( new s t r i n g [] "fo rk ", "d o g s !", "E n g in e ", "a n d " }, 3 ) ,
new L in e ( new s t r i n g [] " lo v e " , "th e y ", " a p p le s ." , " e a tin g " }, 2 ) ,
new L i n e ( new s t r i n g [] " w h is tle d ." , "B um p" }, 1 )
};

var words =
fro m line i n linp.^
gCQ UP l i n e by l i n e . Value
in to w o rd G ro u p s в соот вет ст вии ^ гр упп ы
o rd e rb y wordSroups. Key
s e le c t wordSroups;

Ш iasKgcfflyps = w o r d s . Take ( 2 ) ; nep6t>fC две группы - это ст рочт


^ с о значениями Value 3- u-Z-
fo re a c h (va r g ro u p in tw o G ro u p s )
{ J ^ __ Этот цикл запрашивает ^
in t i = 0; №"■ первый обьект Une в пердои
fo re a c h (var in n e r in group) группе и второй обьект Une
во второй группе.
i+ + ;
i f ( i == group.Key) {
v a r poem =
from w o r d i n I
orderby w o r d d e s c e n d i n g Вы поняли, почему выражения
select w o r d + U l; «Horses enjoy eating carrots, but»
(«Лошади получают удоволь­
f o r e a c h ( v a r w o r d i n poem) ствие от моркови, но») и «they
C o n s o le .W rite (w o rd ); love eating apples» («они любят
яблоки») расположены в алф а-
оитном порядке по убыванию?

Результат : Horses enjoy eating carrots, but they love eating apples.

686 глава IS
UNQ

ПредАО)кение join
Д ж и м м и со б рал ц е л ую к о л л е к ц и ю к о м и к с о в и х о т е л б ы с р а в н и т ь ц е н ы н а н и х с ц е н а м и в ка та л о ге Гре­
га, ч т о б ы п о н я т ь , н е п е р е п л а т и л л и о н . Д ля з а п и с и с в о и х р а с х о д о в о н создал класс P u r c h a s e с двумя
а в т о м а т и ч е с к и м и св о й с тв а м и I s s u e и Price. П е р е ч е н ь к у п л е н н ы х и м к о м и к с о в н а х о д и тс я в к о л л е к ц и и
L i s t < P u r c h a s e > , к о то р а я назы вается p u r c h a s e s . К а к ж е ему те п е р ь о с у щ е с тв и ть с р а в н е н и е с ц е н а м и
и з ка та л о га Грега?

П р е д л о ж е н и е j o i n п о з в о л и т с к о м б и н и р о в а т ь д а н н ы е и з д в у х к о л л е к ц и й в е д и н ы й з а п р о с. Э т о дела­
ется п уте м п о и с к а в п е р в о й к о л л е к ц и и с о в п а д а ю щ и х з н а ч е н и й с о в т о р о й . ( L I N Q делает э т о э ф ф е к ти в ­
н о - с р а в н и в а е т т о л ь к о те п а р ы , к о т о р ы е н у ж н о .) В к а ч е с тв е результа та в ы в о д я тс я с о в п а д а ю щ и е п а р ы .

Аанные Д ж « м м и
fS u d e коллекции объектов Purchase,
которая иазываетсй purchases.
П о с л е п р е д л о ж е н и я f r o m в м е с то к р и т е р и я
о т б о р а р е зульта то в н а п и ш и т е :
class Purchase {
jo in пате in c o lle c tio n
public int Issue
И м я пате н а з н а ч а е тс я чл е н а м , к о т о р ы е и з ­ { get; set; }
в л е к а ю тс я и з о б ъ е д и н е н н о й к о л л е к ц и и н а public decimal Price
{ get; set; )
к а ж д о й и т е р а ц и и ц и к л а . З атем в ы и с п о л ь з у е ­
)
те е го в п р е д л о ж е н и и where.
Аж имми присоединяет
к комиксам из
коллекции purchases
список кцпленнш им
комиксов.

Д о б а в и м п р е д л о ж е н и е o n , ука­
зы ваю щ ее сп особ о б ъ ед и нения
on comic.Issue к о л л е к ц и й . З атем ука ж е м и м я п е р ­
в о й к о л л е к ц и и , к л ю ч е в о е сл о во
e q u a ls p u r c h a s e .I s s u e
e q u a ls и им я в то р о й ко л л е кц и и .
После предложения
О З атем сл е д ую т п р е д л о ж е н и я
w h e r e и orderby. Т ак ка к в
select new в фигурных
скобках перечислены
данные,, которые сле­
ре зул ьта т о б ы ч н о тр е б у е тс я дует вклночить в р е -
в к л ю ч и т ь ч а с т ь д а н н ы х из зильт ат.
о д н о й ко л л е кц и и , а часть из
s e l e c t new { comic.Name,
д р у г о й , в к о н ц е и с п о л ь з у е тс я
comic.Issue, p u r c h a s e .P r i c e }
предлож ение s e le c t new ,
Iqcmo = rI п я те = “Johnnv America" I Price = 3600
создаю щ ее п о л ь з о в а те л ь с к и й
н а б о р ре зул ь та то в п р и п о м о ­ Issue = 1 ^ lame = “Rock and Roll” Price = 375
щ и а н о н и м н о го типа. 1 Issue = 57 name = “Hippie Madnless” Price = 13215 L

дальше ► 687
свой парень Джимми

Д}кимми изрядно сэкономил


К а ж е т с я , Д ж и м м и з а к л ю ч а е т в ы го д н у ю сделку. О н создал с п и с о к кл а с с о в P u r c h a s e , в к о т о р о м п е р е ­
ч и с л е н ы е го п о к у п к и и с р а в н и л е го с ц е н а м и и з к а та л о га Грега.

О Сначала Д ж иалм и создал дополнительную коллекцию


О н и сп о л ь з о в а л с в о й с т а р ы й м е то д B u i l d C a t a l o g (). Д ж и м м и о с та л о с ь т о л ь к о н а п и ­
сать м е то д F i n d P u r c h a s e s {) и п о с т р о и т ь к о л л е к ц и ю кл а ссо в P u r c h a s e .

s t a t i c IE n u m e ra b le < P u rc h a s e > F i n d P u r c h a s e s () {
L is t< P u rc h a s e > p u rc h a s e s = n e w L i s t < P u r c h a s e > () {
new P u rc h a s e 0 { Is s u e = 68, P r ic e = 225M }, 3a выпуск * S 7
new P u rc h a s e 0 { Is s u e = 1 9 , P r ic e = 375M } ,
W ^zxs.
new P u rc h a s e 0 { Is s u e = 6, P r ic e = 3600M },
new P u rc h a s e 0 { Is s u e = 57 , P r ic e = 13215M J
new P u rc h a s e 0 { Is s u e = 3 6 , P r i c e = 660M } ,
};
re tu rn p u rch a s e s ;
}

О Все готово для слияния!


Ч а с т ь э т о г о за п р о с а в ы у ж е в и д е л и . О с т а л о с ь д о б а в и т ь к н е м у н е д о с т а ю щ и й
кусо к.

IE n u m e ra b le < C o m ic > c o m ic s = B u i l d C a t a l o g ( ) ; Предложение join инициирует


D ic t io n a r y < in t , d e c im a l> v a lu e s = G e t P r ic e s O ; сравнение каждого элемента из
I E n u m e r a b l e < P u r c h a s e > p u r c h a s e s = F i n d P u r c h a s e s () коллекции comics с э л е м е н т а м и
var re s u lts = коллекции purchases для поиска
fr o m c o m ic i n c o m ic s
тек, у которых comic.lssue
соВпадает с purchase.Issue.
jo in p u rch a se in p u rch a se s
on c o m ic .ls s u e e q u a ls p u r c h a s e .Is s u e
o r d e r b y c o m ic .ls s u e a s c e n d in g
s e le c t new { c o m ic .N a m e , c o m ic .ls s u e , p u rc h a s e .P ric e };
d,oimal ,r e , s L l , t V a l „ , = 0; n p e J A O X « » select co sl^ em
d e c im a l t o ta lS p e n t = 0;
fo re a c h (v a r r e s u lt in r e s u lts ) ,
g r e g s L is tV a lu e += v a l u e s [ r e s u l t . I s s u e ] ; значением Price от члена purchase.
t o ta lS p e n t += r e s u l t . P r ic e ;
C o n s o le .W r ite L in e ("В ы п ус к # { 0 } ( { ! } ) куплен за { 2 : с } " ,
r e s u lt . I s s u e , re s u lt.N a m e , r e s u l t . P r ic e ) ;

C o n s o le . W r it e L in e ("Я п о т р а т и л { 0 : c } на ко м и кс ы , стоящ ие {l:c}


to ta lS p e n t, g re g s L is tV a lu e );
Джимми счаст­
лив, что он знает Результат:
иЫ 0, так как
это позволило Выпуск #6 (Johnny A m erica vs . the Pinko) куплен за $3,600.00
ему подсчитать,
Выпуск #19 (Rock a n d R o l l (limited edition)) куплен за $375.00
сколько же он
сэкономил. Выпуск #36 (Woman's Work) куплен за $660.00
Выпуск #5 7 (Hippie M a d n e s s (misprinted)) куплен за $13,215.00
Выпуск #6 8 (Revenge of the New Wave Freak (damaged)) куплен за
$225.00
688 глава 15 Я потратил $18,075.00 на комиксы, стоящие $18,525.00
UNQ

Хорошо. Джиллми использовал L I N Q для


запросов из коллекций... а как быть с промо­
акцией кофейного магазина? Я до сих пор не
понимаю, как L IN Q работает с базами данных.
Несмотря на
значительную
разницу LlhlQ
и SQL написан­
ный вами код
будет похож на
Для работы с базами данных LINQ использует остальные за-
просы LINQ.
тот же самый синтаксис.
В главе 1 в ы у ж е в и д е л и , к а к л е гк о .N E T п о з в о л я е т
р а б о та ть с базам и д а н н ы х . В ы м о ж е те б ы с т р о п р и ­
с о е д и н и т ь базу, д о б а в и т ь т а б л и ц ы и д аж е п р и в я ­
за ть к ф о р м е д а н н ы е и з э т и х т а б л и ц .

С делаем з а п р о с к э т о й , у ж е с в я з а н н о й с п р о гр а м ­
м о й базе д а н н ы х . L I N Q без п р о б л е м с к о м б и н и р у е т
д а н н ы е и з базы с д а н н ы м и , п о л у ч е н н ы м и о т о б ъ ­
е к то в .

С и н т а к с и с з а п р о с о в н е и з м е н и т с я ... вам п о т р е б у е т ­
ся в с е го л и ш ь п о л у ч и т ь д о с т у п к базе д а н н ы х .

База данных А здесь находятся


ContactDB данные о постоян­
ных посетителях
кофейного магазина
Starbuzz.
Адрес клиента есть
в объектвильской
базе контактной l l N t
информации. UNQ позволит нам сравнить
*?■— ^ и скомбинировать данные из
нескольких источников и соз­
дать результ ирую щ ую по­
следовательность.

дальше > 689


соберем все вместе

Соединение LINQ с базой данных SQL


L IN Q p a 6 o T a e T с о б ъ е к та м и , р е а л и з у ю щ и м и и н т е р ф е й с I E n u m e r a b l e < T > .
З н а ч и т , для д о с т у п а к базе д а н н ы х S Q L нам тр е б у е тс я и м е н н о т а к о й о б ъ ­
ект. П о э т о м у д о б а в и м е го к н а ш е м у п р о е к ту .

Добавим базу данных к новому консольному приложению


В главе 1 с п о м о щ ь ю S Q L Sender C o m p a c t в ы создали базу с к о н т а к т н о й и н ф о р м а ц и е й и с о х р а н и л и
ее в ф айл C o n t a c t D B .sdf. Н а ч н и т е н о в ы й п р о е к т, щ е л к н и те п р а в о й к н о п к о й м ы ш и н а е го им е­
н и в о к н е S o lu tio n E x p lo re r, в ы б е р и те ком анду A d d E x is tin g Ite m и добавьте базу В р а скр ы в а ю щ е м ­
ся с п и с к е т и п о в о б ъ е кто в н у ж н о будет в ы б р а т ь в а р и а н т D a ta Files. (П о я в и т с я м астер D a ta Source
C o n fig u ra tio n , н о е го м о ж н о п р о с т о з а к р ы ть .)

О Для сопоставления L IN Q и S Q L -платформы используйте SqIMetal ехе


Вам о ста л с я в с е го о д и н ш а г дл я п р и с о е д и н е н и я к к о д у базы д а н н ы х , и сделать е го п о м о ж е т
п р о гр а м м а S q ^ t a l . е х е . О н а у с та н а в л и в а е тс я вм е сте с V is u a l S tu d io 201 0 и н а х о д и т с я в п а п ­
ке M ic ro s o ft S D Ks, в л о ж е н н о й в п а п к у P ro g ra m F ile s. В к о м а н д н у ю с т р о к у в в е д и те ком анду, ука­
з ы в а ю щ у ю м а р ш р у т к п а п к е M ic ro s o ft S D K . Д л я 6 4 -б и т н о й в е р с и и W in d o w s в в е д и те :
Это м о ­
жет быть РАТН=%РАТН%;%PrograinFiles(х86)%\Microsoft SDKs\Windows\v7.OA\Bin\
и папка /Д л я 3 2 -б и т н о й в е р с и и ко м а н д а будет н е с к о л ь к о о тл и ч а т ь с я :
«NETFX
4-.0 Tools»,<
вложенная PATH=%PATH%;%PrograraFiles%\Microsoft SDKs\Windows\v7.OA\Bin\
в пап1ш
В1п\. В J e n e p b п е р е й д и т е в п а п к у с п р о е к т о м ( c d f o l d e r - n a m e ) и в в е д и те ком анду:
эт ом с л у ­
чае- в конце SqlMetal.exe ContactDB.sdf /dbml:ContactDB.dbml
команды
РАТН= В о т ч т о д о л ж н о п о л уч и ть ся на вы ходе:
нужно на­
печатать
«NETFX Microsoft (R) Database Mapping Generator 2008 version 1.00.30729
4.(9 Tools». for Microsoft (R) .NET Framework version 3.5
Copyright (C) Microsoft Corporation. All rights reserved.
П о с л е э т о г о в в а ш е й п а п к е о к а ж е т с я ф а й л C o n t a c t D B .dbml. И с п о л ь з у й т е к о м а н д у A d d
E x is tin g Ite m , ч т о б ы д о б а в и т ь е го к сво е м у п р о е к ту . П р и э т о м И С Р со зда ст д ля вас ещ е два ф а й­
ла: C o n t a c t D B .d e s i g n e r .cs и C o n t a c t D B .d b m l .l ayout.

Получить дополнительную информацию о SqlMetal.exe можно здесь:


________http://msdn.microsoft.com/ru-m/librartf/hb38ea87 я.<;ру

690 глава 15
Откройте классы L IN Q и SQL в конструкторе O bject Relational
П р о гр а м м о й S q I M e t a l . е х е б ы л и с о з д а н ы к л а с с ы L I N Q t o S Q L . Э т и к л а ссы у м е ю т
п о с ы л а т ь з а п р о с к та б л и ц а м в а ш е й базы д а н н ы х и п р и э т о м р е а л и з у ю т и н т е р ф е й с
I E n u m e r a b l e < T > с ф у н к ц и е й , в о з в р а щ а ю щ е й д а н н ы е в таблицу.

И С Р о с н а щ е н а т а к и м з а м е ч а те л ь н ы м и н с т р у м е н т о м к а к к о н с т р у к т о р O b je c t R e la tio n a l.
З десь в ы м о ж е те у в и д е ть , к а к и е к л а ссы б ы л и с о з д а н ы п р о гр а м м о й S q l M e t a l . e x e .
Д в о й н о й щ е л ч о к н а и м е н и д о б а в л е н н о го к п р о е к т у ф айла C o n t a c t D B .d b m l о т к р ы в а ­
е т е го в к о н с т р у к т о р е O b je c t R e la tio n a l. В о т ч т о в ы п р и э то м у в и д и те :

ontaetDB.dbml

8 конструкторе Object
Relational вы увидите
класс PeoplCj созданный
—----- —------------------------------ при поАЛош,и программы
Peopfe
SqlMetal.exe. Он присоеди­
няет к проект у таблицу
People с дазой данных и
S Properties возвращает данные при
f S * ContactID Create methods by помощи интерфейса
Nante
dragging items from 1ЕпитетЫе<т>, давая
Database Explorer возможность осущ ест­
^ Company влять запросы UNQ.
onto this design
Ш Telephone surface
Ш Email
^ Client
^ LastCall

Примечание: В версиях Visual Studio, отличных от E xpress, можно перетащить источник


данных S Q L непосредственно в конструктор Object Relational.

Все готово для написания запросов к базе данных


Д о б авьте э т о т к о д в м е то д M a i n () . О б р а т и т е в н и м а н и е , к а к п р и п о м о щ и к л ю ч е ­
в ы х сл о в s e l e c t n e w б ы л о у к а за н о , ч т о р е зул ь та т д о л ж е н с о д е р ж а ть т о л ь к о дан-
^ontactP B иь1е и з п о л е й N a m e и C o m p a n y
Й оТно им я s tr in g c o n n e c tio n S trin g = "D a ta S o u rc e = ID a ta D ire c to ry |W C o n ta c tD B . s d f" ;

context. C o n ta c tD B c o n t e x t = new C o n ta c tD B ( c o n n e c t io n S tr in g ) ;
И споль - — .
зуйт е его Попрактикуйтесь с применением
свойство v a r p e o p l e D a t a = ключевых слов select new. Именно
People для они из всех данных базы выбира­
получения fr o m p e r s o n i n c o n t e x t . P e o p le
ю т только информацию об имени
данных из s e l e c t n e w { p e r s o n . N a m e , p e r s o n . C o m p a n y } ; человека U фирме, в которой OH
таблицы работает. ^
People.
fo re a c h (va r p e rso n in p e o p le D a ta )
C o n s o le .W rite L in e ( " {0 } w o rks at { i: p e rs o n .N a m e , p e r s o n . C o m p a n y);

дальше ► 691
ключевое слово var

КЛЮЧЕВЫЕ
МОМЕНТЫ

Предложение g r o u p разбивает результат на В результаты запроса j o i n обычно требуется


группы — только создает последовательность выборочно включить элементы из обеих коллек­
из последовательностей. ций. Это реализуется при помощи предложения
Каждая группа содержит член, являющийся s e le c t .
общим для всех остальных членов. Он называ­ Запрос к базе данных SQL осуществляется при
ется ключом и задается ключевым словом by. помощи классов LINQ to SQL.Ohh обеспечива­
Каждая последовательность обладает членом ют программу объектами, работающими с LINQ
Key, содержащим ключ группы. (то есть дают вам непосредственный доступ
Предложение j o i n объединяет две коллек­ к методам этих объектов).
ции в одном запросе. Члены обеих коллекций Конструктор Object Relational позволяет выби­
сравниваются друг с другом и из совпадающих рать таблицы, к которым вы хотите обратиться
пар формируется результат. средствами LINQ. После выбора таблиц к проек­
Ключевые слова o n ... e q u a l s задают крите­ ту добавляется класс D a t a C o n t e x t . Члены
рий сравнения в предложении j o i n . его экземпляров добавляются к запросам LINQ
и обеспечивают доступ к таблицам SQL.

Часзпо
Объясните, пожалуйста, что означает v a r .
^аД аБ аеМ ы е
Б о іп р о с ь і
Q ; Ключевое СЛОВО v a r решает сложную проблему, возни­
кающую в LINQ. Обычно при вызове метода или выполнении
оператора сразу ясно, с каким типом данных вы работаете. Если вместо последней строчки написать:
К примеру, если метод возвращает значения типа s t r i n g ,
результат его работы нужно сохранить в переменную или поле s e le c t new
именно этого типа. { Nam e = с о т і с . N am e,
Iss u e N u m b e r = "# " + c o m ic .ls s u e };
А вот запрос LINQ может вернугь данные анонимного типа, га-
торый нигде не определен. Вы знаете, что это какая-то после­ запрос вернет данные анонимного типа с двумя членами — стро­
довательность. Но тип содержащихся в ней объектов полностью кой Nam e и строкой Is s u e N u m b e r . Но определение класса
зависит от содержания запроса LINQ. Например, рассмотрим вот для этого типа в нашей программе отсугствует! При том, что
такой запрос:
переменная m o s t E x p e n s i v e должна быть объявлена как
принадлежащая к какому-то типу.
v a r m o s t E x p e n s iv e =
fr o m c o m ic i n c o m ic s Здесь на помощь приходит ключевое слово v a r , которое как
w h e re v a l u e s [ c o m i c . I s s u e ] , > 500 бы объясняет компилятору: «Это корректный тип, просто мы
o rd e rb y v a lu e s [ c o m ic .Is s u e ] пока не знаем, какой именно. Определи это, пожалуйста, само­
d e s c e n d in g стоятельно».
s e le c t c o m ic ;

692 глава 15
Часвдо
я так и не понял, как работает при­ Всегда ли нужно добавлять файл
ЧаДаБаеМые . d bm l, создаваемый программой
ложение join. Б о їїр о с ь і
S q I M e t a l . е х е ? Я так и не понял,
зачем он нужен.
г ; Предложение j o i n работает с И ничто не мешает вам выбрать только
двумя последовательностями. Предпо­ имена игроков и размеры их футболок:
ложим, у вас есть коллекция футбольных : Если вы собираетесь писать запросы
игроков p l a y e r s . Ее элементами к Вазе данных SQL, без этого файла не
коаз
var results =
обойтись.
являются объекты со свойствами Name, from player in p l a y e r s
P o s i t i o n и N u m b e r . Выбрать where player.Number > 10 Помните, что LINQ требует от объ­
игроков, на футболке которых номер join shirt in jerseys ектов реализации интерфейса
больше 10, можно запросом: on player.Number I E n u m e r a b l e < T > . Базы SQL не реа­
equals shirt.Number лизуют вообще никаких интерфейсов, так
var results =
s e l e c t new { как не относятся к объектам. Поэтому для
from player in p l a y e r s
player.Name, запросов LINQ источник данных требуется
where player.Number > 10 преобразовать в объект.
s h i r t .S i z e
select player;
};
Вернитесь к только что написанному
У нас есть и коллекция j erseys, ИСР в состоянии самостоятельно разо­ коду, щелкните правой кнопкой мыши на
элементы которой обладают свойствами браться с результатом, который выдает объекте People и выберите команду Go
N u m b e r и Size. Чтобы определить запрос. При создании цикла, нумерующего to Definition. Это приведет вас к методу
размер футболки каждого игрока за­ результаты, сразу после ввода перемен­ доступа C o n t a c t D B .d e s i g n e r .
пишем: ной появится окно IntelliSense со списком. cs, возвращающему объект
Т а Ь 1 е < Р е о р 1 е > . Повторите процеду­
var results = foreach (var г in results) ру для объекта Table. Вы увидите, что класс
from player in p l a y e r s г .
T a b l e < T E n t i t y > расширяет ин­
where player.Number > 10 терфейс I Q u e r y a b l e < T E n t i t y > .
jo in shirt in jerseys Перейдя к определению этого интерфейса,
on p l a y e r . N u m b e r Equals вы обнаружите, что он реализует интер­
e q u a ls shirt.Number I GetHashCode фейс I E n u m e r a b l e < T > .
select shirt; GetType
То есть файл . d b m l (и создаваемый
им файл кпасса .cs) обеспечивает нас
Ш Size
объекгом, реализующим интерфейс
Этот запрос даст мне множество ToString
( )утболок. А как быть, если меня не l E n u m e r a b l e . ИСР точно знает, как
волнуют номера игроков, но хотелось поступать с файлом . dbml: стенерировав
бы узнать размер футболки каждого его, добавив к проекту и отфыв в конструк­
В списке присутствуют свойства N a m e торе Object Relational, вы увидите члены
из них?
и Size. Добавленные к предложению класса People, совпадающие с таблицей
s e l e c t дополнительные пункты тоже P e o p l e в базе данных. Этот класс при­
^ ; Здесь вам пригодятся анонимные
появятся в этом списке. Это связано с тем, соединяется к SQL, автоматически читает
типы — в них можно положить любые
что запрос создает различные анонимные данные из таблиц и преобразует их в форму,
нужные вам данные. Они позволяют и вы­
типы для различных членов. доступную для запросов LINQ.
бирать из объединенных коллекций.

Предложение select new позволяет конструировать пользова­


тельский запрос LINQ,, в результаты которого включены толь
ко нужные вам элементы.
дальше > 693
это всё, ребята

Соединим Starbuzz и Objectville


Т е п е р ь у вас е сть всё, ч т о б ы с о е д и н и т ь д а н н ы е к о ф е й н о г о м ага­ Упраж нение
з и н а S ta rb u zz и о б ъ е к т в и л ь с к о й ф и р м ы п о п р о и з в о д с т в у б ум аги.

Добавим к проекту данные SQL


Е сли в ы э т о го ещ е н е сделали, о т к р о й т е н о в о е к о н с о л ь н о е п р и л о ж е н и е и добавьте к н ем у базу
C o n ta c tD B . С п о м о щ ь ю п р о гр а м м ы S q I M e t a l . е х е создайте кла ссы L I N Q t o S Q L , добавьте и х
к п р о е к т у и н а п и ш и т е п р о с т о й т е с т о в ы й зап р о с, ч т о б ы уб едиться, ч т о п р и с о е д и н е н и е п р о ш л о
усп е ш н о и все работает.

О Построим объекты starbuzz


В о т с п и с о к с д а н н ы м и к л и е н т о в м а га з и н а S tarbuzz. Д о б а в ьте е го к п р о е к ту :
c la s s S ta rb u z z D a ta {
p u b lic s trin g Nam e { g e t; s e t; }
p u b lic D rin k F a v o rite D rin k { g e t; s e t; } Кофейного Магазина
p u b lic in t » „e y s p e n t , get; =et,
p u b lic in t V is its { g e t; s e t; } CAUUAKOM Много — и большинство
} нужны. Поэтому вы­
берем только те из них, которые
enum D r in k { подходят для нашего запроса.
B o rin g C o ffe e , C h o c o R o c k o L a tte , T rip le E s p re s s o ,
Z e s ty L e m o n C h a i, D o u b le C a p p u c c in o , H a lfC a fA m e ric a n o ,
C h o c o M a c c h ia to , B a n a n a S p litln A C u p , PK „ ,
fs;- В кофейном магазине много зам е-
(-1схтелк>ных напиткоб, и у каждого
клиента ecmt> свой любимый.
Вам п о т р е б у е т с я м е то д , г е н е р и р у ю щ и й о б р а з ц ы д а н н ы х :
s t a t i c I E n u m e r a b l e < S t a r b u z z D a t a > G e t S t a r b u z z D a t a () {
r e t u r n new L is t< S ta r b u z z D a ta > {
new S ta rb u z z D a ta {
Name = " J a n e t V e n u t i a n " , F a v o rite D rin k = D r i n k . C h o c o M a c c h ia to ,
Метод M oneySpent = 255, V is it s = 50 ) ,
{
объектов Nam e = " L i z N e l s o n " , F a v o r i t e D r i n k = D r i n k . D o u b le C a p p u c c in o ,
Starbuzx при п о - M o n e y S p e n t = 1 5 0 , V i s i t s = 35 } ,
мои^и инициали­ new S ta rb u z z D a ta {
затора коллекции Name = " M a t t F r a n k s " , F a v o r i t e D r i n k = D rin k .Z e s ty L e m o n C h a i,
M o n e y S p e n t = 7 5 , V i s i t s = 15 } ,
new S ta rb u z z D a ta {
Name = " J o e N g " , F a v o r ite D r in k = D rin k .B a n a n a S p litln A C u p ,
НапоАЛина- M oneySpent = 60, V i s i t s = 10 } ,
ем., ч т о для
к о л л е к ц и и (А
new S ta rb u z z D a ta {
инициализато­ N am e = " S a r a h K a l t e r " , F a v o r i t e D r i n k = D rin k .B o rin g C o ffe e ,
ров можно ие M o n e y S p e n t = 1 1 0 , V i s i t s = 15 }
использовате’ } .
>яд ииме
Метод содержит в числе прочего ряд м е н из базы
скобки О- }
\ контактов бумажной компании. Если вы используе-
^ те свои варианты имен, позаботьтесь о соопааени
ях в обоих списках.

694 глава 15
UNQ

О Соедним базу SQL с коллекцией Starbuzz


Э т о ко д зап р о са . П о м е с т и т е е го в м е то д M a in ( ) :

IE n u m e ra b le < S ta rb u z z D a ta > s ta rb u z z L is t = G e tS ta rb u z z D a ta ();

s trin g c o n n e c tio n S tr in g =
"D a ta S o u r c e = | D a t a D i r e c t o r y 1W C o n t a c t D B . s d f '
C o n ta c tD B c o n te x t = new C o n ta c tD B (c o n n e c tio n S trin g ) ;

v a r r e s u lts = ^ S ta rb u zz с данныАМА из т а
SAUUjbt People-
fro m s ta rb u z z C u s to m e r in s ta rb u z z L is t
Предложение
Тирает из w h e re s ta r b u z z C u s to m e r . M o n e y S p e n t > 90 Член Pecwle к л а с­
Sa3t>i данные са D a ta d o n text -
oS имени и j o i n p e r s o n i n c o n t e x t . P e o p le эт о коллекция,
ф и р м е, a из обеспечиваюи^ая
коллекции S t a r b u z z C u s t o m e r .N a m e e q u a l s p e r s o n . N a m e вас дост упом к
S ta rb u zz оан- т аблице People
в базе данных.
MoM напит ке s e l e c t n e w { p e r s o n . N a m e , p e r s o n . C o m p a n y ,
M объединяет M
их в р езу л ь -'^ ----------- s t a r b u z z C u s t o m e r . F a v o r i t e D r i n k } ;
т и р ую щ
f \
ую
^ *f-ZJ
П роверьт е результ а -
nocAedoda- 1 . . . . . ...
meAtiHocmb. me?/-
кЛЛ
ySedum ecbj
л
чт о
fo re a c h (v a r ro w in re s u lts ) все работ ает т а к /
"
как вы ' ожидали.
C o n s o le .W r ite L in e ( " { 0 } a t {1 } lik e s {2 }
ro w .N a m e , ro w .C o m p a n y , ro w . F a v o rite D rin k );

C o n s o le . R e a d K e y ( ) ;
Прекрасная работа... благодаря
рекламной акции наш бизнес
пойдет в гору. М и обязательно
позвоним вам снова.

„нсж рам еи м зава-


„ р и м е и е и ч я L IN S _

е!«ся UNOP““' “ ^
скачать его здесь-
k t t p y / w w w - I H P “«“' ’ ' * * ' '
Эндрю Стиллмен, Д ж енниф ер Гоин

Изучаем С#
2-е издание
Перевела с английского И. Рузмайкина

Заведующий редакцией А. Кривцов


Руководитель проекта Л. Юрченко
Ведущий редактор Ю. Сергиенко
Художественный редактор Л. Адуевская
Корректор Л. Казарина
Верстка Л. Родионова

ООО «Мир книг», 198206, Санкт-Петербург, Петергофское шоссе, 73, лит. А29.
Налоговая льгота — общероссийский классификатор продукции ОК 005-93, том 2;
95 3005 — литература учебная.
Подписано в печать 29.08.11. Формат 84x100/16. Уел. п. л. 72,24. Тираж 1500. Заказ 26278.
Отпечатано по технологии О Р в ОАО «Первая Образцовая типография»,
обосойленное подразделение «Печатный двор».
197110, Санкт-Петербург, Чкаловский пр., 15.

Вам также может понравиться