+ All Categories
Home > Documents > C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf ·...

C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf ·...

Date post: 23-Jun-2020
Category:
Upload: others
View: 6 times
Download: 0 times
Share this document with a friend
200
Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования «ТОМСКИЙ ПОЛИТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» ___________________________________________________________________________________________ А.Ф. Тузовский ВЫСОКОУРОВНЕВЫЕ МЕТОДЫ ИНФОРМАТИКИ И ПРОГРАММИРОВАНИЯ Рекомендовано в качестве учебно-методического пособия Редакционно-издательским советом Томского политехнического университета Издательство Томского политехнического университета 2009
Transcript
Page 1: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

Федеральное агентство по образованию Государственное образовательное учреждение высшего профессионального образования

«ТОМСКИЙ ПОЛИТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ» ___________________________________________________________________________________________

А.Ф. Тузовский

ВЫСОКОУРОВНЕВЫЕ МЕТОДЫ ИНФОРМАТИКИ И ПРОГРАММИРОВАНИЯ

Рекомендовано в качестве учебно-методического пособия

Редакционно-издательским советом Томского политехнического университета

Издательство

Томского политехнического университета 2009

Page 2: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

2

УДК 004

ББК 32.973.26-018.2

T81

Тузовский А.Ф.

T81 Высокоуровневые методы информатики и программирования; учебно-

методическое пособие / А.Ф. Тузовский; Томский политехнический университет. –

Томск: Изд-во Томского политехнического университета, 2009. – 200 с.

В пособии рассматриваются современные высокоуровневые методы разработки

приложений для операционной системы Windows с использованием платформы Mi-

crosoft .Net. Поясняются структура платформы выполнения и разработки про-

граммного обеспечения, основы объектно-ориентированного подхода, язык про-

граммирования C#, разработка приложений с графическим интерфейсом и техноло-

гия работа с базами данных ADO.Net.

Пособие подготовлено на кафедре оптимизации систем управления ТПУ и

предназначено для студентов специальности 080801«Прикладная информатика (в

экономике)», изучающих дисциплину «Высокоуровневые методы информатики и

программирования».

УДК 004

ББК 32.973.26-018.8

Рецензенты

Доктор технических наук, профессор,

зав. кафедрой «Автоматизированные системы управления» ТУСУРа

А.М. Кориков

Доктор физико-математических наук, профессор,

зав. кафедрой «Теоретическая кибернетика» ТГУ

Ю.Г. Дмитриев

© ГОУ ВПО «Томский политехнический

университет», 2009

© Тузовский А.Ф., 2009

© Оформление. Издательство Томского

политехнического университета, 2009

Page 3: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

3

Оглавление

Введение .......................................................................................................... 6

1. Основные понятия платформы .Net .......................................................... 7

1.1. Состав платформы .Net ........................................................................ 7

1.2. Общеязыковая исполняющая среда .................................................... 8

1.3. Языки программирования .................................................................... 9

1.4. Общий промежуточный язык ............................................................ 10

1.5. Новый тип программ – сборки .......................................................... 11

1.6. Библиотека классов .NET Framework ............................................... 13

1.7. Установка платформы .Net Framework............................................. 15

2. Обзор языка программирования C# ........................................................ 15

2.1. Основы объектно-ориентированного подхода ................................ 16

2.2. Структура приложений на языке С# ................................................. 22

2.3. Пример простого приложения ........................................................... 25

2.4. Создание выполняемой программы .................................................. 28

2.5. Базовые классы FCL для консольных приложений ........................ 32

3. Основные понятия языка C# .................................................................... 35

3.1. Типы данных ....................................................................................... 35

3.2. Переменные и константы ................................................................... 40

3.3. Операции ............................................................................................. 44

3.4. Операторы ........................................................................................... 53

3.5. Массивы ............................................................................................... 60

4. Описание и использование классов ........................................................ 64

4.1. Поля класса .......................................................................................... 66

4.2. Методы класса .................................................................................... 67

4.3. Перегрузка методов ............................................................................ 72

4.4. Конструкторы класса.......................................................................... 73

4.5. Свойства класса .................................................................................. 74

4.6. Индексаторы ........................................................................................ 77

4.7. Статические поля и методы класса ................................................... 78

4.8. Перегрузка операций класса .............................................................. 78

Page 4: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

4

4.9. Определение преобразования типов ................................................. 80

4.10. События класса ................................................................................. 81

5. Отношения между классами .................................................................... 86

5.1 Отношение вложенности .................................................................... 87

5.2. Отношение наследования .................................................................. 88

5.3. Абстрактные классы ........................................................................... 93

6. Другие пользовательские типы ............................................................... 95

6.1. Структуры ............................................................................................ 95

6.2. Перечисления ...................................................................................... 97

6.3. Интерфейсы ......................................................................................... 98

7. Коллекции и словари .............................................................................. 104

7.1. Коллекции .......................................................................................... 105

7.2. Словари .............................................................................................. 108

7.3. Универсальные классы..................................................................... 109

7.4. Обобщенные классы коллекций ...................................................... 110

7.5. Некоторые часто используемые классы FCL ................................ 113

8. Графический интерфейс приложений .................................................. 119

8.1. Описание графического интерфейса .............................................. 120

8.2. Пример простой программы с графическим интерфейсом .......... 127

8.3. Класс форм Form............................................................................... 130

8.4. Основные классы элементов управления ....................................... 136

8.5. Работа с меню и инструментальными полосами ........................... 145

8.6. Разработка Windows приложений в Visual Studio ......................... 150

8.7. Рисование в форме ............................................................................ 153

9. Работа с файлами и папками.................................................................. 164

9.1. Абстрактный базовый класс FileSystemInfo .................................. 164

9.2. Работа с классом DirectoryInfo ........................................................ 165

9.3. Работа с классом Directory ............................................................... 167

9.4. Работа с классом DriveInfo .............................................................. 168

9.5. Работа с классом FileInfo ................................................................. 168

9.6. Работа с классом File ........................................................................ 172

Page 5: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

5

9.7. Абстрактный класс Stream ............................................................... 174

9.8. Работа с классами StreamWriter и StreamReader ........................... 175

9.9. Работа с классами BinaryWriter и BinaryReader ............................ 176

10. Работа с базами данных ....................................................................... 178

10.1. Архитектура технологии ADO.NET ............................................. 179

10.2. Провайдеры данных ....................................................................... 180

10.3. Отсоединенный режим работы с базой данных .......................... 189

10.4. Типизированные классы DataSet................................................... 197

Список литературы ..................................................................................... 199

Page 6: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

6

Введение В настоящее время разрабатываются и совершенствуются новые

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

зующие объектно-ориентированное компонентное программирование

на основе обработки событий, графического интерфейса и взаимодейст-

вия с базами данных. Программирование все больше становится не про-

сто кодированием алгоритмов, а процессом построения моделей, ре-

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

надежного программного обеспечения. Для реализации такого подхода

к программированию разными компаниями разрабатываются новые

платформы, включающие наборы технологий и инструментов.

Одним из наиболее новых и полных реализаций данного подхода

является платформа .Net компании Microsoft. В этой платформе реали-

зованы последние достижения высокоуровневых методов информатики

и программирования. Изучение данной платформы позволит быстро и

эффективно разрабатывать современное программное обеспечение для

семейства операционных систем Windows, которые установлены на бо-

лее чем 90% всех персональных компьютеров в мире. Кроме этого, изу-

чение данной платформы позволит освоить новые методы построения и

разработки программ, которые реализованы и в других платформах и

технологиях.

Целью данного пособия является обучение студентов базовым ме-

тодам разработки современных прикладных программ (приложений) с

использованием языка C# и библиотеки классов платформы .Net. Разра-

ботка таких приложений в значительной степени автоматизирована за

счет использования системы разработки Visual Studio, которая облегча-

ет процесс разработки, но при работе с ней нужны базовые знания языка

программирования и способы решения разного типа задач.

Однако, следует отметить, что эта тема очень обширная и включа-

ет: объектно-ориентированный подход к программированию, описание

языка C#, разные технологии предоставляемые библиотекой классов

FCL (коллекции, графический интерфейс пользователя, работа с базами

данных) и, наконец, описание работы с интегрированной системой раз-

работки Visual Studio .Net. Понятно, что в одном учебном пособии все

это описать не возможно. В связи с этим в пособии поясняется только

то, что полезно при изучении данной дисциплины. Более подробную

информацию о разработке приложений с использованием платформы

.Net и системы Visual Studio .Net можно найти в книгах, ссылки на ко-

торые приведены в конце пособия.

Page 7: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

7

1. Основные понятия платформы .Net В 2002 году компания Microsoft создала новую платформу разра-

ботки и выполнения программ, которая поучила название .Net Frame-

work.

1.1. Состав платформы .Net Данная платформа является новым подходом к созданию и вы-

полнению прикладных программ (приложений). Это полностью объект-

но-ориентированная платформа, которая позволяет использовать уже

имеющиеся и создавать собственные типы данных. В .Net под термином

“тип” понимаются: классы, структуры, перечисления и иные формы

данных. Платформа .Net позволяет разрабатывать компоненты (назы-

ваемые сборками), которые предоставляют доступ к описанным в них

типам другим компонентам (возможно написанным на других языках).

Основными целями платформы .Net являлось создание:

нового формата выполняемых программных модулей – компонент

(EXE и DLL), называемых сборками (assembly) или управляемы-

ми модулями, основными особенностями которых является ис-

пользование общего (независимого от исходного языка) промежу-

точного языка программирования и метаданных, описывающих

все открытые типы данных, содержащиеся в них;

специальной виртуальной машины (общеязыковой исполняющей

среды, common language runtime, CLR), которая управляет компи-

ляцией в инструкции процессора и выполнением модулей, состав-

ленных на промежуточном языке; CLR начинает работать при ка-

ждом запуске управляемых модулей на выполнение;

общей библиотеки классов .NET Framework (Framework Class Li-

brary, FCL), которые помогают выполнить все базовую функцио-

нальность управляемых приложений (например, работа с коллек-

циями, файлами, сетями, графическим интерфейсом и т.п.).

набора программных средств, помогающих разрабатывать управ-

ляемые модули, например, таких как компиляторы и отладчики;

основным средством разработки является интегрированная среда

разработки – Visual Studio, позволяющая автоматизировать разра-

ботку приложений на всех языках поддерживаемых платформой.

Взаимосвязи компонентов платформы .NET Framework, с концеп-

туальной точки зрения, представлены на рис. 1.1.

Следует отметить, что данная платформа активно развивается и в

2008 году уже использовалась версия .Net Framework 3.5. В связи с ог-

раниченностью объема данного пособия, в нем будут рассматриваться

Page 8: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

8

только некоторые базовые возможности, которые уже имелись в первых

версиях платформы.

Рис. 1.1. Архитектура и окружение платформы .NET.

1.2. Общеязыковая исполняющая среда Основой .NET Framework является общеязыковая среда выполне-

ния программ (CLR). До разработки данной платформы приложения

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

процессах операционной системы Windows (ОС) и могли выполнять са-

мостоятельно любые доступные им действия. С использованием плат-

формы .Net, все созданные приложения компилируются в команды об-

щего промежуточного языка (common intermediate language, CIL) и ис-

полняются под управлением CLR. В связи с этим, они называются

управляемыми приложениями (managed application). Среда CLR являет-

ся виртуальной машиной, которая расположена поверх ОС (выполняет-

ся под управлением ОС и использует все ее возможности) и управляет

выполнением приложений разработанных для платформы .Net.

CLR управляет компиляцией программы с языка CIL в инструк-

ции процессора по запросу (just-in-time) в период выполнения приложе-

ния. Обычно компиляция любого метода происходит лишь раз – при

первом его вызове, и затем результат компиляции кэшируется в памяти,

чтобы при повторном вызове он мог быть исполнен без задержки. Код,

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

Выполнение кода в управляемой среде CLR дает много преиму-

ществ пользователю компьютера. Преобразуя команды CIL в команды

процессора, JIT-компилятор выполняет проверку (верификацию) кода

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

Page 9: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

9

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

к которым у приложения нет разрешения на доступ.

Помимо устранения наиболее распространенных ошибок, влияю-

щих на приложения, процедура верификации кода значительно затруд-

няет написание вредоносных программ, пытающихся нарушить работу

операционной системы.

Среда CLR также может организовать выполнение нескольких

приложений внутри одного процесса, в результате разделения процесса

на домены приложений (application domains), которые полностью изо-

лированы друг от друга. Среда CLR следит за использованием опера-

тивной памяти. Если участки оперативной памяти, выделяемые управ-

ляемым кодом, уже не используются (на них нет ссылок в программе),

то они автоматически освобождаются сборщиком мусора. В результате

этого, разработчик может выделять оперативную память – создавать но-

вые объекты, и не заботиться об ее освобождении – за него это делает

CLR. Благодаря сборщику мусора, в управляемых приложениях не бы-

вает проблем «утечки памяти».

1.3. Языки программирования Так как управляемые приложения, разработанные в платформе

.Net, компилируется не в команды процессора (настоящие машинные

инструкции), а в промежуточный код на языке CIL, то и выбор языка

программирования становится практически вопросом личных предпоч-

тений. Термин «общеязыковая» в словосочетании "общеязыковая ис-

полняющая среда" указывает на то, что CLR безразлична к языку про-

граммирования. Независимо от языка, на котором написаны управляе-

мые приложения, они используют один и тот же интерфейс прикладно-

го программирования (Application Program Interface, API): библиотеку

классов .NET Framework – FCL.

Microsoft поставляет компиляторы создающие модули на проме-

жуточном языке CIL для четырех исходных языков программирования:

С#, C++, Visual Basic и JScript. В .NET Framework Software Development

Kit (SDK) входит и сам ассемблер CIL – ILASM, так что при желании

можно писать приложения непосредственно на языке CIL. Разные ком-

пании поставляют компиляторы для других языков, включая Perl,

Python, Eiffel и даже COBOL.

1.3.1. Управляемые модули

В результате обработки программы компилятором С#, Visual Ba-

sic .NET или любым другим компилятором способным генерировать

CIL, получается управляемый модуль (managed module), т. е. просто ис-

полняемый файл, предназначенный для выполнения под управлением

Page 10: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

10

среды CLR. Эти модули имеют такие расширения имен файлов, как

EXE, DLL или NETMODULE. Внутри управляемого модуля есть четыре

основных элемента:

заголовок файла Windows Portable Executable (PE);

заголовок CLR, содержащий такие сведения, как местоположение

кода на языке CIL и метаданных;

метаданные, описывающие все типы данных, которые описаны

внутри модуля и ссылки на другие используемые модули;

команды CIL, сгенерированные из исходного текста.

В каждом управляемом модуле имеются метаданные, описываю-

щие его содержимое. Метаданные – это обязательный компонент

управляемого модуля, каждый CIL-совместимый компилятор должен их

генерировать. С помощью метаданных любой управляемый модуль

описывает сам себя. При использовании управляемого модуля нет про-

блемы узнать, какие классы (и включенные в них методы) в нем содер-

жатся. Метаданные дают CLR возможность определять, какие типы

(классы, интерфейсы и т.п.) присутствуют в каждом из загружаемых

управляемых модулей.

1.3.2. Метаданные

Основные метаданные модулей хранятся в виде набора таблиц. В

одной из них – TypeDef – перечислены определенные в этом модуле

типы. В другой таблице перечислены методы, реализованные этими ти-

пами, в третьей – поля, в четвертой – свойства и т. д. Используя эти таб-

лицы, можно составить список всех типов данных, определенных в мо-

дуле, а также элементов, из которых состоит каждый тип. В некоторых

таблицах перечислены ссылки на внешние типы (типы и элементы ти-

пов в других модулях, используемые данным модулем), сборки, содер-

жащие внешние типы, и др.

Формат метаданных для прикладного разработчика большого ин-

тереса не представляет. Однако, в составе .NET Framework SDK имеется

утилита ILDASM, которая позволяет просматривать содержание управ-

ляемых модулей.

1.4. Общий промежуточный язык Промежуточный язык CIL часто называют псевдо-ассемблером,

так как он определяет набор команд виртуального процессора. Для со-

ставления программ для .NET Framework нет необходимости знать CIL.

Однако общее представление о языке CIL иметь надо.

Язык CIL содержит примерно 100 команд. Некоторые из них –

низкоуровневые, аналогичные командам микропроцессоров, например,

Page 11: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

11

команды сложения двух значений (ADD) или перехода, если два значе-

ния равны (BEQ). Другие, более высокого уровня, редко встречаются в

аппаратных наборах команд. Так, NEWOBJ создает экземпляр объекта, а

THROW генерирует исключение. Благодаря таким большим возможно-

стям команд CIL, код на языке высокого уровня, таком как С# или Visu-

al Basic .NET, зачастую порождает при компиляции достаточно малое

число команд.

Язык CIL использует стековую модель исполнения. Если процес-

соры х86 для обработки значений загружают их в регистры, то CLR по-

мещает их в вычислительный стек. Чтобы сложить два числа, они копи-

руются в стек, вызывается ADD, и результат считывается из стека. Копи-

рование значения из памяти в стек называется загрузкой (loading), а ко-

пирование в обратном направлении – сохранением (storing).

В CIL есть несколько команд загрузки и сохранения. Например,

LDLOC загружает в стек значение по некоторому адресу в памяти, a

STLOC копирует значение из стека в память, удаляя его из стека.

В качестве примера работы языка CIL, можно рассмотреть фраг-

мент программы С#, в котором объявляются и инициализируются две

переменные, затем они суммируются, и результат записывается в тре-

тью переменную: int а = 3;

int b = 7;

int с = а + b;

Ниже приведен набор команд CIL, сгенерированный компилято-

ром Microsoft C# (с поясняющими комментариями): ldc.i4.3 // Загрузить в стек 32-разрядное (i4) число 3.

stloc.0 // Сохранить его в локальной переменной 0 (a).

ldc.i4.7 // Загрузить на стек 32-разрядное (i4) число 7,

stloc.1 // Сохранить его в локальной переменной 1 (b).

ldloc.0 // Загрузить в стек локальную переменную 0.

ldloc.1 // Загрузить в стек локальную переменную 1.

add // Сложить два числа и получить сумму в стеке.

stloc.2 // Сохранить сумму в локальной переменной 2 (c).

1.5. Новый тип программ – сборки Компиляторы .NET Framework создают управляемые модули, ко-

торые содержат код на промежуточном языке CIL и метаданные. Одна-

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

Базовой единицей защиты, управления версиями и развертывания в

.NET Framework является не управляемый модуль, а сборка (assembly).

Page 12: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

12

Сборка – это файл или набор файлов, в совокупности составляю-

щих логическую единицу. В сборку входят управляемые модули, а так-

же и другие типы файлов (например, изображения). Один из управляе-

мых модулей, входящих в сборку должен содержать декларацию (ma-

nifest). Физически декларация является просто дополнительными мета-

данными. Когда компилятор создает управляемый модуль, одновремен-

но являющийся и сборкой, декларация просто помещается в метаданные

модуля. Логически декларация – это описание всей сборки. Наиболее

важными элементами декларации являются:

имя сборки;

список всех файлов сборки вместе с криптографическими хеш-

значениями, вычисленными по содержимому файлов;

список типов данных, экспортируемых другими файлами сборки,

и информация, связывающая эти типы данных с файлами, где они

определены;

номер версии в формате major.mmor.build.revision (например,

1.0.3705-0).

Большинство сборок содержит один файл, но иногда содержат и

несколько файлов. Все файлы в составе одной сборки должны нахо-

диться в одной папке. Когда с помощью компилятора С# создается про-

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

сборкой. Большинство компиляторов могут создавать управляемые мо-

дули, не являющиеся сборками, а также добавлять другие файлы к

сборкам, которые они создают. В состав .NET Framework SDK входит

утилита AL (Assembly Linker) для объединения файлов в сборки. Мно-

гофайловые сборки обычно служат для объединения модулей, написан-

ных на разных языках, и для объединения управляемых модулей с

обычными файлами, содержащими изображения в формате JPEG и дру-

гие ресурсы, Многофайловые сборки также применяются для разделе-

ния приложений на отдельные загружаемые части, что может приго-

диться в случае развертывания приложения через Интернет.

При отсутствии специальных указаний компиляторы генерируют

нестрого именованные (weakly named) сборки. Это означает, что сборка

не имеет криптографической подписи и для ее идентификации CLR ис-

пользует только имя, указанное в декларации (представляющее собой

лишь имя файла сборки без расширения). Однако сборки могут быть и

строго именованы (strongly named). Такие сборки содержат открытый

ключ разработчика, а также цифровую подпись, являющуюся хэш-

значением, сгенерированным для декларации сборки, в которой хранит-

ся открытый ключ.

Page 13: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

13

Цифровая подпись, генерируемая с помощью закрытого ключа

создателя сборки, может быть проверена открытым ключом и делает

декларацию сборки (а значит, саму сборку) устойчивой к подделкам.

Для идентификации строго именованной сборки служат имя сборки, от-

крытый ключ, номер версии и строка региональных стандартов, если

она есть. Любое, даже самое небольшое отличие является достаточным,

чтобы выявить изменение сборки.

1.6. Библиотека классов .NET Framework При программировании на языке С, для работы с ОС Windows,

обычно используются вызовы API Windows и различные динамические

библиотеки DLL; при программировании на языке С++, часто использу-

ется стандартная библиотека классов MFC (Microsoft Foundation

Classes). При программировании на языке Visual Basic используют Vis-

ual Basic API, представляющий набор функций для использования низ-

коуровневых API Windows.

В .NET Framework используется новый прикладной интерфейс –

библиотека классов .NET Framework, которая содержит более 10 000

различных типов: классов, структур, интерфейсов, перечислений и де-

легатов. Некоторые классы FCL содержат до 100 методов, свойств и

других членов. Чтобы уметь разрабатывать приложения в .Net Frame-

work требуется не только знание программирования на некотором язы-

ке, но и умение использовать библиотеку FCL. Однако достоинством

FCL то, что она полностью объектно-ориентированная и используется

всеми языками, которые работают с платформой .Net. Все языки ис-

пользуют один и тот же API при создании разных типов приложений

(локальных или распределенных).

Библиотека FCL содержит набор системных типов данных, для

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

с используемыми ими типами данных. Например, для языка C# соответ-

ствие типов приведено в табл. 3.1.

Для облегчения использования FCL, все ее содержание хорошо

структурировано в виде иерархически организованных групп типов.

Каждая группа типов называется пространством имен. Всего в FCL

около 100 таких пространств. В каждом из них содержатся классы и

другие типы, имеющие некоторое общее назначение. Например, боль-

шая часть API Windows для управления окнами содержится в простран-

стве имен System.Windows.Forms. Здесь находятся все классы, пред-

ставляющие окна, диалоги, меню и другие элементы, обычно приме-

няемые в приложениях с графическим интерфейсом пользователя. От-

дельное пространство – System.Collections – содержит классы кол-

Page 14: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

14

лекции и словарей, а в пространстве имен System.IO – классы для ра-

боты с данными на внешних устройствах. В табл. 1.1 перечислены ос-

новные пространства имен FCL и кратко описано их содержимое.

Таблица 1.1.

Основные пространства имен библиотеки FCL

Пространство имен Содержимое System базовые типы данных и вспомогательные классы; System.

Collections

коллекции, словари, массивы переменной размер-

ности и другие контейнеры;

System.Data и др. классы ADO.NET для доступа к данным;

System.Drawing классы для рисования в окне (GDI+); System.IO классы файлового и потокового ввода –вывода;

System.Net классы для работы с сетевыми протоколами, на-

пример, http;

System.Reflection

и др.

классы для чтения и записи метаданных;

System.Runtime.

Remoting и др.

классы для распределенных приложений;

System.

ServiceProcess

классы для создания служб Windows;

System.Treading классы для создания и управления потоками;

System.Web классы для поддержки протокола http;

System.

WebServices

классы для разработки Web-сервисов;

System.Web.Servic

es.Protocols

классы для разработки клиентов Web-сервисов;

System.Web.UI основные классы, используемые ASP.NET;

System.Web.

UI.WebControls

серверные элементы управления ASP.NET;

System.Windows.

Forms

классы для реализации графического интерфейса

пользователя;

System.Xml и др. классы для чтения и вывода данных в формате

XML.

Реально библиотека FCL представляет собой набор DLL файлов

(файлов в формате динамических библиотек) в папке

%SystemRoot%\Microsoft.NET\Framework\v1.0.nnnn. Каждый

файл DLL – это сборка, загружаемая CLR по запросу. Встроенные типы

данных, такие как целые, вещественные, логические, реализованы в мо-

дуле Mscorlib.dll, другие типы разнесены по разным DLL файлам

библиотеки FCL. В документации на каждый тип указана сборка, в ко-

Page 15: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

15

торой он определен. При создании программы разработчик должен ука-

зать (сделать ссылки), какие библиотеки будут использоваться.

1.7. Установка платформы .Net Framework Для выполнения в ОС Windows XP управляемых приложений

требуется установить платформу .Net (компоненты CLR и FCL). Для

этого с сайта компании Microsoft нужно скачать

(http://www.microsoft.com/net/Download.aspx) установочный пакет “Mi-

crosoft .NET Framework 3.5 Redistributable Package” (197 Мб). В среде

ОС Windows Vista платформа .Net уже имеется.

Для выполнения разработки приложений в платформе .Net нужно

установить одно из средств:

пакет средств разработки (Software Development Kit – SDK) –

“Windows SDK for Windows Server 2008 and .NET Framework 3.5”

(1.33 Гб), который включает компиляторы, отладчики, примеры,

документацию для разработки приложений.

систему разработки Visual C# 2008 Express Edition – бесплатная

версия среды разработки Visual Studio (сокращенная, но обла-

дающая практически всеми возможностями). Данный программ-

ный продукт предназначен для студентов и начинающих разра-

ботчиков. Загрузить можно с сайта

http://www.microsoft.com/express/ru/download/.

2. Обзор языка программирования C# Разработка приложений для платформы .Net может выполняться

на любом языке, который формирует код на общем промежуточном

языке и поддерживает взаимодействие с библиотекой FCL. Однако язык

C# был специально разработан для платформы .Net и в полной мере

учитывает все ее возможности – как FCL, так и CLR.

Язык C# является наиболее новым из широко известных языков

программирования. Создателем языка C# является сотрудник Microsoft

Anders Hejlsberg, который ранее был ведущим разработчиком среды

программирования Delphi. Язык C# создавался как язык компонентного,

объектно-ориентированного программирования, и в этом одно из глав-

ных достоинств языка, направленное на возможность повторного ис-

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

языка являются следующие:

C# является полностью объектно-ориентированным языком, где

даже встроенные в язык типы являются объектами классов;

Page 16: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

16

C# является мощным объектным языком с возможностями насле-

дования и универсализации (создания обобщенных классов);

C# является наследником языка C++, сохраняя лучшие черты это-

го популярного языка программирования.

Язык C# активно развивается и в 2008 году текущей версией язы-

ка была C# 3.0.

Прежде чем начать непосредственно рассматривать программиро-

вание на языке C# следует кратко пояснить основы объектно-

ориентированного подхода.

2.1. Основы объектно-ориентированного подхода В отличие от обычного процедурного программирования при объ-

ектно-ориентированном программировании (ООП) основными элемен-

тами программы являются не переменные и методы (процедуры), а объ-

екты. Объекты – это программные конструкции, включающие набор

логически связанных свойств (данных) и методов. Объекты являются

автономными сущностями, они предоставляют некоторую функцио-

нальность другим компонентам среды приложения, изолируя от них

свое внутреннее устройство. Объекты создаются на основе шаблонов,

которые называются классами и являются экземплярами этих классов.

Библиотека .Net платформы FCL предоставляет набор уже созданных

классов, которые можно применять для создания объектов в разрабаты-

ваемых приложениях. Но в приложениях также создаются и другие

классы, требуемые для описания решаемой задачи. Например, класс Ав-

томобилей: class Автомобиль

{

// описание данных

// описание методов

}

Классы как шаблоны объектов

Можно сказать, что классы – это «шаблоны» (чертежи) объек-

тов. Они определяют все элементы объекта: его свойства и его поведе-

ние (методы), а также задают начальные значения для создаваемых объ-

ектов, если это необходимо. При создании экземпляра класса в памяти

создается копия этого класса. Созданный таким образом экземпляр

класса называют объектом. Экземпляр класса можно создать посред-

ством специальной операции new, например, так: // Объявление переменной типа Автомобиль

Автомобиль myAuto; // переменная это не объект класса!

// Создание экземпляра класса Автомобиль

Page 17: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

17

// и сохранение ссылки на него в переменной myAuto

myAuto = new Автомобиль ();

При создании экземпляра класса, выделяется блок оперативной

памяти, в который записывается копия данных, и адрес этого блока

присваивается переменной, в данном случае myAuto, которая хранит

эту ссылку, Экземпляры класса не зависят друг от друга и являются от-

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

создавать произвольное число копий класса, которые могут существо-

вать одновременно. Продолжая параллель с реальным миром, можно

сказать: если считать конкретный автомобиль объектом, то чертежи ав-

томобиля представляют собой класс Автомобиль. По чертежу можно

сделаете сколько угодно автомобилей. Если один из автомобилей будет

работать не так, как все, это никак не повлияет на остальные.

2.1.1. Объекты и элементы

Объекты состоят из элементов, к которым относят поля, свойст-

ва, методы и события, представляющие данные и функциональность

объекта. Поля содержат данные объекта, свойства – предоставляют

управляемый способ доступа к данным объекта, методы – определяют

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

заинтересованных пользователей (другие класса, которые подпишутся

на эти события), если в объекте происходит что-то важное.

Свойства объектов класса Автомобиль, например, такие как

Цвет, Модель, Расход_топлива и т.д., являются данными, описы-

вающими состояние объекта, а методы этих объектов, такие, как на-

пример Нажать_акселератор, Переключить_передачу или Повер-

нуть_руль описывают функциональность автомобиля. Методы позво-

ляют реализовать поведение объекта. События представляют собой

уведомления о важных происшествиях, например о том, что количество

бензина стало ниже заданной величины, или объект класса Двигатель

может уведомлять объект класса Автомобиль о событии Пере-

грев_двигателя. В этом случае описание класса Автомобиль может

выглядеть следующим образом: class Автомобиль

{

// описание свойств

public string Mодель;

public float Расход_топлива;

private int Число_цилиндров;

// описание методов

public void Повернуть_руль(){...};

private Регулировка_датчика(){...};

Page 18: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

18

// описание события

event Перегрев_двигателя();

}

Для доступа к полям, свойствам, методам элементам объектов ис-

пользуется специальная операция точка (.). Например: myAuto.Модель // определение Модели

myAuto.Повернуть_руль() // поворот руля автомобиля

Однако к элементам объекта имеется доступ не из всех частей

программы (из методов данного класса или других классов, из других

сборок). Возможность использования элементов класса задается (явно

или неявно) с помощью указания режима доступа. Например, таким ре-

жимом может быть private – означающий, что элемент, у которого он

задан, может использоваться только в методах того класса, где он опи-

сан (закрытые элементы), или режим public – означающий, что дан-

ный элемент можно использовать и в других класса (открытые элемен-

ты).

2.1.2. Отношения между классами

Классы разных объектов не являются изолированными друг от

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

два основных типа взаимосвязи: вложенность и наследование. Вло-

женность – это включение объектов одного класса в объекты другого

класса. Наследование – это описание одного класса на основе другого

класса.

Объектные модели

Объекты одних классов могут включать объекты других классов в

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

им элементам. Иерархия вложенности объектов друг в друга называется

объектной моделью (object model).

Например, в случае с автомобилем, объект класса Автомобиль,

который сам по себе является объектом, состоит из ряда вложенных

объектов, например, таких как объект класса Двигатель, четырех объ-

ектов класса Колесо, объект класса Трансмиссия и т.д. Компоновка

вложенных объектов непосредственно определяет работу объекта клас-

са Автомобиль. Например, поведение объектов Автомобиль, у кото-

рых свойство Число_цилиндров вложенного объекта Двигатель рав-

но соответственно 4 и 8, будет различным. В свою очередь у вложенных

объектов могут быть собственные вложенные объекты. Например, объ-

ект Двигатель (который является вложенным объектом объекта Авто-

мобиль) может иметь несколько вложенных объектов Све-

Page 19: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

19

ча_зажигания. Например, для получения сведений о марке свечей за-

жигания автомобиля можно записать следующее выражение: myAuto.Свеча_зажигания.Марка;

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

Один класс может быть описан на основе уже имеющегося описа-

ния другого класса. В этом случае между классами задается отношение

наследования. Наследование позволяет создавать новые классы на осно-

ве существующих, при этом новые классы обладают всей функциональ-

ностью старых и при необходимости могут модифицировать их. Класс,

объявленный на основе некоторого (базового) класса, называется про-

изводным или классом-потомком. У любого класса может быть только

один прямой предок – его базовый класс (base class). У производного

класса окажется тот же набор элементов, что и у базового, но, при необ-

ходимости, к производному классу разрешается добавлять дополни-

тельные элементы. Можно также изменить реализацию членов, унасле-

дованную от базового класса, переопределив их.

Рис. 2.1. Схема наследования классов.

Например, на основе описания класса Транспортное_средство

(базовый класс) можно описать класс Автомобиль (производный класс)

на основе которого, в свою очередь, можно описать классы: Грузовик,

Пассажирский_автомобиль и Спортивный_автомобиль (тоже про-

изводные классы). Такая схема наследования показана на рис. 2.1. При-

мер описания производного класса Автомобиль показан ниже:

class Автомобиль : Транспорт

{

// описание свойств

public string model;

public float Расход_топлива;

private int Число_цилиндров;

// описание методов

public void Повернуть_руль(){...};

Транспорт

Корабль Автомобиль Самолет

Грузовик Пассажирский Спортивный

Page 20: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

20

private Регулировка_датчика(){...};

// описание события

public event Перегрев_двигателя();

}

В производном классе сохраняется функциональность (поведе-

ние), определенная в базовом классе. Кроме того, в производных клас-

сах могут описываться новые элементы и переопределяться методы (по-

ведение), описанные в базовых классах.

Для указания возможности доступа к наследуемым элементам

(помимо public) также используется специальный режим доступа

protected.

2.1.3. Основные принципы ООП

К основным принципам (положениям) ООП относятся: абстраги-

рование, инкапсуляция, наследование и полиморфизм.

Абстрагирование

Объект – это программная конструкция, представляющая некото-

рую сущность. В нашей повседневной жизни сущностями, или объекта-

ми, например, можно считать: автомобили, велосипеды, настольные

компьютеры, банковский счет. Каждый объект обладает определенной

функциональностью и свойствами. Объект представляет собой завер-

шенную функциональную единицу, содержащую все данные и предос-

тавляющую всю функциональность, необходимую для решения задачи,

для которой он предназначен. Описание объектов реального мира при

помощи программных объектов называют абстрагированием

(abstraction).

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

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

(его внутреннего содержания) от способа взаимодействия с ним. Другие

объекты приложения взаимодействует с рассматриваемым объектом по-

средством имеющихся у него открытых (public) свойств и методов,

которые составляют его интерфейс. В общем виде под интерфейсом

понимается открытый способ взаимодействия между разными система-

ми. Если интерфейс класса не будет меняться, то приложение сохраняет

способность к взаимодействию с его объектами, даже если в новой вер-

сии класса его реализация значительно изменится.

Объекты могут взаимодействовать друг с другом только через

свои открытые методы и свойства, поэтому объект должен предостав-

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

необходимы. Интерфейс ни в коем случае не должен открывать доступ

Page 21: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

21

к внутренним данным объекта, поэтому поля с внутренними данными

объекта обычно объявляют с модификатором private.

Например у объектов класса Автомобиль, которые могут взаимо-

действовать с объектами класса Водитель через открытый интерфейс,

открытыми объявлены только методы Ехатъ_вперед, Ехать_назад,

Повернуть и Остановиться – их достаточно для взаимодействия объ-

ектов классов Водитель и Автомобиль. У объекта класса Автомобиль

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

объектов класса Водитель, которому будут открыты лишь методы, тре-

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

вложенный объект класса Двигатель, и взаимодействующий с ним

объект класса Водитель не заметит замены, если она не нарушит кор-

ректную работу интерфейса.

Полиморфизм

Благодаря полиморфизму, одни и те же открытые интерфейсы

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

полиморфизм позволяет вызывать методы и свойства объекта независи-

мо от их реализации. Например, объект класса Водитель взаимодейст-

вует с объектом класса Автомобиль через открытый интерфейс. Если

другой объект, например Грузовик или Гоночный_автомобиль, под-

держивает такой открытый интерфейс, то объект класса Водитель

сможет взаимодействовать и с ними (управлять ими), невзирая на раз-

личия в реализации интерфейса. Основных подходов к реализации по-

лиморфизма два: через интерфейсы и через наследование, которые бу-

дут рассмотрены далее.

Реализация полиморфизма с помощью интерфейсов

Интерфейс (interface) – это соглашение, определяющее набор от-

крытых методов, реализованных классом. Интерфейс определяет список

методов класса, но ничего не говорит об их реализации. В объекте до-

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

можно реализовать в разных классах. Например, можно описать интер-

фейс возможности управления некоторыми объектами: // имена интерфейсов обычно начинаться с буквы I

interface IDrivable {

int Ехать(...);

float Повернуть(...);

bool Остановиться(...);

}

Если класс реализует какой-то интерфейс, то в нем должны быть

описаны все методы этого интерфейса. Например:

Page 22: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

22

class Automobile : Транспорт, IDrivable

{

int Ехать(...) {<реализация метода>};

float Повернуть(...){<реализация метода>};

bool Остановиться(...){<реализация метода>};

// описание других элементов ...

}

Любые объекты, в которых реализован некоторый интерфейс,

способны взаимодействовать друг с другом с его помощью. Интерфейс

IDrivable также можно реализовать и в других классах, например, та-

ких, как Грузовик, Автопогрузчик или Катер. В результате, эти

объекты получат возможность взаимодействия с объектом класса Води-

тель. Объект класса Водитель находится в полном неведении относи-

тельно реализации интерфейса, с которым он взаимодействует, ему из-

вестен лишь сам интерфейс. Подробнее о реализации полиморфизма с

использованием интерфейсов рассматривается в разделе 6.3.

Реализация полиморфизма через наследование

Производные классы сохраняют все характеристики своих базо-

вых классов и способны взаимодействовать с другими объектами, под

видом экземпляров базового класса! Т.е., переменным базового типа

можно присваивать ссылки на объекты производных классов. Напри-

мер: Aвтомобиль myAuto; // переменная это не объект класса!

Спортивный_автомобиль sportAuto =

new Спортивный_автомобиль();

// можно присвоить, так как есть наследование

myAuto = sportAuto;

В этом случае можно выполнять работу с объектом производного

класса, как если бы он был объектом базового класса. Реализация поли-

морфизма через наследование более подробно рассматривается в разде-

ле 5.2.

2.2. Структура приложений на языке С# Платформа .Net и язык C# полностью соответствуют объектно-

ориентированному подходу. В общем виде это означает, что программа

состоит только из описанных разработчиком типов данных, а также ис-

пользует типы данных расположенные в библиотеке FCL и других

сборках, на которые делаются ссылки (рис. 2.2). Как уже отмечалось,

платформе .Net под типами понимаются: классы, структуры, интерфей-

сы, перечисления и делегаты. Т.е. программа это набор типов, в основ-

ном – классов. Выполнение программы заключается в создании объек-

Page 23: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

23

тов этих классов, и в вызове методов этих объектов. Даже самая простая

программа на языке C# включает один класс. Один из классов должен

включать статический открытый метод Main(), с выполнения которого

и начинается работа приложения.

Рис. 2.2. Распределение пользовательских типов программы по сборкам.

Так как программа на языке C# это просто набор объявлений ти-

пов, то изучение данного языка состоит в изучении того, как создавать и

использовать типы. Общая схема описания приложения, в виде набора

пользовательских типов, показана на рис. 2.3. А на рис. 2.4 показан

пример описания пользовательского типа – class.

using <имя namespace>;

...

namespace Nnnn

{

Объявление класса

class Cccc

...

Объявление структуры

struct Ssss

...

Объявление интерфейса

interface Iccc

...

}

class Cccc

{

Объявление поля _a

...

Объявление свойства A

...

Объявление метода M( )

...

Объявление события

event A

...

}

Рис. 2.3. Структура приложения. Рис. 2.4. Структура описания класса.

На рис. 2.3 видно, что основные части программы, такие как, про-

странства имен и классы, состоят из заголовков и блоков. В заголовке

Page 24: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

24

указывается тип элемента программы и его имя, например namespace

Nnnn или class Сссс.

Блок образуется с помощью пары фигурных скобок ({ и }), между

которыми записываются объявления различных типов, либо элементы

классов, либо набор операторов языка C#.

2.2.1. Общая структура программы на языке C#

Общая структура приложения может быть описана следующим обра-

зом:

1. Программа состоит из описаний пользовательских типов (в ос-

новном классов).

2. Описания классов состоят из описания полей (переменных) и ме-

тодов.

3. Описание переменных состоит из указания типа и имени пере-

менной.

4. Описание методов состоит из описания локальных переменных и

набора операторов.

5. Оператор состоит из набора ключевых слов и выражений.

6. Выражения состоят их переменных и констант, связанных знака-

ми операций.

Прежде всего, сделаем несколько общих пояснений синтаксиса

языка C#. В данном языке, как и в других языках, основанных на языке

С, большинство операторов заканчивается символом “точка с запятой”

(;). Несколько операторов может записываться в одной строке или один

оператор может записываться в нескольких строках. Операторы могут

объединяться в блоки с помощью фигурных скобок ({}).

В текст программы могут вставляться комментарии (компилятор

их не обрабатывает). Комментарий, расположенный на одной строке,

начинается с двух последовательных символов “косая черта” (//), а

комментарий, расположенный на нескольких строках начинается с сим-

волов /* и заканчивается символами */.

Для всех элементов языка C#, таких как, переменные, поля, мето-

ды, классы и т.п., задаются имена – идентификаторы.

2.2.2. Идентификаторы в языке C#

Идентификатор может состоять из символов (букв латинского и

русского алфавитов, больших и маленьких) цифр и знака подчеркива-

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

Язык C# чувствителен к регистру и компилятор различает маленькие и

большие буквы. Это касается написания и идентификаторов программы

Page 25: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

25

и ключевых слов языка. Это означает, что идентификатор Aaaa и aaaa

являются разными, также как class и Class.

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

пространства имен, к которому он принадлежит, и самого идентифика-

тора (имени) пользовательского типа, которые объединяются символом

точка “.”, например: Nnnn.Aaaa (пользовательский тип Aaaa в про-

странстве имен Nnnn) или ConsoleApp.Program (класс Program из

приложения ConsoleApp).

Все идентификаторы должны быть уникальными в своей области

действия:

Областью действия пользовательских типов является пространст-

во имен, в котором оно описано.

Областью действия элементов класса является класс, в котором

они описаны.

Областью действия локальных переменных метода является блок

операторов данного метода или метод в целом.

2.2.3. Типы приложений

На языке C# можно разрабатывать приложения с консольным ин-

терфейсом (консольные приложения) и графическим интерфейсом

(Windows приложения). Тип создаваемого приложения указывается при

запуске компилятора. Первоначально в пособии рассматриваются кон-

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

их проще создавать. В главе 8 будет рассмотрено создание Windows

приложений, которые требует знания работы с классами на языке C#, и

используют большое количество классов библиотеки FCL.

2.3. Пример простого приложения Рассмотрим пример простого консольного приложения, которое

вычисляет площадь круга для заданного пользователем программы ра-

диуса: using System;

namespace ConsoleApp

{

class Program

{

static void Main(){

Console.Write("Введите радиус круга:");

string s = Console.ReadLine();

double r = Convert.ToDouble(s);

double p = Math.PI * r * r;

Console.WriteLine("Площадь круга = {0}", p);

Page 26: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

26

Console.ReadLine();

return;

}

}

}

Запишем данный код в файл ConsoleApp.cs (например, с ис-

пользованием стандартного приложения «Блокнот» (Notepad)). Для соз-

дания управляемого приложения можно использовать компилятор язы-

ка C# – csc.exe: csc.exe ConsoleApp.cs

Результат выполнения созданной компилятором программы Con-

soleApp.exe, показанный на рис. 2.5.

Рис. 2.5. Результат работы простой программы.

В первой строке текста простой программы указано, какое про-

странство имен (в данном случае System) будет использоваться. Про-

странства имен – это способ объединения в группы связанных классов.

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

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

ные в программе классы. Утверждение using указывает на пространст-

во имен, которое должен просматривать компилятор для поиска описа-

ния класса, у которого не задано полное имя.

Причиной использования утверждения using в приведенной про-

грамме является то, что в ней используются классы из библиотеки FCL,

которые включены в пространство имен System. Использование утвер-

ждения using System; позволяет ссылаться на классы Sys-

tem.Console и System.Convert, как Console и Convert, без указа-

ния пространства имен. В стандартном пространстве имен System со-

держатся базовые типы данных платформы .NET. Важно понимать, что

функциональность языка C#, в значительной степени основывается на

базовых классах платформы .NET. Сам язык C# не имеет встроенных

операторов ввода – вывода, также как и встроенных типов, а использует

базовые типы, описанные в библиотеке FCL платформы .NET.

Page 27: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

27

В следующей строке рассматриваемой программы объявляется

новое пространство имен ConsoleApp, в котором описывается один

класс Program: namespace ConsoleApp {

class Program{

. . .

}

}

Полным именем данного класса является ConsoleApp.Program.

Весь код программы C# должен содержаться только внутри классов.

Описание классов в C# состоит из ключевого слова class, за которым

стоит название класса и пара фигурных скобок (блок). Весь код, связан-

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

В классе Program описан только один метод с именем Main() (отме-

тим, что первая буква имени это большая M):

static void Main() { ... }

Данный метод запускается автоматически (является точкой входа)

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

может возвращать целое значение (int) или не возвращать ничего

(void). Данный метод C# соответствует методу main() в языках C++ и

Java. Описание методов в C# имеет следующую структуру:

[модификаторы] тип_результата имя_метода ([параметры])

{

// содержание метода

}

Здесь квадратные скобки указывают необязательные элементы описа-

ния. Модификаторы используются для задания некоторых особенностей

методов, например, таких как откуда и как данный метод может вызы-

ваться. В расматриваемом примере программы используются два моди-

фикатора: public и static. Модификатор (режим доступа) public

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

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

конкретным экземпляром класса и может вызываться без использования

ссылки на экземпляр. Это является важным для запуска программы на

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

примере задан тип результата void (означающий, что никакого резуль-

тата нет) и для метода Main() не описаны передаваемые параметры.

И, наконец, рассмотрим операторы, содержащиеся в методе

Main(). Первый оператор выполняет вывод подсказки с помощью ме-

тода Write() класса Console:

Page 28: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

28

Console.Write("Введите радиус круга:");

Данный метод выводит строку текста на экран в консольное окно, но не

переводит курсор на начало следующей строки. Для ввода данных поль-

зователя используется метод ReadLine()того же класса Console для

получения данных с клавиатуры (при этом сразу же объявляется пере-

менная s типа string): string s = Console.ReadLine();

Пользователь может ввести число (для отделения дробной части ис-

пользуется запятая, а не точка, как в коде программы, например: 2,5) и

нажать клавишу Enter. Метод ReadLine()возвращает текстовую

строку (тип string). Для того, чтобы преобразовать значение тексто-

вой строки s в вещественное значение для переменной r (которая также

объявляется в данной строке, как тип double) используется метод To-

Double(s)класса Convert: double r = Convert.ToDouble(s);

Полученное значение радиуса используется для вычисления величины

площади круга, которое сохраняется в переменной p: double p = Math.PI * r * r;

Отметим, что для вычисления используется значение числа π, которое

можно получить из статического класса System.Math библиотеки FCL.

Для вывода вычисленного значения используется метод Write-

Line() со строкой форматирования: Console.WriteLine("Площадь круга = {0}", p);

И, наконец, для того, чтобы консольное окно не закрылось ав-

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

Line(), который приостанавливает выполнение программы до нажатия

клавиши Enter: Console.ReadLine();

После этого выполняется оператор return, который вызывает за-

вершение работы метода. Обычно данный оператор возвращает резуль-

тирующее значение, но так как у данного метода указан тип void в за-

головке, то никакого значения не возвращается. В этом случае данный

оператор можно было бы и не записывать.

2.4. Создание выполняемой программы Основным инструментом преобразования исходного текста про-

граммы на языке C# в выполняемый модуль для платформы .Net являет-

ся компилятор. Можно составить программу, записать ее с помощью

стандартной программы “Блокнот” в текстовый файл и преобразовать в

сборку с помощью компилятора csc.exe:

Page 29: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

29

csc.exe <имя файла с программой> [опции компиляции]

У компилятора есть много разных опций, из которых основными явля-

ются следующие:

/target:[exe | winexe | library | module] – тип созда-

ваемого модуля: exe – консольное приложение (по умолчанию);

winexe –Windows приложение; library – библиотека классов

(без метода Main); module – модуль (в модуль не добавляется

декларация)

/reference:<список файлов> – имена сборок (собственных

или из FCL), на которые будут делаться ссылки в создаваемом

модуле; основные сборки FCL (такие, как System.dll) подклю-

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

/out:<имя файла> – имя создаваемого модуля, если оно не сов-

падает с именем входного файла.

Для упрощения и автоматизации разработки программ на языке

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

систем разработки Visual Studio .Net Professional (VS Professional) или

Visual C# Express Edition (VS EE). Следует отметить, что сами они так-

же используют компилятор языка C#, но, кроме этого, предоставляют

большое количество средств автоматизации создания и отладки про-

грамм. Логика работы с этими системами во многом сходна. В данном

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

систем разработки.

Система разработки Visual C# Express Edition позволяет создавать

только консольные и Windows приложения на языке C#. А система Vis-

ual Studio .Net Professional позволяет создавать консольные, Windows и

Интернет приложения на разных языках. Разработка приложений в этих

системах основана на понятии проект, под которым понимается множе-

ство файлов с описаниями классов, ссылками на используемые сборки,

с другими типами данных, а также с параметрами для запуска компиля-

ции. Все файлы проекта хранятся в одной специально создаваемой пап-

ке. Кроме понятия проекта используется понятие решения. Решение со-

держит один или несколько проектов, ресурсы, необходимые этим про-

ектам, возможно, дополнительные файлы, не входящие в проекты.

Один из проектов решения должен быть указан, как стартовый про-

ект. Выполнение решения начинается со стартового проекта. Проек-

ты одного решения могут быть зависимыми или независимыми. Изме-

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

примеру. Отметим, стартовый проект должен иметь точку входа –

Page 30: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

30

класс, содержащий статическую метод с именем Main(), которому ав-

томатически передается управление в момент запуска решения на вы-

полнение. В уже имеющееся решение можно добавлять как новые, так и

существующие проекты. Для каждого решения создается папка в за-

данном месте. В этой папке для каждого проекта решения создаются

собственные подпапки, в которых будут создаваться другие подпапки с

результатами компиляции приложения.

Проект – это основная единица, с которой работает программист.

Он выбирает тип проекта, а Visual Studio создает скелет проекта в со-

ответствии с выбранным типом.

Проект создания консольного приложения

Для создания проекта, в котором будет создаваться консольное

приложение, нужно выполнить команду меню File->New->Project и

выбрать вид проекта – Console Application, а затем задать имя проекта,

например, HelloApp и указать папку, в которой будет храниться про-

ект (рис. 2.6).

Рис. 2.6. Окно создания нового проекта.

Если принять эти установки, то компилятор создаст решение, имя

которого совпадает с именем проекта. На рис. 2.7 показано, как выгля-

дит это решение в среде разработки. Интегрированные системы разра-

боток является многооконными, настраиваемыми и обладают большим

набором возможностей. Можно отметить следующие четыре основных

окна.

В окне Solution Explorer представлена структура создаваемого ре-

шения. В окне Properties можно увидеть свойства выбранного элемента

программы. В окне редактора текстов (центральное окно) отображается

выбранный документ, в данном случае, программный код класса проек-

Page 31: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

31

та – HelloApp.Program. Отметим, что в этом окне можно отображать

и другие документы, список которых показан в верхней части окна. В

окне Output (под окном редактора) показываются результаты компиля-

ции программы (панель Error List), результаты ее выполнения (панель

Output) или результаты поиска (панель Find Symbols Results).

Построенное решение содержит единственный заданный нами

проект – HelloApp. Создаваемый проект включает логические папки

со свойствами проекта (папка Properties) и со ссылками на подклю-

ченные сборки из библиотеки FCL (папка Referencies) и файл с рас-

ширением cs. Файл со стандартным именем Program.cs содержит по-

строенный по умолчанию класс, включающий точку входа – метод

Main() (в данном случае пустой).

Рис. 2.7. Окно системы разработки и консольное приложение, построенное

по умолчанию.

Класс проекта включен в пространство имен, которое по умол-

чанию имеет то же имя, что и решение, и проект. Таким образом, при

создании нового проекта автоматически создается специальная струк-

тура – решение, которая включает проект, содержащий пространство

имен, в котором описан класс с методом Main(), являющимся точкой

входа в программу. Для простых решений такая структура может пока-

Page 32: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

32

заться избыточной, но она позволяет хорошо структурировать сложные

приложения.

Функциональность консольного проекта, построенного по умол-

чанию, небольшая. Его можно скомпилировать, выбрав соответствую-

щий пункт (команду) из меню Build. Если компиляция прошла без

ошибок, то в результате будет создана сборка и в папке Debug появится

EXE файл разрабатываемого проектa. Приложение можно запустить

нажатием, например, соответствующих клавиш (CTRL + F5) или выбо-

ром соответствующего пункта из меню Debug. Приложение будет вы-

полнено под управлением среды CLR. В результате выполнения про-

граммы появится консольное окно с предложением нажать любую кла-

вишу для закрытия окна.

2.5. Базовые классы FCL для консольных приложений На языке C# невозможно разработать даже простую программу

без знания базовых классов библиотеки FCL. Для консольных приложе-

ний, такими базовыми классами являются статические классы

Console,Convert и Math из пространства имен System.

2.5.1. Класс Console

При создании всех консольных приложений для ввода данных с

клавиатуры и вывода текста в стандартное консольное окно использует-

ся статический класс System.Console из библиотеки FCL. Данный

класс является статическим, т.е. все методы данного класса доступны не

с помощью ссылки на объекты класса, а с помощью названия класса,

например, Console.Write(). Основные методы класса Console при-

ведены в табл. 2.1.

Таблица 2.1.

Основные методы статического класса Console

Метод Описание ReadLine() читает строку символов, введенную с клавиатуры, которая

завершается нажатием клавиши Enter; возвращает строку

типа string;

Read() ждет нажатия клавиш, которые завершаются нажатием кла-

виши Enter и возвращает код первого введенного символа;

если в буфере есть символы, то они читаются;

Write(),

WriteLine()

вывод значений различных типов в консольное окно; без пе-

рехода и с переходом на новую строку;

Clear() очистка содержания консольного окна;

Beep() издается звуковой сигнал через динамик компьютера.

Page 33: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

33

Ниже приведен пример ввода данных с клавиатуры, использую-

щий метод ReadLine(), возвращающий строку данных: string ss;

ss = Console.ReadLine();

int a = Convert.ToInt32(ss);

Как показано в данном примере, для преобразования строковых

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

класс System.Convert, который, кроме других преобразований между

значениями встроенных типов, выполняет преобразование строковых

типов в заданный встроенный тип. Для этого у него имеется множество

методов с названием Convert.To<тип>(string s), где <тип> – это

название системного типа CLR, в который выполняется преобразование.

Соответствие между системными типами и встроенными типами языка

C# можно посмотреть в табл. 3.1. Следует отметить, что для перемен-

ных с дробной частью следует использовать запятую (а не точку, как

это принято в языке программирования). Например: string s = "45,75"; // используется запятая, как обычно float ff = Convert.ToSingle(s);

Метод Write() с форматированием

В классе Console имеется много перегруженных вариантов мето-

дов Write() и WriteLine(). Одним из наиболее используемых вари-

антов являются методы, выполняющие форматирование вывода с заго-

ловком: public static string WriteLine(string, obj0, obj1,...);

Например: Console.WriteLine("s1={0}, s2={1}", s1,s2);

При вызове данного метода в качестве первого параметра типа

string передается строка, которая задает формат вывода на экран.

Форматирующая строка помимо простого текста (который выводится

без изменений), также может содержать спецификации, заключенные в

фигурные скобки, например, "x={0}".Число спецификаций, включен-

ных в форматирующую строку должно соответствовать число значений,

стоящих после нее. Каждая спецификация задает форматирование выво-

да значения, на которую она ссылается, и которое, после преобразова-

ния его в строку, будет выведено на экран вместо нее. Формат специфи-

кации имеют следующий общий вид: {N [,M [:<коды_форматирования>]]}

Здесь обязательный параметр N задает индекс объекта, который обраба-

тывается данной спецификацией. Индексация объектов начинается с

Page 34: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

34

нуля. Второй параметр M, если он задан, определяет минимальную ши-

рину поля, которое отводится строке, вставляемой вместо специфика-

ции. Третий необязательный параметр задает шаблон вывода или коды

форматирования, указывающие, как следует выполнять вывод значения.

Например, код C (Currency) говорит о том, что параметр должен фор-

матироваться как валюта с учетом национальных особенностей пред-

ставления. Код P (Percent) задает форматирование в виде процентов с

точностью до сотой доли. Можно составить шаблон вывода из символов

#, 0 и десятичной точки (например: ####.00), где # – соответствует про-

белу или цифре (пробелы в начале области вывода не делаются), а 0 –

только цифре. Пример использования метода WriteLine() показан

ниже: public void TestFormat(){

float x = 77.77;

Console.WriteLine("x={0}",x);

Console.WriteLine("Итого:{0,10} рублей",x);

Console.WriteLine("Итого:{0,6:######} рублей",x);

Console.WriteLine("Итого:{0:P} ",0.77);

Console.WriteLine("Итого:{0,4:C} ",77.77);

}

Результат работы данного кода показан на рис. 2.8.

Рис. 2.8. Результаты работы методов Console.WriteLine().

2.5.2. Класс Math

Класс System.Math из библиотеки FCL предоставляет константы

и статические методы для тригонометрических, логарифмических и

других общих математических функций. Основные константы и методы

данного класса приведены в табл. 2.2.

Таблица 2.2.

Основные элементы статического класса Math

Элементы Описание E константа, равная значению натурального числа e; PI константа, равная значению натурального числа ;

Pow (x,y) возведение числа x в степень y – xy; параметры и результат

имею тип double;

Sin(x), вычисление тригонометрических функций синуса, косинуса и

Page 35: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

35

Cos(x),

Tan(x)

тангенса от числа х (тип double);

Log(x),

Log10(x)

вычисление натурального и десятичного логарифмов от числа

x (тип double);

Exp(x) возведение e в заданную степень eх;

Sqrt(x) вычисление корня квадратного от числа х; Min(x,y),

Max(x,y)

вычисление минимума или максимума двух чисел; перегру-

женные методы.

Практически все методы используют параметры типа double и возвра-

щают результат типа double.

2.5.3. Класс Random

Класс формирования псевдослучайных чисел выбираемых с оди-

наковой вероятностью из заданного множества чисел. При генерации

случайных чисел используется некоторое начальное базовое значения

(seed), которое можно задать в конструкторе. Если используется одно и

то же базовое значение, то формируется одинаковая последовательность

чисел. По умолчанию класс Random использует текущее время компью-

тера для формирования начального значения. С помощью метода

NextDouble() можно получать значения типа double из интервала от

0 до 1. С помощью метода Next(int a, int b) возвращает следую-

щее целое случайное число в интервале от a до b. Например: Random rnd = new Random(55); // задаем

for (int i=0; i<5; i++)

Console.Write("{0}; ", rnd.Next(0,100));

Результатом работы будет: 45; 95; 21; 29; 3;

3. Основные понятия языка C# Основными элементами языка программирования являются типы

данных, переменные, выражения, операторы и методы.

3.1. Типы данных Язык C# является строго типизированным языком. Это означает,

что все данные (константы и переменные), с которыми работает про-

грамма, имеют явно или неявно заданный тип. Под типом данных по-

нимается набор доступных значений, которые может хранить пе-

ременная данного типа, и разрешенный набор операций над этими

значениями.

Page 36: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

36

3.2.1. Система типов языка С#

В языке C# различает две категории типов данных: значащие ти-

пы (value types) и ссылочные типы (reference types). Основное различие

между ними состоит в том, что значащие типы хранят непосредственно

свои значения, а ссылочные типы непосредственно хранят ссылки на

свои значения. К значащим типам относятся:

встроенные типы (описанные в библиотеке FCL), которые хра-

нят одиночные значения, как например, int – целые; float – ве-

щественные значения; bool – логические;

структуры – сложные типы (описаны в разделе 6.1), которые во

многом сходны с классами, но значения которых хранятся в стеке

(см. далее в разделе 3.2.2).

К ссылочным типам относятся классы, объекты которых хранятся

в "куче" (см. далее в разделе 3.2.2). С учетом выше сказанного, структу-

ра типов языка C# показана на рис. 3.1.

Рис. 3.1. Структура типов языка C#.

Все типы языка C# также делятся на две большие группы: встро-

енные и описываемые разработчиками.

Встроенные (или фундаментальные) типы изначально принад-

лежат базисной системе типов, поддерживаемой средой CLR. Но в раз-

ных языках программирования эти типы могут иметь свои собственные

имена. В соответствие со стандартом общих типов (Common Type Stan-

dard – CTS) в .Net имеется 15 встроенных типов (см. табл. 3.1).

Типы, описываемые разработчиками. Кроме встроенных типов,

которые предоставляются в языке C#, программист может описывать и

использовать свои собственные (пользовательские) типы. Имеются сле-

дующие пользовательские типы:

1. классы (class) (см. раздел 4);

2. структуры (struct) (см. раздел 6.1);

Page 37: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

37

3. перечисления (enum) (см. раздел 6.2);

4. интерфейсы (interface) (см. раздел 6.3);

5. делегаты (delegate) (см. раздел 4.10.1).

Пользовательские типы создаются разработчиками и составляют са-

му программу. После того, как тип описан в программе, можно созда-

вать и использовать объекты данного типа, точно так же, как если бы

они были встроенными типами.

3.2.2. Хранение данных в оперативной памяти

При выполнении программы все ее данные хранятся в оператив-

ной памяти компьютера. Количество памяти, требуемой для экземпля-

ров данных (в байтах), и место их хранения, зависит от их типа. При

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

оперативной памяти, которые называются стеком (stack) и "кучей"

(heap). На рис. 3.2 схематически показаны стек и "куча" и связь с ними

переменных программы.

Рис. 3.2. Использование в программе стека и "кучи".

Стек (stack) – это линейный участок памяти (массив), который

действует как структура данных типа «Последним пришел – первым

ушел» (last-in, first-out – LIFO). Основной особенностью стека являются

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

вершины. Добавление и удаление данных из произвольного места стека

невозможно. Операции по добавлению и удаление элементов из стека

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

чен, и время хранения данных зависит от времени жизни переменной.

Для всех локальных переменных методов и передаваемых методам па-

Page 38: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

38

раметров память выделяется в вершине стека. После того, как методы

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

автоматически освобождается.

Куча (heap) – это область оперативной памяти, в разных частях

которой могут выделяться участки для хранения объектов классов. В

отличие от стека, такие участки памяти в "куче" могут выделяться и ос-

вобождаться в любом порядке. Хотя программа может хранить элемен-

ты данных в "куче", она не может явно удалять их из нее. Вместо этого

компонент среды CLR, называемый «Сборщиком мусора» (Garbage Col-

lector, GC), автоматически удаляет неиспользуемые участки "кучи", ко-

гда он определит, что код программы уже не имеет доступа к ним (не

хранит их адреса).

Локальные переменные методов хранятся следующим образом:

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

хранит реальные данные и размещается в стеке.

Ссылочные типы требуют два участка памяти: первый – содержит

реальные данные (сами объекты) и всегда размещается в "куче";

второй – размещается в стеке и содержит ссылку (адрес), которая

указывает на размещения объекта в "куче".

Поля классов (переменные объектов, рассмотрены в разделе 4.1)

хранятся в участке "кучи", выделенном для конкретного объекта. При

создании объекта выделяется участок "кучи", в котором и сохраняются

все его данные (для методов объекта память не выделяется).

3.2.3. Встроенные типы данных

Все встроенные типы языка C# однозначно соответствуют сис-

темным типам платформы .Net Framework, описанным в пространстве

имен System. Поэтому всюду, где можно использовать имя типа, на-

пример, – int, с тем же успехом можно использовать и имя

System.Int32. Описание встроенных типов языка C# и их основные

характеристики показано в табл. 3.1.

Таблица 3.1.

Основные характеристики встроенных типов языка C#

Имя

типа

Системный тип

CLR Значения - диапазон Размер - точность

Логический тип

bool System.Boolean true, false 8 бит

Арифметические целочисленные типы

sbyte System.SByte -128 – +127 Знаковое, 8 бит

byte System.Byte 0 – 255 Беззнаковое, 8 бит

Page 39: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

39

short System.Short -32768 – 32767 Знаковое, 16 бит

ushort System.UShort 0 – 65535 Беззнаковое, 16 бит

int System.Int32 ≈(-2*10^9 – 2*10^9) Знаковое, 32 бит

uint System.UInt32 ≈(0 – 4*10^9) Беззнаковое, 32 бит

long System.Int64 ≈(-9*10^18 – 9*10^18) Знаковое, 64 бит

ulong System.UInt64 ≈(0 – 18*10^18) Беззнаковое, 64 бит

Арифметический тип с плавающей точкой

float System.Single +1.5*10^-45 –

+3.4*10^38

32 бита (точность 7

цифр)

double System.Double +5.0*10^-324 –

+1.7*10^308

64 бита (точность 15–16

цифр)

Арифметический тип с фиксированной точкой

decimal System.Decimal +1.0*10^-28 –

+7.9*10^28

28–29 значащих цифр

Символьные типы

char System.Char U+0000 – U+ffff 16 бит Unicode символ

string System.String Строка из символов

Unicode

Объектный тип

Имя типа Системный тип Примечание

оbject System.Object Базовый тип всех встроенных и

пользовательских типов

void Отсутствие какого-либо значения

В языке C# все типы – встроенные и пользовательские, связаны

отношением наследования (рис. 3.3.).

Подробно отношение наследования описано в разделе 5.2. Роди-

тельским (базовым) классом всех типов является класс Object (тип

object). Все остальные типы являются его потомками, наследуя ме-

тоды этого класса. У класса Object есть четыре наследуемых метода:

1. bool Equals (object obj) – проверяет эквивалентность теку-

щего объекта и объекта, переданного в качестве параметра;

2. System.Type GetType() – возвращает системный тип текущего

объекта;

3. string ToString() – возвращает строку, связанную с объектом;

для арифметических типов возвращается значение, преобразован-

ное в строку;

Page 40: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

40

4. int GetHashCode()– служит как хэш-функция в соответствую-

щих алгоритмах поиска по ключу при хранении данных в хэш-

таблицах.

Рис. 3.3. Наследование типов в среде CLR.

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

родительского класса Object. Естественно, что все встроенные типы

нужным образом переопределяют методы родителя и добавляют собст-

венные поля, свойства и методы. Учитывая, что и типы, создаваемые

пользователем, также являются потомками класса Object, то для них

необходимо переопределить методы родителя, если предполагается ис-

пользование этих методов; реализация родителя, предоставляемая по

умолчанию, не обеспечивает нужного эффекта.

3.2. Переменные и константы

3.2.1. Переменные методов

Любая программа использует данные в виде переменных и кон-

стант. Переменные – это именованные участки памяти, которые могут

хранить либо значения некоторого типа (для значащих типов, в стеке),

либо ссылки на экземпляры некоторых классов (для ссылочных типов,

ссылки на объекты, расположенные в "куче"). В C# также разделяют пе-

ременные на поля классов (объявляются в описаниях классов и создают-

ся для каждого объекта) и локальные переменные методов (создаются

при каждом вызове метода класса). В данном разделе рассматривается

объявление и использование локальных переменных. Создание и ис-

пользование полей классов рассматривается в разделе 4.1.

Для создания и использования в программе переменных, их нуж-

но объявить и инициализировать в любом месте метода, но до того, как

Page 41: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

41

они будет использоваться. Объявления переменных в C# имеют сле-

дующий общий вид:

[модификаторы] <тип> <имя_переменной>;

При объявлении переменных указывается их тип и имя, где

имя_переменной – это просто название переменной или название пере-

менной с инициализацией (начальным значением). После типа можно

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

осуществлять либо с помощью присваивания значений или с использо-

ванием конструкции new и вызовом конструктора по умолчанию. В

примере, приведенном ниже, показаны разные способы объявления пе-

ременных и простое их использование: int x, s; //без инициализации

int y =0, u = 77; //обычный способ инициализации

//допустимая инициализация

float w1 = 0f, w2 = 5.5f, w3 = w1 + w2 + 125.25f;

//допустимая инициализация в объектном стиле

int z= new int();

x = u + y; //теперь x инициализирована

В первой строке объявляются переменные x и s с отложенной

инициализацией. Отметим, что всякая попытка использовать еще не

инициализированную переменную в вычислениях приводит к ошибке

уже на этапе компиляции.

Вывод типа переменной

Для переменной можно задать неопределенный тип (var) и при-

своит некоторое значение. В этом случае компилятор автоматически

определит тип присваиваемого значения и назначит его переменной.

Например, объявление переменной: var name = "Петров А.В.";

аналогично следующему объявлению: string name = "Петров А.В.";

В этом случае обязательно нужно инициализировать переменную при ее

объявлении.

3.2.2 Области видимости переменных

Область видимости переменной (variable scope) это участок про-

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

областью видимости локальной переменной является участок програм-

мы, от строки, в которой она объявляется, до первой фигурной скобки,

завершающей блок или метод, в котором переменная объявлена. Обла-

стью видимости локальных переменных, которые объявляются в опера-

Page 42: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

42

торах цикла (например, for или while) (см. раздел 3.4.4), является со-

держание (тело) данного цикла. Например:

public void ScopeTest() {

int n = 0;

for (int i = 0; i < 10; i++) {

Console.WriteLine(i);

} // i выходит из области видимости и удаляется

// можно объявить другу переменную с именем i

{

var i = ”другой цикл”; // строка

Console.WriteLine(i);

} // i опять выходит из области видимости

for (int i = 9; i > = 0; i--) {

Console.WriteLine(i);

} // i еще раз выходит из области видимости

return;

} // переменная n тоже выходит из области видимости

В данном примере переменная i объявляется три раза в разных блоках,

а переменная n выходит из области видимости при завершении работы

метода.

3.2.3 Создание значений ссылочных переменных

При объявлении ссылочной переменной выделяется память для

хранения ссылки, но не выделяется память для хранения самого объекта

(реальных данных). Для выделения памяти для самого объекта нужно

использовать операцию new:

операция new выделяет и инициализирует память для экземпляра

любого заданного типа (для ссылочных переменных – в куче);

после оператора new задается имя типа, экземпляр которого соз-

дается, пара круглых скобок, в которой могут быть заданы пара-

метры (вызов конструктора класса). new TypeName (. . .)

если выделение памяти выполняется успешно, то оператор new

возвращает ссылку на выделенный и инициализированный объект

в куче.

Например: Box b1; // объявление ссылочной переменной b1 = new Box (); // выделение памяти для объекта

Два этих оператора могут быть объединены в один: Box b1 = new Box(); // объявление и инициализация

Page 43: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

43

Операция new имеет высший уровень приоритета и используется для

создания нового объекта заданного типа и вызова соответствующего

конструктора. Результатом выполнения данной операции является

ссылка на созданный объект.

3.2.4. Константы

В C# константы могут задаваться в виде литералов (набора сим-

волов) или именованных констант. Например: y = 7.7;

Значение константы "7.7" является одновременно ее именем, а

также она имеет и тип. По умолчанию константы с дробной частью

имеют тип double. Точный тип константы можно задать с помощью

символа, стоящего после литерала (в верхнем или нижнем регистре).

Такими символами могут быть: f – тип float; d – тип double; m – тип

decimal.

Также можно объявить именованную константу. Для этого в

объявление переменной добавляется модификатор const. Именованные

константы обязательно должны быть инициализированы и инициализа-

ция не может быть отложенной. Например: const float с = 0.1f;

Строковые константы

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

символов заключенная в двойные кавычки ("). В C# существуют два ви-

да строковых констант:

обычные константы, которые представляют строку символов, за-

ключенную в двойные кавычки – "ssss";

@-константы, заданные обычной константой c предшествующим

знаком @.

В обычных константах некоторые символы интерпретируются

особым образом. Это требуется, для задания в строке специальных

управляющих символов, в виде escape-последовательностей. Для этого

используется комбинация символов, начинающаяся символом "\" – об-

ратная косая черта. Так, пары символов: "\n", "\t", "\\", "\"" задают

соответственно символ перехода на новую строку, символ табуляции,

сам символ обратной косой черты, символ кавычки, вставляемый в

строку, но не сигнализирующий о ее окончании. Однако часто при за-

дании констант, определяющих путь к файлу, приходится каждый раз

удваивать символ обратной косой черты: “C:\\test.txt”, что не со-

всем удобно. В этом случае и используются @-константы, в которых

все символы понимаются в полном соответствии с их изображением.

Например, две следующие строки будут аналогичными:

Page 44: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

44

s1 = "c:\\c#book\\ch5\\chapter5.doc";

s2 = @"c:\c#book\ch5\chapter5.doc";

После рассмотрения объявления переменных и констант следует

рассмотреть имеющиеся в языке C# операции над данными.

3.3. Операции Переменные и константы могут объединяться с помощью опера-

ций. В языке C# операция – это термин или символ, получающий на

вход одно или несколько операндов (переменных или констант) или вы-

ражений (переменных или констант, связанных между собой знаками

операций), и возвращающий значение некоторого типа (табл. 3.2).

Операции, получающие на вход один операнд, например операция

приращения (++) или new, называются унарными операциями. Опера-

ции, получающие на вход два операнда, например, арифметические

операции (+, –, *, /) называются бинарными операциями. И только одна

операция языка C# получает на вход три операнда и называется тер-

нарной операцией, это условная операция (?:).

Таблица 3.2.

Описание операций языка C#

Основные операции Выражение Описание

x.y доступ к элементам типа; f(x) вызов метода и делегата; a[x] доступ к массиву и индексатору; x++ постфиксное приращение; x-- постфиксное уменьшение;

new T(...) создание объекта класса или делегата; new T(...)

{...}

создание объекта с инициализацией;

New T[...] создание массива (см. раздел 3.5); typeof(T) получение объекта System.Type для T; delegate {} анонимная функция (анонимный метод);

Унарные операции

Выражение Описание –x отрицательное значение; !x логическое отрицание; ~x поразрядное отрицание; ++x префиксное приращение; --x префиксное уменьшение; (T)x явное преобразование x в тип T (кастинг);

Мультипликативные

операции

Аддитивные операции

Page 45: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

45

Выражение Описание Выражение Описание * умножение; x + y сложение, объединение строк / деление; x – y вычитание % остаток;

Операции сдвига Операции равенства

Выражение Описание Выражение Описание x << y сдвиг влево; x == y равно; x >> y сдвиг вправо; x != y не равно;

Операции отношения и типа

Выражение Описание x < y меньше; x > y больше; x <= y меньше или равно;

x >= y больше или равно; x is T

возвращает значение true, если x относится к типу T, в

противном случае возвращает значение false; x as T

возвращает x типа T или нулевое значение, если x не

относится к типу T;

Операции назначения и анонимные операции

Выражение Описание = присваивание;

x op= y составные операции присвоения:

+=, -=, *=, /=, %=, &=, |=, !=, <<=, >>= ;

Логические, условные операции и Null-операции

Категория Выраже

ние

Описание

Логическое

AND

x & y

целочисленное поразрядное AND, логическое

AND

Логическое

исключающее

XOR

x ^ y

целочисленное поразрядное исключающее

XOR, логическое исключающее XOR

Логическое OR x | y целочисленное поразрядное OR, логическое OR

Условное AND x && y вычисляет y только если x имеет значение true

Условное OR x || y вычисляет y только если x имеет значение false

Объединение

нулей

x ?? y

равно y, если x = null, в противном случае

равно x

Условное

x ? y:z

равно y, если x имеет значение true, z если x

имеет значение false

Page 46: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

46

Когда выражение содержит несколько разных операций, то поря-

док выполнения определяется на основе приоритетов операций, а если

операции одинаковые, то порядок их выполнения задается их ассоциа-

тивностью (табл. 3.3).

Таблица 3.3.

Приоритеты и ассоциативность операций языка C# При-

ори-

тет Категория Операции

Ассоциа-

тивность

0 Первичные (expr) x.y f(x) a[x] x++

x-- new sizeof(t)

typeof(t) checked(expr)

unchecked(expr)

Слева направо

1 Унарные + - ! ~ ++x --x (T)x Слева направо

2 Мультипликативные

(Умножение)

* / % Слева направо

3 Аддитивные (Сложение) + - Слева направо

4 Сдвиг << >> Слева направо

5 Отношения, проверка ти-

пов

< > <= >= is as Слева направо

6 Эквивалентность == != Слева направо

7 Логическое & Слева направо

8 Логическое исключаю-

щее ИЛИ (XOR)

^ Слева направо

9 Логическое ИЛИ (OR) | Слева направо

10 Условное И && Слева направо

11 Условное ИЛИ || Слева направо

12 Условное выражение ? : Справа налево

13 Присваивание = *= /= %= += -= <<= >>=

&= ^= |=

Справа налево

Вычисление выражений начинается с выполнения операций выс-

шего приоритета. Первым делом вычисляются выражения в круглых

скобках – (expr), определяются значения полей объекта – x.y, вычис-

ляются функции – f(x), переменные с индексами – a[i].

Если есть несколько операций с одинаковым приоритетом, то они

вычисляются в соответствии с их ассоциативностью. Операции с левой

ассоциативностью вычисляются слева направо. Например, x * y / z

вычисляется как (x*y)/z. Операции с правой ассоциативностью вы-

числяются справа налево. Операции присваивания и тернарная опера-

Page 47: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

47

ция (?:) имеют правую ассоциативность. Все другие двоичные опера-

ции имеют левую ассоциативность.

Можно заключать выражения в скобки для принудительного вы-

числения некоторых частей выражения раньше других. Например, вы-

ражение 2 + 3 * 2 в обычном случае будет иметь значение 8, по-

скольку операции умножения выполняются раньше операций сложения.

А результатом вычисления выражения (2 + 3) * 2 будет число 10,

поскольку компилятор C# получит данные о том, что операцию сложе-

ния (+) нужно вычислить до выполнения операции умножения (*).

Порядок выполнения операций с объектами пользовательских

классов и структур можно изменить. Такой процесс называется пере-

грузкой операций (рассмотрено в разделе 4.8).

Следует отметить, что в языке C# нет операции возведения в сте-

пень. Для возведения в степень используется статический метод Pow()

класса System.Math.

3.3.1. Перегрузка операций

Под перегрузкой операции понимается существование несколь-

ких реализаций одной и той же операции. Большинство операций языка

C# являются перегруженными, т.е. одна и та же операция может приме-

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

рации компилятор ведет поиск описания операции, подходящего для

используемых типов операндов. Следует отметить, что операции, как

правило, выполняются над операндами одного типа. Если же операнды

разных типов, то предварительно происходит неявное преобразование

типа операнда.

Оба операнда могут быть одного типа, но преобразование типов

может все равно происходить – по той причине, что для заданных типов

нет соответствующей перегруженной операции. Такая ситуация доста-

точно часто возникает на практике, поскольку, например, операция

сложения не определена для младших подтипов арифметического типа.

Например, если объявлены переменные byte b1 =1, b2 =2, b3;

short sh1;

int in1;

То при записи простого оператора будет выдаваться ошибка компиля-

ции: b3 = b1 + b2; //ошибка: результат типа int

Оба операнда имеют тип byte, но для данного типа нет перегруженной

реализации сложения. Ближайшей операцией является сложение целых

типа int. Поэтому оба операнда преобразуются к типу int, выполняет-

Page 48: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

48

ся операция сложения, результат имеет тип int и не может быть неявно

преобразован в тип byte, – возникает ошибка еще на этапе компиляции.

Правильной будет следующая запись оператора: b3 = (byte)(b1+b2);

3.3.2. Операция присваивания

В C# присваивание является операцией, которая может использо-

ваться в выражениях. В выражении, называемом множественным при-

сваиванием, списку переменных присваивается одно и то же значение.

Например: x = y = z = w =(u+v+w)/(u-v-w);

При присвоении переменных разного типа выполняется преобра-

зование типов. Компилятор пытается выполнить преобразование типа

переменной стоящей справа в тип переменной, стоящей слева. Присваи-

вание переменной стоящей слева (тип T) значения переменной или ре-

зультата вычисления выражения (типа T1) возможно только в следую-

щих случаях:

типы T и T1 совпадают;

тип T является базовым (родительским) типом для типа T1 (в со-

ответствии с наследованием типов);

в определении типа T1 описано явное или неявное преобразова-

ние в тип T (см. раздел 4.9).

Так как все классы в языке C# – встроенные и определенные поль-

зователем – по определению являются потомками класса Object, то от-

сюда и следует, что переменным класса Object можно присваивать вы-

ражения любого типа.

Для типов может быть задано неявное (implicit) или явно (explicit)

преобразования. Например, для встроенных арифметических типов су-

ществует схема неявных преобразований, показанная на рис. 3.4.

Если на этой диаграмме есть путь (стрелками) от типа А к типу В,

то это означает, что имеется неявное преобразования из типа А в тип В.

Например, из short в float. Все остальные преобразования между

подтипами арифметического типа существуют, но являются явными.

Например, из float в int.

Для указания явного преобразования типов используется опера-

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

следующий вид: (type) <выражение>

Она задает явное преобразование типа, определенного выражени-

ем, к типу, указанному в скобках. Например:

Page 49: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

49

int i = (int) 2.99; // i = 2;

В данном случае тип константы 2.99 (по умолчанию тип double) пре-

образуется в тип int с обрезанием дробной части. Или, например, если

описаны пользовательские типы T и P, и для типа T описано явное пре-

образование в тип P, то возможна следующая запись: T y;

P x = new P();

y = (T) x;

Рис. 3.4. Неявное преобразование встроенных типов.

Следует отметить, что существуют явные преобразования внутри

арифметического типа, но не существует, например, явного преобразо-

вания арифметического типа в тип bool. Например: double a = 5.0;

int p = (int)a;

//bool b = (bool)a;//ошибка,такого преобразования нет!

В ряде случаев можно задать явным образом требуемое преобра-

зование, используя специальные методы преобразования, определенные

в классе System.Convert, которые обеспечивают преобразование зна-

чения одного типа к значению другого типа (в том числе значения стро-

кового типа к значениям встроенных типов).

3.3.3. Специальные варианты присваивания

В языке C# для двух частных случаев присваивания предложен

специальный синтаксис. Для присваиваний вида "x=x+1", в которых пе-

ременная увеличивается или уменьшается на единицу, используются

специальные операции "++" (операция инкрементации) и "--" (опера-

ция декрементации).

Другой важный частный случай – это краткая запись для присваи-

вания вида:

Page 50: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

50

X = X <operator> (expression); // например: x = x * 2;

Для таких присваиваний используется краткая форма записи:

X <operator>= expression; // например: x *= 2;

В качестве операции разрешается использовать арифметические и

логические (или побитовые) операции языка C#. Например:

x += u + v; y /= (u-v);

Операции инкрементации (++) и декрементации (--) могут быть

префиксными (стоять перед переменной) и постфиксными (стоять после

переменной). К высшему приоритету относятся постфиксные операции

x++ и x--. Префиксные операции имеют на единицу меньший приори-

тет. Результатом выполнения, как префиксных, так и постфиксных

операций, является увеличение (++) или уменьшение (--) значения пе-

ременной на единицу.

Для префиксных (++x, --x) операций результатом их выполнения

является измененное значение x, постфиксные операции возвращают в

качестве результата значение x до изменения. Например: int n1, n2, n = 5;

n1 = n++; // n1 = 5; n = 6;

n2 = ++n; // n2 = 7; n = 7;

3.3.4. Арифметические операции

В языке C# имеются обычные для всех языков арифметические

операции – "+, –, *, /, %". Все они перегружены. Операции "+" и "–" мо-

гут быть унарными и бинарными. Операция деления "/" над целыми

типами осуществляет деление нацело, для типов с плавающей и фикси-

рованной точкой – обычное деление.

Операция "%" определена над всеми арифметическими типами и

возвращает остаток от деления нацело.

Тип результата зависит от типов операндов. Рассмотрим пример

вычислений с различными арифметическими типами:

int n = 5, m =3, k;

float p; //

k = n/m; // k = 1

p = n/m; // p = 1.0

p = (n*1.0F)/m; // p = 1.66666663

k = n%m; // k = 2

double x=7, y =3, u,v,w;

u = x/y; // u = 2.3333333333333335

v= u*y; // v = 7.0

w= x%y; // w = 1.0

Page 51: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

51

3.3.5. Вычисление выражений

В языках программирования под выражением понимается комби-

нация констант, переменных, вызовов методов (возвращающих значе-

ния), объединенных знаками операций и скобками, которая в результате

вычисления, возвращает некоторый результат. При вычислении выра-

жения определяется его значение и тип. Тип результата выражения за-

висит от типов переменных и констант участвующих в выражении. Ти-

пом результата выражения является наиболее сложный тип переменных

и констант в соответствии с рис. 3.4. Наиболее простым типом является

byte, а наиболее сложным – decimal. Например: int a = 5; float f;

f = a/4; // значение f = 1.0, так как результат а/4 int

f = a/4f;// значение а = 1.25, так как результат float

3.3.6. Операции отношения

Операции отношения используются для сравнения значений пе-

ременных и констант. Всего имеется 6 операций отношения:

==, !=, <, >, <=, >= (равно, не равно, меньше, больше, меньше или

равно и больше или равно, соответственно).

Следует обратить внимание на запись операции "равно" – "=="

(два знака присвоить =) и "не равно" – "!=".

Следует отметить, что при сравнении ссылочных переменных

сравниваются не сами объекты, а ссылки на объекты (если операция

сравнения не переопределена для данного типа).

3.3.7. Логические операции

В языке C# логические операции делятся на две категории: одни

выполняются над логическими значениями операндов, другие осущест-

вляют выполнение логической операции над битами операндов. По этой

причине в C# существуют две унарные операции отрицания – логиче-

ское отрицание, заданное операцией "!", и побитовое отрицание, задан-

ное операцией "~". Первая из них определена над операндом типа bool,

вторая – над операндом целочисленного типа, начиная с типа int и

выше (int, uint, long, ulong). Результатом операции, во втором слу-

чае, является операнд, в котором каждый бит заменен его дополнением

(0 на 1 и 1 на 0). Рассмотрим пример: //операции отрицания ~,!

bool b1,b2;

b1 = 2*2==4; // b1 = true;

b2 =!b1; // b2 = false;

//b2= ~b1; // ошибка !

uint j1 =7, j2;

j2= ~j1; // j2 = 4294967288

Page 52: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

52

//j2 = !j1; // ошибка !

int j4 = 7, j5;

j5 = ~j4; // j5 = -8

В этом фрагменте закомментированы операторы, приводящие к

ошибкам. В первом случае была сделана попытка применения операции

побитового отрицания к выражению типа bool, во втором – логическое

отрицание применялось к целочисленным данным. И то, и другое в C#

незаконно. Обратите внимание на разную интерпретацию побитового

отрицания для беззнаковых и знаковых целочисленных типов. Для пе-

ременных j5 и j2 строка битов, задающая значение – одна и та же, но

интерпретируется по-разному.

Бинарные логические операции: && – условное И и || – условное

ИЛИ, определены только над данными типа bool. Операции называют-

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

операнд, зависит от уже вычисленного значения первого операнда. В

операции &&, если первый операнд равен значению false, то второй

операнд не вычисляется и результат операции равен false. Аналогич-

но, в операции ||, если первый операнд равен значению true, то вто-

рой операнд не вычисляется и результат операции равен true. Цен-

ность условных логических операций заключается не в их эффективности

по времени выполнения. Часто они позволяют вычислить логическое

выражение, имеющее смысл, но в котором второй операнд не опреде-

лен. Например, рассмотрим задачу поиска элемента массива. Заданный

элемент в массиве может быть, а может и не быть. Вот типичное реше-

ние этой задачи: //Условное And – &&

int[] ar= {1,2,3};

int search = 7, i=0;

// search – заданное значение

while ((i < ar.Length) && (ar[i]!= search)) i++;

if(i < ar.Length)

Console.WriteLine("Значение найдено");

else

Console.WriteLine("Значение не найдено");

Если значение переменной search не совпадает ни с одним из

значений элементов массива ar, то последняя проверка условия цикла

while будет выполняться при значении i, равном ar.Length. В этом

случае первый операнд получит значение false, и, хотя второй опе-

ранд при этом не определен, цикл нормально завершит свою работу.

Второй операнд не определен в последней проверке, поскольку индекс

элемента массива выходит за допустимые пределы (в C# индексация

Page 53: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

53

элементов начинается с нуля). Отметим, что "нормальная" конъюнкция

требует вычисления обеих операндов, поэтому ее применение в данной

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

образца нет в массиве.

Три бинарные побитовые операции: & – AND, | – OR , ^ – XOR

могут использоваться как с целыми типами выше int, так и с булевыми

типами. В первом случае они используются как побитовые операции, во

втором – как обычные логические операции. Иногда необходимо, чтобы

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

обойтись. Вот пример первого их использования: //Логические побитовые операции And, Or, XOR (&,|,^)

int k2 = 7, k3 = 5, k4, k5, k6;

k4 = k2 & k3; k5 = k2 | k3; k6 = k2^k3;

3.3.8. Условная операция

В C# имеется условная операция, начинающаяся с условия (вы-

ражение типа bool), заключенного в круглые скобки, после которого

следует знак вопроса и пара выражений, разделенных двоеточием „:‟.

Если условие истинно, то из пары выражений выполняется первое, в

противном случае результатом является значение второго выражения.

Например: int a = 7, b = 9, max;

max = (a>b) ? a:b; // max получит значение 9.

3.4. Операторы Операторы являются инструкциями языка программирования, ко-

торые представляет собой законченные фразы и определяют некоторые

вполне законченные этапы обработки данных. В состав операторов вхо-

дят ключевые слова, переменные, константы, операции и выражения.

Каждый оператор заканчивается символом “;”. В одно строке програм-

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

быть записан в нескольких строках.

3.4.1. Оператор присваивания

В языке C# присваивание считается операцией. Вместе с тем за-

пись вида: x = expr; можно считать настоящим оператором при-

сваивания, так же, как и одновременное присваивание со списком пере-

менных в левой части: x1 = x2 = ... = xk = expr;

В качестве выражения expr может выступать просто переменная или

константа.

Page 54: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

54

3.4.2. Операторы выбора

В языке C# для выбора одной из нескольких возможностей ис-

пользуются две конструкции – if и switch. Первую из них обычно назы-

вают альтернативным выбором, вторую – разбором случаев.

Оператор if

Синтаксис оператора if:

if(выражение_1) оператор_1

else if(выражение_2) оператор_2

...

else if(выражение_K) оператор_K

else оператор_N

Выражения (условия), стоящие после ключевого слова if долж-

ны заключаться в круглые скобки и быть булевого типа (т.е., эти выра-

жения должны возвращать значения true или false). Следует отме-

тить, что арифметический тип не имеет явных или неявных преобразо-

ваний к булевому типу (как это делается в языке С++).

Ветви else if, позволяющие организовать выбор из многих воз-

можностей, могут отсутствовать. Может быть опущена и заключитель-

ная else-ветвь. В этом случае краткая форма оператора if задает аль-

тернативный выбор – выполнять или не выполнять оператор, который

стоит после условия.

Смысл оператора if является простым и понятным. Выражения в

if проверяются в порядке их написания. Как только получено значение

true, проверка прекращается и выполняется оператор (это может быть

блок), который следует за выражением, получившим значение true. С

завершением этого оператора завершается и оператор if.

Оператор switch

Частным, но важным случаем выбора из нескольких вариантов

является ситуация, при которой выбор варианта определяется значе-

ниями некоторого выражения. В этом случае используется оператор

switch:

switch(выражение)

{

case константное_выражение_1:

[операторы_1 оператор_перехода_1]

...

case константное_выражение_K:

[операторы_K оператор_перехода_K]

[default: операторы_N оператор_перехода_N]

}

Page 55: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

55

Ветвь default может отсутствовать. Синтаксически допустимо,

чтобы после двоеточия следовала пустая последовательность операто-

ров, а не последовательность, заканчивающаяся оператором перехода.

Константные выражения в case должны иметь тот же тип, что и

switch-выражение.

Оператор switch работает следующим образом. Вначале вычис-

ляется значение switch-выражения. Затем оно поочередно в порядке

следования case сравнивается на совпадение с константными выраже-

ниями. Как только достигнуто совпадение, выполняется соответствую-

щая последовательность операторов case-ветви. Поскольку последний

оператор этой последовательности является оператором перехода (ча-

ще всего это оператор break), то обычно он завершает выполнение

оператора switch. Если значение switch-выражения не совпадает ни

с одним константным выражением, то выполняется последовательность

операторов ветви default, если же таковой ветви нет, то оператор

switch эквивалентен пустому оператору.

Отметим, что case-выражения могут быть только константным

выражениями. public void Starosta(string group){

string stud;

switch (group)

{

case "8551":

stud = "Иванов С.П.";

break;

case "8552":

stud = "Сидоров А.И.";

break;

case "8553":

stud = "Петров В.Т.";

break;

default :

stud = "не определен";

break;

}

Console.WriteLine("Староста группы {0} – {1}",

group, stud);

}

Когда требуется проверить попадание в некоторый диапазон зна-

чений, приходится прибегать к оператору if для формирования специ-

альной переменной. Например:

Page 56: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

56

int period =0;

if ((age > 0)&& (age <7))period=1;

else if ((age >= 7)&& (age <17))period=2;

else if ((age >= 17)&& (age <22))period=3;

else period =4;

3.4.3. Операторы перехода

В языке C# есть несколько операторов перехода, которые позво-

ляют прервать естественный порядок выполнения операторов блока.

Это такие операторы, как goto, break и continue.

Оператор goto

Оператор goto имеет следующий формат: goto [метка|case константное_выражение|default];

Любой оператор языка C# может иметь метку – уникальный иден-

тификатор, предшествующий оператору и отделенный от него симво-

лом двоеточия. Передача управления помеченному оператору – это

классическое использование оператора goto. Два других способа ис-

пользования goto (передача управления в case или default-ветвь) ис-

пользуются в операторе switch, о чем шла речь выше.

Операторы break и continue

В структурном программировании признаются полезными "пере-

ходы вперед" (но не назад), позволяющие при выполнении некоторого

условия выйти из цикла, из оператора выбора, из блока. Для этой цели

можно использовать оператор goto, но лучше применять специально

предназначенные для этих целей операторы break и continue.

Оператор break может стоять в теле цикла или завершать case-

ветвь в операторе switch. Пример его использования в операторе switch

уже демонстрировался. При выполнении оператора break в теле цикла

завершается выполнение самого внутреннего цикла. В теле цикла, чаще

всего, оператор break помещается в одну из ветвей оператора if, про-

веряющего условие преждевременного завершения цикла: int i = 1, j=1;

for(i =1; i<100; i++){

for(j = 1; j<10; j++) {if (j>=3) break;}

Console.WriteLine(

"Выход из цикла j при j = {0}", j);

if (i>=3) break;

}

Console.WriteLine("Выход из цикла i при i= {0}", i);

Page 57: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

57

Оператор continue используется только в теле цикла. В отличие

от оператора break, завершающего внутренний цикл, continue осу-

ществляет переход к следующей итерации этого цикла.

3.4.4. Операторы цикла

Оператор цикла for

Оператор цикла for имеет следующий формат:

for(инициализация; условие; изменение) оператор;

Оператор, стоящий после закрывающей скобки, задает тело цикла. В

большинстве случаев телом цикла является блок. Сколько раз будет вы-

полняться тело цикла, зависит от трех управляющих элементов, задан-

ных в скобках. Инициализация задает начальное значение одной или

нескольких переменных, часто называемых переменными цикла. В

большинстве случаев цикл for имеет одну переменную. Условие задает

условие окончания цикла, соответствующее выражение при вычислении

должно получать значение true или false. Изменение описывает, как

меняется переменная цикла в каждой итерации выполнения. Если усло-

вие цикла истинно, то выполняется тело цикла, затем изменяются зна-

чения переменной цикла и снова проверяется условие. Как только усло-

вие становится ложным, цикл завершает свою работу. Например, для

вычисления суммы значений целых чисел от 1 до 10 можно использо-

вать следующий цикл: int s=0;

for (int i = 1; i <= 10; i++) s += i;

Переменная цикла часто объявляется непосредственно в инициа-

лизации цикла и соответственно являются локальной в цикле перемен-

ной, так что после завершения цикла она перестанет существовать. В

тех случаях, когда предусматривается возможность преждевременного

завершения цикла с помощью одного из операторов перехода, пере-

менные цикла объявляются до цикла, что позволяет анализировать их

значения при выходе из цикла.

Оператор цикла while

Оператор цикла while(условие) является универсальным видом

цикла, включаемым во все языки программирования. Тело цикла вы-

полняется до тех пор, пока остается истинным условие оператора

while. В языке C# у этого вида цикла есть два варианта – с проверкой

условия в начале и в конце цикла. Первый вариант имеет следующий

вид:

while(логическое выражение) оператор;

Page 58: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

58

Этот оператор сначала проверяет условие, а затем выполняет тело

цикла. Если в результате проверки условие не выполнится, то тело мо-

жет быть ни разу не выполнено. В нормальной ситуации каждое выпол-

нение тела цикла – это очередной шаг к завершению цикла.

Вариант цикла do-while, проверяет условие завершения в конце

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

мере, один раз. Такой цикл записывается следующим образом: do

оператор

while(выражение);

В приведенном ниже примере показан типичный способ органи-

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

с пользователем многократно решается некоторая задача. На каждом

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

(например, возведение числа в заданную степень) и просматривает по-

лученные данные. Он может сам решить, продолжать выполнять вычис-

ления или нет, но, по крайней мере, один раз данная задача будет ре-

шена. string answer;

double x, y;

do {

Console.Write("Введите значение:");

x = (Convert.ToDouble(Console.ReadLine()));

Console.Write

("Возвести в:\n1. 2 степень\n2. 3 степень\n");

int i = Convert.ToInt32(Console.ReadLine());

y = x;

switch (i) {

case 1: y = Math.Pow(x, 2.0); break;

case 2: y = Math.Pow(x, 3.0); break;

}

Console.WriteLine("Результат: {0}", y);

Console.WriteLine("Продолжить? (да/нет)");

answer = Console.ReadLine();

}

while (answer == "да");

На рис. 3.5 показан результат работы программы.

Оператор цикла foreach

Новым видом цикла является цикл foreach, удобный при работе с

массивами и другими коллекциями данных. Он имеет следующий вид: foreach(тип идентификатор in контейнер) оператор;

Page 59: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

59

Тело цикла выполняется для каждого элемента коллекции. Тип

идентификатора должен быть согласован с типом элементов, хранящих-

ся в коллекции данных. Цикл заканчивается, когда полностью перебра-

ны все элементы коллекции. Следует отметить, что в данном цикле

идентификатору нельзя присваивать новые значения.

Рис. 3.5. Результат работы программы с циклом.

В приведенном ниже примере показана работа с одномерным мас-

сивом. Массив создается с использованием циклов типа for, а при нахо-

ждении суммы его элементов, минимального и максимального значения

используется цикл foreach: int [] arr = new int[10];

Random rnd = new Random();

for (int i =0; i<10; i++) arr[i]= rnd.Next(100);

long sum =0;

foreach(int item in arr) {

sum +=item;

// недопустимо для идентификатора foreach

// item = 0;

}

Console.WriteLine("sum = {0}", sum);

3.4.5. Операторы обработки исключений

При работе методов возможно возникновение непредвиденных,

особых ситуаций (исключений), которые могут привести к аварийному

завершению программы. Например, деление на ноль, недостаток опера-

тивной памяти, отсутствие требуемых ресурсов (файлов, баз данных).

Если в некотором методе предполагается возможность появления таких

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

зуются try-блоки, перед которыми стоит ключевое слово try. Вслед за

этим блоком следуют один или несколько блоков, обрабатывающих ис-

ключения, – catch-блоков. Каждый catch-блок имеет формальный па-

раметр класса Exception (из библиотеки FCL) или одного из его по-

томков. Если в try-блоке возникает исключение типа T, то будет выби-

раться один из блоков catch. Первый по порядку catch-блок, тип

Page 60: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

60

формального аргумента которого согласован с типом T - совпадает с

ним или является его потомком - захватывает исключение и начинает

выполняться.

Исключения могут генерироваться средой CLR, платформой .NET

Framework или внешними библиотеками, либо кодом приложения. Ис-

ключения создаются при помощи ключевого слова throw. Во многих

случаях исключение может инициироваться не методом, вызванным не-

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

последовательности вызовов. Когда это происходит, среда CLR выпол-

няет анализ последовательных вызовов методов в поиске метода с бло-

ком catch для определенного типа исключения. При обнаружении пер-

вого такого блока catch этот блок выполняется. Если среда CLR не на-

ходит соответствующего блока catch где-либо в стеке вызовов, она за-

вершает процесс и отображает пользователю сообщение.

В приведенном ниже примере метод тестирует деление на ноль и

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

ний эта программа завершила бы работу с ошибкой “DivideByZeroEx-

ception was unhandled” (не обработано исключение "деление на

ноль"). class ExceptionTest {

static double SafeDivision(double x, double y) {

if (y == 0)

throw new System.DivideByZeroException();

return x / y;

}

static void Main(){

double a = 98, b = 0;

double result = 0;

try {

result = SafeDivision(a, b);

Console.WriteLine("{0} divided by {1} = {2}",

a, b, result);

}

catch (DivideByZeroException e) {

Console.WriteLine("Attempted divide by zero.");

}

}}

3.5. Массивы Массивом называют упорядоченную совокупность элементов од-

ного типа. Каждый элемент массива имеет индексы, определяющие по-

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

Page 61: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

61

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

N. Индексы задаются целочисленным типом.

Массивы относятся к ссылочным типам, а следовательно, память

им отводится в "куче".

В языке C# имеются одномерные массивы и многомерные масси-

вы. Кроме них, в языке C# также имеется новый тип массивов – ступен-

чатый.

3.5.1. Одномерные массивы

Объявление одномерного массива выглядит следующим образом: <тип>[] <объявление>;

Запись T[] следует понимать как класс одномерный массив с

элементами типа T. Границы изменения индексов при объявлении мас-

сива не задаются, они устанавливаются при создании объектов – экзем-

пляров, каждый из которых является одномерным массивом со своим

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

Как и в случае объявления простых переменных, в каждом объяв-

лении массива задается имя или имя с инициализацией. В первом слу-

чае речь идет об отложенной инициализации. Нужно понимать, что при

объявлении с отложенной инициализацией сам массив не формируется,

а создается только ссылка на массив, имеющая неопределенное значе-

ние null. Поэтому пока массив не будет реально создан и его элементы

инициализированы, использовать его в вычислениях нельзя. Вот пример

объявления трех массивов с отложенной инициализацией: int[] a, b, c;

Чаще всего при объявлении массива используется имя с инициа-

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

два варианта инициализации.

В первом случае инициализация является явной и задается кон-

стантным массивом. При этом элементы константного массива заклю-

чаются в фигурные скобки. Например: double[] x= {5.5, 6.6, 7.7};

Во втором случае создание и инициализация массива выполняется

в объектном стиле с вызовом конструктора массива. При этом количе-

ство элементов указывается в квадратных скобках. Например: int[] d = new int[5];

Элементы массива, если они не заданы при инициализации, либо

вычисляются, либо вводятся пользователем. Рассмотрим пример работы

с массивами: //объявляются одномерный массив a

int[] a = new int[5] {1,2,3,4,5};

Page 62: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

62

//объявление массива с явной инициализацией

int[] x = {5,5,6,6,7,7};

//объявление массивов с отложенной инициализацией

int[] u,v;

u = new int[3];

for(int i=0; i<3; i++) u[i] = i+1;

v = new int[4];

v=u; //допустимое присваивание

Отметим следующие особенности данного примера:

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

Вначале объявляется одномерный массив a, создаваемый с помо-

щью конструктора. Значения элементов этого массива имеют тип

int. То, что заданная размерность массива соответствует количе-

ству инициализирующих элементов, определяется программи-

стом, а не является требованием языка. Если размерность массива

будет больше, чем число элементов инициализации, то оставщие-

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

Массив x объявлен с явной инициализацией. Число и значения

его элементов определяется константным массивом.

Массивы u и v объявлены с отложенной инициализацией. В по-

следующих операторах массив u инициализируется в объектном

стиле – его элементы получают значения в цикле.

Оператор присваивания v = u является правильным ссылочным

присваиванием: хотя u и v имеют разное число элементов, но они

являются объектами одного класса. Теперь обе ссылки u и v бу-

дут указывать на один и тот же массив, так что изменение элемен-

та одного массива немедленно отразится на другом массиве. На

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

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

мусора.

3.5.2. Многомерные массивы

Одномерные массивы позволяют задавать такие математические

структуры, как векторы, но кроме них также используются многомер-

ные массивы, которые используются, например, для работы с матрица-

ми или таблицами (двумерные массивы), кубами данных или наборами

однотипных таблиц – трехмерные массивы. Объявление многомерного

массива выполняется следующим образом: <тип>[, ... ,] <имя_переменной>;

Page 63: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

63

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

единицу меньше размерности объявляемого массива. Для создания са-

мого массива используется операция new: имя_переменной = new тип [n1,..., nm];

Одновременно можно выполнить инициализацию массива. Дан-

ные для инициализации задаются в фигурных скобках, при этом данные

по каждой строке заключаются в свои фигурные скобки. Ниже показан

пример объявления инициализации двумерного массива: int [,] ms = new int [2,3] {{1,2,3},{4,5,6}};

Доступ к элементам массива выполняется путем указания в квад-

ратных скобках индексов по каждой размерности, разделенных запятой.

Например: int k = ms[1,2]; // получим значение 6

Ниже приведен пример использования двухмерного массива: int s = 0;

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

for (int j = 0; j < 3; j++)

s += ms[i, j];

В результате выполнения этого кода s получит значение 21.

3.5.3. Ступенчатые массивы

Новым видом массивов C# ступенчатые массивы (jagged arrays).

Такой массив можно рассматривать как одномерный массив, элементы

которого являются массивами, элементы которых, в свою очередь, сно-

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

уровня вложенности. Объявление таких массивов выполняется следую-

щим образом: <тип>[]...[] <имя_массива>;

имя_массива = new <тип>[количество]...[];

При этом в конструкторе массива у последней размерности коли-

чество элементов не задается

Например: int[][] jagged = new int[3][];

Для создания самих строк массива нужно создать объекты соответст-

вующих типов: jagged[0] = new int[2] { 1, 2 };

jagged[1] = new int[6] { 3, 4, 5, 6 };

jagged[2] = new int[3] { 7, 8, 9 };

Схема данного ступенчатого массива показана на рис. 3.6.

Page 64: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

64

Рис. 3.6. Структура ступенчатого массива jagger.

Индексы элементов такого массива задаются в разных квадратных

скобках. Например: jagged[row][e]

Ниже приведен пример работы со ступенчатым массивом: for (int row = 0; row < jagged.Length; row++) {

for (int e = 0; e < jagged[row].Length; e++){

Console.Write("{0},{1},{2};",

row, e, jagged[row][e]);

}

Console.WriteLine(); }

Результатом работы данного примера будет: 0,0,1;0,1,2;

1,0,3;1,1,4;1,2,5;1,3,6;

2,1,7;2,2,8;2,3,9;

3.5.4. Массивы как коллекции

Массивы C# можно также рассматривать как коллекции, и рабо-

тать с ними не используя индексы для получения нужного элемента. В

этом случае вместо циклов типа for по каждому измерению достаточно

использовать единый цикл foreach. Однако отметим, что имеется

только возможность чтения элементов коллекции (массива), но нет воз-

можности их изменения. Ниже приведен пример кода для вывода эле-

ментов массива на экран: string[] ss = new string[]{"раз ","два ","три "}

foreach (string s in A )

Console.Write(s);

4. Описание и использование классов Классы позволяют разработчику создать собственные типы, кото-

рые могут использоваться точно так же, как и встроенные типы. Описа-

ние классов имеет следующую структуру: [режим_доступа] [partial] class имя_класса

{

// описание элементов класса

}

Page 65: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

65

Описатель класса partial указывает на то, что объявление клас-

са может быть записано в нескольких файлах. Класс и все его элементы

имеет некоторый режим доступа. Основные значения для режима дос-

тупа описаны в табл. 4.1.

Таблица 4.1.

Режимы доступа Режим доступа Пояснение public общедоступные элементы, к ним можно обратиться из лю-

бого метода любого класса программы; protected элементы доступные в рамках объявляемого класса и из ме-

тодов производных классов; internal элементы доступные из методов классов, объявляемых в

рамках сборки, где содержится объявление данного класса; protected

internal

элементы доступные в рамках объявляемого класса, из ме-

тодов производных классов, а также из методов классов, ко-

торые объявлены в сборке, содержащей объявление данного

класса; private элементы доступные только в методах того класса, которому

они принадлежат (используется по умолчанию).

Независимо от значения режима доступа, все элементы класса

доступны в его методах. Если элементы имеют режим доступа pri-

vate (возможно, опущенный), то тогда они доступны только в методах

самого класса. Такие элементы называются закрытыми. Если некото-

рые элементы должны быть доступны для методов любого класса B,

которому доступен сам класс A, то они должны быть описаны с атрибу-

том public (открытые элементы). Закрытые элементы составляют

важную часть класса, позволяя клиентам не вникать во многие детали

реализации этого класса. Открытые элементы класса описывают интер-

фейс класса (способ взаимодействия с объектами класса).

Если некоторые элементы класса A должны быть доступны для

вызовов в методах класса B, являющегося потомком класса A (наследо-

вании классов рассмотрено в разделе 5.2) то такие элементы описыва-

ются с атрибутом protected. Если некоторые элементы должны быть

доступны только для методов классов B1, B2 и так далее, дружествен-

ных по отношению к классу A, то они должны быть описаны с атрибу-

том internal, и включены в одну сборку (описаны в одном проекте).

Ниже показан пример описания пользовательского типа Person: class Person {

private string name; // задается значение ""

private int age; // задается значение 0

private double salary; // задается значение 0.0

Page 66: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

66

public Person(string name, int age, double salary) {

this.name = name;

this.age = age;

this.salary = salary;

}

public void PrintPerson(){

Console.WriteLine("name= {0}, age = {1},

salary ={2}", name, age, salary);

}

}

В данном примере в конструкторе класса (метод Person) исполь-

зуется предопределенное поле this. Это ссылка на тот объект, в кото-

ром выполняется вызываемый метод. В данном случае this.name это

поле name текущего объекта, а присваиваемое значение name – это па-

раметр метода.

Используя данный класс можно объявлять переменные и созда-

вать его экземпляры. Например: Person p;

p = new Person("Петров А.И. ", 25, 28000);

4.1. Поля класса Состояние объектов класса (а также структур) задается с помо-

щью переменных, которые называются полями (fields). При создании

объекта – экземпляра класса, в динамической памяти ("куче") выделяет-

ся участок памяти, содержащий набор полей, определяемых классом, и в

них записываются значения, характеризующие начальное состояние

данного экземпляра. Объявление полей выполняется следующим обра-

зом: [<режим_доступа>] [модификаторы] <тип> <имя>;

Например, в классе Person заданы следующие поля: private string name; // задается значение ""

private int age; // задается значение 0

private double salary; // задается значение 0.0

Общим правилом является создание закрытых полей, имеющих режим

доступа private. Данный режим задается полям по умолчанию. Таким

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

Любое воздействие на состояние объекта класса выполняется с исполь-

зованием свойств или методов класса, которые контролируют последст-

вия этих воздействий.

Если полям класса не задается значение при объявлении, то они

автоматически инициализируются значениями по умолчанию. Для зна-

Page 67: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

67

чащих переменных – это нулевое значение, для строк – это пустая стро-

ка, а для ссылочных переменных – это стандартное значение null, как

показано в комментариях описания класса Person.

Обычные поля класса создаются для каждого создаваемого объек-

та в выделенном ему участке памяти в "куче". Областью видимости по-

лей являются все методы класса. При этом для использования поля тре-

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

для объекта класса Person в днях может быть выполнено следующим

образом: public int CalcDays() { // вычисление возраста в днях

int days = age * 365; // age – поле данного объекта

return days;

}

В качестве модификатора поля может использоваться ключевое

слово static, обозначающее, что это статическое поле. Например, в

классе Person может быть описано следующее статическое поле: static int numPersons=0; // кол-во объектов класса

Статическое поле класса создаются только одно для всего класса. Для

обращения к нему нужно указать имя класса и через точку имя статиче-

ского поля. Например: Person.numPersons++;

Если поле имеет режим public, то оно доступно там, где имеется

ссылка на объект данного класса. Для обращения к этим полям из мето-

дов других классов (если поля открытые) нужно использовать ссылоч-

ную переменную, которая хранит ссылку на созданный объект. Напри-

мер: Person p; //объявление переменной типа Person

p = new Person(); //создание объекта и сохр. ссылки

p.Name = "Иванов П.И. "; //задание значения public поля

Время существования полей определяется объектом, которому

они принадлежат. Объекты в "куче", с которыми не связана ни одна

ссылочная переменная, становятся недоступными и удаляются сборщи-

ком мусора.

4.2. Методы класса Метод класса это именованный блок выполняемого кода (набор

операторов), который может быть вызван на выполнение из разных час-

тей программы. При вызове метода он выполняет свой код, а затем воз-

вращает управление тому коду, который его вызвал. Методы также мо-

гут возвращать некоторое значение.

Page 68: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

68

Методы описывают поведение объектов класса. Два объекта одно-

го класса имеют один и тот же набор методов. В описании метода клас-

са выделяют две части – заголовок и тело метода: заголовок_метода

{ // тело_метода }

Заголовок метода описывается следующим образом: [режим доступа][модификаторы] тип_результата имя_метода

([список_формальных_параметров])

Тип результата и список формальных параметров составляют сиг-

натуру метода. Следует отметить, что в сигнатуру не входят ни имя

метода, ни имена параметров. Квадратные скобки в описании заголовка

метода показывают, что режим доступа, модификаторы и параметры

могут быть опущены при описании метода. Обязательным при описании

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

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

сок формальных параметров отсутствует. Формально тип результата

метода указывается всегда, но ключевое слово void, означает отсутст-

вие какого-либо результата. Вот несколько простейших примеров опи-

сания методов: void A(int p) {...}

int B(){...}

public void C(){...}

Список формальных параметров метода может быть пустым, что

является обычным для методов класса. Список может содержать фикси-

рованное число параметров, разделяемых символом запятой. Объявле-

ние формального параметра имеет следующий вид: [ref|out|params]тип_параметра имя_параметра

Обязательным является указание типа и имени параметра. Ника-

ких ограничений на тип параметра не накладывается.

Несмотря на фиксированное число формальных параметров, есть

возможность при вызове метода передавать ему произвольное число

фактических параметров. Для реализации этой возможности в списке

формальных параметров необходимо задать ключевое слово params.

Оно задается один раз и указывается только для последнего параметра

списка, объявляемого как массив произвольного типа. При вызове ме-

тода этому формальному параметру соответствует массив, содержа-

щий произвольное число передаваемых фактических параметров.

Содержательно, все параметры метода разделяются на три груп-

пы: входные, выходные и обновляемые. Входные параметры передают

Page 69: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

69

информацию методу, их значения в теле метода только читаются. Вход-

ные параметры задаются без ключевых слов out и ref.

Выходные параметры представляют собой результаты метода, они

получают значения в ходе работы метода. Выходные параметры всегда

должны сопровождаться ключевым словом out. Выходным парамет-

рам в теле метода обязательно должно присваиваться некоторое значе-

ние. В противном случае возникает ошибка уже на этапе компиляции.

Обновляемые параметры выполняют обе функции. Их значения

используются в ходе вычислений и обновляются в результате работы

метода. Обновляемые параметры сопровождаться ключевым словом

ref.

Для примера рассмотрим следующую группу методов:

void A(out long p2, int p1) {

p2 = (long) Math.Pow(p1,3);

Console.WriteLine("Метод A-1");

}

void A(out long p2, params int[] p) {

p2=0;

for(int i=0; i <p.Length; i++)

p2 += (long)Math.Pow(p[i],3);

Console.WriteLine("Метод A-2");

}

int f(ref int a) {

return(a++);

}

Рассмотрим только заголовки методов. Все методы закрыты, по-

скольку объявлены без модификатора доступа. Перегруженные методы

с именем A не возвращают результирующее значение, а метод f – воз-

вращает значение. Оба перегруженных метода A() имеют разную сиг-

натуру. Хотя имена и число параметров у всех методов одинаковы, но

типы и ключевые слова, предшествующие параметрам, различны. Пер-

вый параметр у обоих перегруженных методов – выходной и сопровож-

дается ключевым словом out, в теле метода этому параметру присваи-

вается значение.

Параметр функции f – обновляемый, он снабжен ключевым сло-

вом ref, в теле функции используется его значение для получения ре-

зультата функции, но и само значение параметра изменяется в теле

функции. Один метод из группы перегруженных методов использует

ключевое слово params для своего последнего параметра.

Page 70: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

70

Методы класса имеют значительно меньше параметров, чем мето-

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

зуется концепция классов. Это связано с тем, что методы класса исполь-

зуются для работы с данными класса, которому они принадлежат. Все

поля доступны любому методу по определению. Нужно четко понимать,

что в момент выполнения программной системы работа идет не с клас-

сом вообще, а с конкретными объектами – экземплярами класса. Из по-

лей соответствующего объекта – цели вызова – извлекается информа-

ция, нужная методу в момент вызова, а работа метода чаще всего сво-

дится к обновлению значений полей этого объекта. Поэтому очевидно,

что методу не нужно через входные параметры передавать информа-

цию, содержащуюся в полях объекта. Если в результате работы метода

обновляется значение некоторого поля, то также не нужен никакой вы-

ходной параметр.

4.2.1. Тело метода

Тело метода класса является блоком, который представляет собой набор

описаний переменных и операторов, заключенных в фигурные скобки.

Если метод является функцией (т.е. должен возвращать некоторое зна-

чение), то в блоке должен быть, хотя бы один оператор return. В опера-

торе return задается значение, возвращаемое функцией, и он имеет

следующий вид: return [выражение];

Для переменных, объявленных в блоке, память выделяется в стеке

и они могут использовать только в теле метода. После завершения вы-

полнения метода все локальные переменные будут уничтожены. В опе-

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

поля объекта (описанные в классе), для которого выполняется данный

метод, и передаваемые в заголовке параметры метода.

4.2.2. Синтаксис вызова методов

Как уже отмечалось, метод может вызываться в выражениях или

как отдельный оператор. Вызов метода имеет следующий вид:

имя_метода([список_фактических_параметров])

Формальный параметр, задаваемый при описании метода – это

всегда имя параметра (идентификатор). Фактический параметр может

быть следующим выражением: [ref|out]выражение

Между списком формальных и списком фактических параметров

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

и статусу параметров. Если в методе описано n формальных парамет-

Page 71: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

71

ров, то фактических параметров должно быть не меньше n (соответст-

вие по числу). Каждому i-му формальному параметру (для всех i от 1

до n-1) ставится в соответствие i-й фактический параметр. Послед-

нему формальному параметру, при условии, что он объявлен с ключе-

вым словом params, ставятся в соответствие все оставшиеся фактиче-

ские параметры (соответствие по порядку). Если формальный пара-

метр объявлен с ключевым словом ref или out, то фактический пара-

метр должен сопровождаться таким же ключевым словом в точке вызо-

ва (соответствие по статусу).

Если у формального параметра задан тип T, то выражение, за-

дающее фактический параметр, должно быть согласовано по типу с ти-

пом T, т.е. совпадать с типом T; являться его потомком (соответствие

по типу) или допускать преобразование к типу T (как и при выполне-

нии операции присваивания).

Если формальный параметр является выходным – объявлен с клю-

чевым словом ref или out, то соответствующий фактический параметр

не может быть выражением, поскольку используется в левой части опе-

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

можно присвоить значение.

4.2.3. Выполнение вызова метода

При вызове метода выполнение начинается с вычисления факти-

ческих параметров, которые являются выражениями. Для простоты по-

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

блок, соответствующий телу метода (в реальности все значительно эф-

фективнее). В этом блоке происходит замена имен формальных пара-

метров фактическими параметрами. Для выходных параметров, для ко-

торых фактические параметры также являются именами, эта замена или

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

формальный параметр ссылкой на реально существующий объект, за-

данный фактическим параметром. Чуть более сложным является вызов

по значению, применяемый к формальным параметрам, которые объяв-

лены без ключевых слов ref или out. При вычислении выражений, за-

данных такими фактическими параметрами, их значения присваиваются

специально создаваемым переменным, локализованным в теле испол-

няемого блока. Имена этих локализованных переменных и подставля-

ются вместо имен формальных параметров. Понятно, что тип локализо-

ванных переменных определяется типом соответствующего формально-

го параметра. Задание значений фактических параметров формальным

параметрам выполняется так же, как и в операторе присваивания.

Page 72: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

72

Если для параметра не указаны ключевые слова ref или out, то к

нему будет применяться вызов по значению. Даже если в теле метода

происходит изменение значения этого параметра, то оно действует

только на время выполнения тела метода. Как только метод заканчивает

свою работу (завершается блок), все локальные переменные (в том чис-

ле, созданные для замены формальных параметров) оканчивают свое

существование, так что изменения не затронут фактических параметров,

и они сохранят свои значения, бывшие у них до вызова. Все выходные

параметры, значения которых предполагается изменить в процессе ра-

боты метода, должны иметь ключевое слово ref или out.

В языке C# основную роль играют ссылочные типы, в основном

выполняется работа с классами и объектами. Когда методу передается

объект ссылочного типа, то все поля этого объекта могут меняться в ме-

тоде любым образом. А это означает, что, несмотря на то, что параметр

формально не является выходным (т.е., не имеет ключевых слов ref

или out), но он используется, как и при вызове по значению. Сама

ссылка на объект остается неизменной, но состояние объекта, его поля

могут полностью обновиться. Такая ситуация является типичной и

представляет один из основных способов изменения состояния объек-

тов. Именно поэтому ref или out не очень часто появляются при опи-

сании параметров метода.

4.3. Перегрузка методов В C# не требуется уникальности имен методов в классе. Сущест-

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

грузкой, а сами методы называются перегруженными. Перегрузка ме-

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

бором параметров. Типичный пример – это конструкторы класса, но пе-

регружать можно и любой другой метод.

При вызове не перегруженного метода, его имя однозначно опре-

деляет, какой метод должен выполняться в точке вызова. Когда же ме-

тод перегружен, то знания имени недостаточно. Уникальной характери-

стикой перегруженных методов является их сигнатура. Перегруженные

методы, имея одинаковое имя, должны отличаться либо числом пара-

метров, либо их типами, либо ключевыми словами (заметьте: с точки

зрения сигнатуры, ключевые слова ref или out не отличаются). Уни-

кальность сигнатуры позволяет вызвать требуемый перегруженный ме-

тод. Ниже приведен пример, в котором выполнена перегрузка метода A,

для которого заданы четыре метода с именем A, различающиеся по сиг-

натуре, и метод для их тестирования TestLoadMethods:

Page 73: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

73

void A(out long p2, int p1){

p2 =(long) Math.Pow(p1,3);

}

void A(out long p2, params int[] p){

p2=0;

for(int i=0; i <p.Length; i++)

p2 += (long)Math.Pow(p[i],3);

Console.WriteLine("Метод A-2");

}

void A(out double p2, double p1){

p2 = Math.Pow(p1,3);

}

void A(out double p2, params double[] p){

p2=0;

for(int i=0; i <p.Length; i++)

p2 += Math.Pow(p[i],3);

}

public void TestLoadMethods(){

long u=0; double v =0;

A(out u, 7); A(out v, 7.5);

Console.WriteLine ("u= {0}, v= {1}", u,v);

A(out v,7);

Console.WriteLine("v= {0}",v);

A(out u, 7,11,13);

A(out v, 7.5, Math.Sin(11.5)+Math.Cos(13.5), 15.5);

Console.WriteLine ("u= {0}, v= {1}", u,v);

}

Эти методы различаются типами параметров и ключевым словом

params. Когда вызывается метод A с двумя параметрами, то, в зависимо-

сти от типа, будет вызываться реализация без ключевого слова params.

Когда же число параметров больше двух, то работает реализация, по-

зволяющая работать с заранее не фиксированным числом параметров.

4.4. Конструкторы класса Специальными методами класса, которые вызываются при созда-

нии объектов класса, являются конструкторы.

Имя конструктора или нескольких перегруженных конструкторов

должно совпадать с именем класса. В отличие от других методов, в за-

головке конструктора не указывается тип результата (даже void).

Если программист не определяет конструктор класса, то к классу

автоматически добавляется конструктор по умолчанию – конструктор

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

Page 74: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

74

создает хотя бы один конструктор, то автоматического добавления кон-

структора без параметров не происходит.

Чаще всего, в классе описываются собственные конструкторы и,

как правило, не один, задавая разные варианты инициализации полей

(отличающиеся набором формальных параметров). Например, в классе

Person можно описать следующие конструкторы: public Person() { }

public Person(string n) { name = n; }

public Person(string n, int a, double s) {

name = n; age = a; salary = s;

}

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

параметров, во второй строке описывается конструктор с одним пара-

метром типа string. Третий конструктор имеет три параметра.

Создание объектов чаще всего происходит, при объявлении ссы-

лочных переменных в момент их инициализации. Рассмотрим создание

трех объектов класса Person: Person p1 = new Person(), p2 = new Person();

Person p3 = new Person("Петрова");

Объекты p1, p2 и p3 класса Person объявляются с инициализа-

цией, задаваемой унарной операцией new, которой в качестве параметра

передается конструктор класса Person.

В языке С++, кроме конструкторов, также используются специ-

альные методы – деструкторы (они начинаются с символа ~ после кото-

рого стоит имя класса, например, ~Person()), которые вызываются

при удалении объектов. Но в C# задачей удаления объектов занимается

специальный компонент среды CLR – сборщик мусора, поэтому дест-

рукторы обычно не используются.

4.5. Свойства класса Свойства (Properties) являются специальными синтаксическими

конструкциями, предназначенными для обеспечения работы с закрыты-

ми полями класса.

4.5.1. Описание свойств класса

Свойства являются частными случаями методов класса и описы-

ваются следующим образом: режим_доступа <тип> <название> {

set {

// задание значения value некоторому закрытому полю

}

get {

Page 75: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

75

return(<закрытое поле>);

}

}

Например: public int Age {

set {if (value > 0) _age = value;}

get {return(_age);}

}

В теле свойства могут содержаться методы get и set, однако

один из них может быть опущен. Метод get возвращает значение за-

крытого поля, метод set – задает значение закрытому полю, используя

значение, передаваемое ему в момент вызова, которое содержится в

служебной переменной со стандартным именем value. Например: public class Person

{ //поля (все закрыты)

string fam="", status="", health="";

int age=0, salary=0;

//методы - свойства

public string Fam{

set {if (fam == "") fam = value;}

get {return(fam);}

}

public string Status{ get {return(status);} }

public int Age{

set{

age = value;

if(age < 7)status ="ребенок";

else if(age <17)status ="школьник";

else if (age < 22)status = "студент";

else status = "служащий";

}

get {return(age);}

}

public int Salary{

set {salary = value;}

}

// конструкторы

public Person() {}

public Person(string fm) {fam = fm;}

public Person(string fm, int a, int s) {

Fam = fm; Age=a; Salary=s;

}

}

Page 76: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

76

Пользователь объектов класса работает со свойствами так, как ес-

ли бы они были обычными полями объектов, используя их без скобок,

(требуемые при вызове методов), как в правой, так и в левой части опе-

ратора присваивания. В том случае, когда свойство используется в ле-

вой части оператора присвоения, как например: Person pr = new Person();

pr.Age = 25;

на выполнение вызывается метода set() свойства Age и присваиваемое

значение будет передаваться в этот метод в виде параметра value.

В случае использовании свойства в правой части оператора при-

своения, как например: int n = pr.Age;

на выполнение будет вызываться метода get() свойства Age.

4.5.2. Автоматически реализуемые свойства

В версии языка C# 3.0 можно создавать автоматически реализуе-

мые свойства. Вместо обычного определения свойства, например: private int _myItem;

public int MyItem {

get {return myItem;}

set {myItem = value;}

}

Можно сделать следующее описание: public int MyProperty { get; set; }

Использование такого синтаксиса даст точно такой же результат, что и

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

Описание полей соответствующих свойству теперь можно не делать.

Позже такое описание свойств можно будет заменить на более подроб-

ное, с указанием конкретных полей и операторов в методах get и set.

4.5.3. Инициализация объектов класса

В C# 3.0 разрешается назначать значения свойствам объектов во

время создания. Например, если имеется описание класса, имеющего

два свойства A и B: public class MyClass {

public int A { get; set; }

public int B { get; set; }

}

то можно создать и инициализировать объект данного класса следую-

щим образом: MyClass myObject = new MyClass() { A = 5, B = 10 };

Page 77: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

77

4.6. Индексаторы Индексаторы позволяют использовать для объекта класса опера-

цию получения или задания значения по индексу [i], точно также, как

у массивов или коллекций. В описании класса может быть только один

индексатор. Он имеет стандартное имя this, после которого в квад-

ратных скобках в заголовке перечисляются индексы, число которых

равно используемой размерности. Также как и свойства класса, индек-

сатор имеет методы get и set, которые описывают получение и зада-

ние значения для заданных индексов.

Например, в класс Person можно добавить закрытое поле child-

ren, описывающее массив детей человека, а доступ к нему можно вы-

полнять с использование индексатора: const int Child_Max = 20; //максимальное число детей

Person[] children = new Person[Child_Max];

int count_children=0; //число детей

public Person this[int i] //индексатор

{

get {

if (i>=0 && i< count_children)

return(children[i]);

else return(children[0]);

}

set {

if (i==count_children && i< Child_Max)

{children[i] = value; count_children++;}

}

}

В методах get и set, обеспечивающих доступ к массиву child-

ren, по которому ведется индексирование, проверяется правильность

задания индекса. Закрытое поле count_children, хранящее текущее

число детей, доступно только для чтения благодаря добавлению соот-

ветствующего метода-свойства. Запись в это поле происходит в методе

set индексатора, когда к массиву children добавляется новый эле-

мент. Ниже показан пример использования индексатора: Person p1 = new Person("Петров",42,20000);

Person p2 = new Person("Петров",22,10000);

p1[0] = p2;

Person p3= new Person("Петрова", 5,0);

p1[1] = p3;

Сonsole.WriteLine ("Фам={0}, возраст={1}, статус={2}",

P1.Fam, p1.Age, p1.Status);

Console.WriteLine ("Сын={0}, возраст={1}, статус={2}",

Page 78: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

78

P1[0].Fam, p1[0].Age, pers1[0].Status);

Console.WriteLine ("Дочь={0}, возраст={1}, статус={2}",

p1[1].Fam, p1[1].Age, p1[1].Status);

Отметим, что индексатор создает из объекта как бы массив объек-

тов, индексированный по соответствующему полю, в данном случае по

полю children.

4.7. Статические поля и методы класса При создании нового объекта класса, в памяти создается структу-

ра данных с полями, описанными в классе. Однако у класса могут быть

поля, связанные не с конкретными объектами, а со всем классом. Эти

поля объявляются как статические с модификатором static. Стати-

ческие поля доступны всем методам класса. Статические поля описы-

вают общие данные всех объектов классов. Например, у класса Person

может быть статическое поле message, в котором каждый объект мо-

жет оставить сообщение для других объектов класса.

У класса могут также быть и статические методы, объявленные с

модификатором static. Такие методы не могут использовать данные

конкретных объектов класса – они обрабатывают только общие дан-

ные класса, хранящиеся в его статических полях. Например, в классе

Person может быть статический метод, обрабатывающий данные из

статического поля message. Другим частым случаем применения ста-

тических методов является ситуация, когда класс предоставляет свои

методы объектам других классов, как например, классы Console и

Math из библиотеки FCL, которые не имеют собственных полей.

Статические поля и методы вызываются с использованием име-

ни класса. <имя класса>.<статическое поле или метод>

Использовать статические элементы класса можно в любое время, неза-

висимо от того, созданы или нет объекты класса.

4.8. Перегрузка операций класса Для объектов класса можно описать порядок выполнения встро-

енных операций путем описания статических методов, имя которых со-

стоит из ключевого слова operator после которого стоит знак пере-

гружаемой операции (т.е. "operator X", где X – символ перегружаемой

операции). В качестве параметров данного метода используются опе-

ранды, участвующие в операции. Унарные операции имеют один пара-

метр, а бинарные – два. В каждом случае один параметр должен быть

такого же типа, как класс, в котором определяется операция, как пока-

зано в следующем примере:

Page 79: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

79

public static Complex operator +(Complex c1,Complex c2)

{...}

Не все операции могут быть перегружены, а для некоторых опе-

раций есть ограничения (табл. 4.2).

Таблица 4.2.

Возможность перегрузки операций в классах

Операции Возможность описания +, -, !, ~, ++,

--, true, false

эти унарные операции можно перегрузить;

+, -, *, /, %,

&, |, ^, <<, >>

эти бинарные операции можно перегрузить;

==, !=, <, >,

<=, >=

операции сравнения можно перегрузить, но только

парами: если операция == перегружена, то также

должна быть перегружена операция != (и наоборот);

то же самое и для операций < и >, а также <= и >=; &&, ||

условные логические операции нельзя перегрузить, но

они вычисляются с помощью операций & и |, которые

могут быть перегружены; []

операцию индексирования массива нельзя перегру-

зить, но можно описать индексатор класса; ()

операцию приведения типов нельзя перегрузить, но

можно описать новые операции преобразования типов

(explicit и implicit); +=, -=, *=, /=,

%=, &=, |=, ^=,

<<=, >>=

операции присвоения нельзя перегрузить, но, напри-

мер, += вычисляется с помощью операции +, которая

допускает перегрузку; =, ., ?:, ->,

new, is, sizeof,

typeof

эти операции перегружать нельзя!

Перегрузки в классе операций позволяют записывать выражения с

использованием объектов данного класса, аналогичные арифметиче-

ским и условным выражениям с обычно применяемыми знаками опера-

ций и сохранением приоритетов операций.

Пример перегрузки операции сложения:

public struct Point {

float x,y;

// overloaded operator +

public static Point operator +(Point p1,Point p2)

{ return new Point(p1.x + p2.x, p1.y + p2.y); }

}

Page 80: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

80

4.9. Определение преобразования типов Для класса можно описать порядок выполнения неявного или яв-

ного преобразования объектов данного класса в объекты других типов

или объектов других типов в объекты данного класса.

Для описания неявного (по умолчанию) преобразования, которое

не требует выполнения кастинга (указания требуемого типа в скобках)

необходимо в описание класса включить статический метод с атрибу-

том implicit и именем, состоящим из ключевого слова operator,

после которого стоит название типа, в который будет выполняться пре-

образование. В качестве параметра данного метода описывается пере-

менная того типа, который будет преобразовываться. Например: // в описании Point – неявное преобразование в тип int

public static implicit operator int(Point p)

{ int n; n = p.x; return n;}

. . .

Point p = new Point(5,6);

int a;

a = p + 2;

Описание явного преобразования выполняется аналогично, но пе-

ред статическим методом нужно задавать атрибут explicit. Пример

описания явного преобразования приведен ниже: // в классе Fahrenheit явное преобразование в Celsius

public static explicit operator Celsius(Fahrenheit f) {

return new Celsius((5.0f/9.0f)*(f.degrees-32));

}

//...

Fahrenheit f = new Fahrenheit(100.0f);

Celsius c = (Celsius)f;

Рис. 4.1. Схема наследования классов.

Page 81: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

81

Следует отметить, что существуют некоторые ограничения на

описание преобразований типов:

Нельзя задать преобразование типов, если один класс является

производным от другого класса (компилятор уже знает, как вы-

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

Преобразование должно быть описано только в одном из классов

(в исходном или результирующем типе).

На рис. 4.1 в качестве примера показана схема наследования классов

A,B,C,D. В данном случае возможными являются только преобразова-

ния между классами C и D, так как они не связаны отношением насле-

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

образом (если задавать явное преобразования, что является обычным

для пользовательских классов):

public static explicit operator D(C value)

{

//описание преобразования класса C в класс D

}

public static explicit operator C(D value)

{

//описание преобразования класса D в класс C

}

Такие преобразования можно поместить или в класс C или в класс

D, но не в каждый из них.

4.10. События класса Прежде чем рассматривать понятие и описание событий класса

нужно рассмотреть еще один пользовательский тип – делегаты (dele-

gate), с использованием которых и выполняется описание событий.

4.10.1. Делегаты

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

хранить ссылки на методы. В описании делегата задается сигнатура ме-

тодов (возвращаемое значение и типы параметров), ссылки на которые

можно сохранить в экземпляре делегата данного типа. В экземпляре де-

легата может быть сохранена ссылка на все методы классов, сигнатура

которых совпадает с сигнатурой делегата.

Объявление нового пользовательского типа – делегата имеет сле-

дующий вид: [<режим_доступа>] delegate <тип_результата >

<имя_класса> (<список_параметров>);

Page 82: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

82

Этим объявлением задается множество методов с некоторой сиг-

натурой, у которой параметры определяются списком, заданным в объ-

явлении делегата, и тип возвращаемого значения определяется типом

результата делегата. Например: public delegate float Compute(float a,float b);

В данном случае, в переменной типа Compute можно будет сохранять

ссылки на методы, которые имеют два формальных параметра типа

float и возвращают значение типа float.

Объявление делегата может быть сделано в пространстве имен, на-

ряду с объявлениями других классов или внутри некоторого класса, на-

ряду с объявлениями методов и свойств; в этом случае делегат может

использоваться только в методах данного класса.

Делегаты описывают новый тип данных, а не задают реализации

методов. Классы, использующие делегат, могут создать экземпляры де-

легата. В конструкторе делегата в качестве параметра используется имя

метода, сигнатура которого соответствует описанию делегата. Напри-

мер: Compute func = null;

func = new Compute(Plus);

или более краткий вариант: func = Plus;

В данном случае, метод Plus имеет следующий заголовок: public static float Plus(float a, float b) {...}

Экземпляры делегатов могут хранить ссылки не на один, а на не-

сколько методов. Для удобства работы, в классе делегатов переопреде-

лены две операции: "+=" и "-=". Они используются для добавления

ссылок в список вызовов экземпляров делегатов и удаления из списка,

соответственно. func += new Compute(Multi); //или func += Multi;

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

описаны, чтобы делегат мог работать. Однако существует еще один

способ задания значений делегатам, это использование анонимных ме-

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

качестве параметра для используемого делегата. Например: Compute func1 = delegate(float a, float b)

{

float с = a+b;

return c;

};

Page 83: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

83

Преимуществом анонимных методов состоит в том, что уменьшается

объем кода, не нужно описывать метод только для того, чтобы исполь-

зовать его с делегатом. Такой способ удобен при задании делегатов для

событий.

Используя экземпляр делегата можно вызвать метод, ссылка на

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

Например: float b = func(2.5F, 3.4F);

Фактически получается, что переменная func становится другим

именем метода Plus. В этом случае в действительности будет вызы-

ваться метод с именем Plus, ссылка на который сохранена в func.

При вызове на выполнение экземпляра делегата, который содер-

жит несколько ссылок, будут последовательно вызываться на выполне-

ние все методы, ссылки на которые хранятся в данном экземпляре. Рас-

смотрим следующий пример: class Program {

public delegate float Compute(float a, float b);

static void Main() {

Comp func = null;

func += new Comp(Plus); //или func += Plus;

func += new Comp(Multi); //или func += Multi;

float b = func(2.0F, 3.5F);

}

public static float Plus(float a, float b)

{ return a + b; }

public static float Multi(float a, float b)

{ return a * b; }

}

Здесь в классе Program объявлен новый тип – делегат Comp. А также

описаны два метода: Plus и Multi, сигнатуры которых соответствуют

сигнатуре, заданной делегатом Comp. Переменная func в методе Main

являются экземпляром класса Comp и ей присваиваются ссылки описан-

ные в классе методы. При выполнении операции выполнения методов

для переменной func: float b = func(2.0F, 3.5F); на выполне-

ние будут вызываться два метода Plus и Multi. Результат выполнения

последнего метода будет сохранен в переменной b (в данном случае это

будет 7.0)

4.10.2. События С помощью делегатов в классах можно описывать события. Со-

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

Объекты одного класса могут использовать объекты других классов.

Для этого они создают объекты требуемого класса и вызывают его ме-

Page 84: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

84

тоды (передают ему сообщения). При работе вызванных методов часто

возникает необходимость передать сообщения о возникших ситуациях

(важных изменения в своей работе) объектам вызывающего класса, без

завершения их работы. Для передачи таких сообщений и используются

специальные элементы класса – события.

Класс, которому требуется сообщать о возникновении некоторых

ситуаций – событий, должен уметь делать следующее:

объявить тип делегата, соответствующего событию;

объявить событие в классе (экземпляр делегата);

инициировать в нужный момент событие, передав обработчику

необходимые для обработки параметры (т.е. некоторым способом

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

Инициируя событие, класс посылает сообщение получателям со-

бытия – объектам некоторых других классов. Класс, инициирующий

событие, называется отправителем сообщения (sender). Классы, чьи

объекты получают сообщения, называют классами – получателями со-

общений (receiver). Класс-отправитель сообщения, в принципе, не знает

своих получателей. Он просто инициирует событие. Одно и то же со-

общение может быть получено и по-разному обработано произвольным

числом объектов разных классов.

Интерфейсы и многие классы FCL обладают стандартным набо-

ром предопределенных событий.

Объявление события в классе

Сообщение, посылаемое при инициировании события, является

аналогом вызова метода. Поскольку сигнатура посылаемого сообщения

должна соответствовать сигнатуре принимаемого сообщения, то объяв-

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

В C# работа событий реализуется с помощью делегатов. Каждое

событие задается делегатом, описывающим сигнатуру метода, который

будет обрабатывать сообщения (обработчика события). Объявление со-

бытия – это двухэтапный процесс:

Вначале объявляется делегат, как это рассматривалось ранее.

Объявление делегата описывается в некотором классе. Но, часто,

это объявление находится вне класса в пространстве имен. На-

пример: public delegate <тип> <имя_делегата> ([параметры]);

Если делегат определен, то в классе, создающем события, доста-

точно объявить событие как экземпляр соответствующего делега-

та. При этом используется ключевое слово event (это гарантиру-

Page 85: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

85

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

сах). Объявление события выполняется следующим образом: рublic event <имя_делегата> <имя_события>;

Ниже приведен пример объявления делегата и события, пред-

ставляющего экземпляр этого делегата: // Объявление делегата - новый тип данных

public delegate void MyHandler(object o, int n);

// Объявление события класса – экземпляр делегата

public event MyHandler MyEvent;

Для инициирования события требуется просто вызвать на выпол-

нение экземпляр делегата. Например: // проверка, что для события заданы обработчики

if (MyEvent != null)

MyEvent(this, i); // запускаем событие

Ниже приведен пример описания класса, в котором содержится

метод, выполнение которого занимает достаточно долгое время. Дан-

ный метод сообщает о выполнении каждой десятой доли своей работы.

Объекты, использующие данный метод могут визуально информировать

пользователей о ходе выполнения работы в данном методе. // Объявление нового типа – делегата с параметрами:

//o – ссылка на объект, который инициировал событие

//n – процент выполненной работы

public delegate void MyHandler (object o, int n);

// объявление класса с событием

public class ClassWithEvent{ // класс с событием

// Создание экземпляра делегата - событие

public event MyHandler MyEvent;

// поле класса

private int Volume = 0;

// конструктор класса – парамер – объем работы

public EventClass(int p){if (p > 0) Volume = p;}

//описание метода выполняющего долгую работу

//(но в данном примере бессмысленную)

public void LongWork(){

double s = 0; int k = 0;

int st = Volume / 10; // десятая часть работы

for (int i = 0; i < Volume; i++) {

s += Math.Atan(Math.Sqrt(i) * Math.Sqrt(i));

if (k == st){// выполнена заданная часть работы

if (MyEvent != null){

int n = (int)(i*100.0)/Volume;

MyEvent(this, n); // запускаем событие

Page 86: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

86

}

k = 0;

}

else k += 1;

} } }

В данном примере, в классе ClassWithEvent описан делегат My-

Handler, сигнатура которых содержит три параметра. Событие

MyEvent в классе ClassWithEvents является экземпляром класса, за-

данного делегатом.

Обработка событий в классах

Класс, который заинтересован в обработке событий, должен:

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

сигнатурой делегата, который задает событие;

иметь ссылку на объект, создающий событие, чтобы получить

доступ к этому событию – event-объекту;

присоединить обработчик события к event-объекту. Это реализу-

ется с использованием операции += для описанного события.

Пример класса, использующего описанный ранее класс ClassWi-

thEvent с событием, показан ниже: class Program {

const int WorkVolume = 10000000; // объем работы

public static void Main(){

// создается объект

ClassWithEvent obj=new ClassWithEvent(WorkVolume);

// задается обработчик события

obj.MyEvent += new MyHandler(ShowStar);

// запускам длинную работу у объекта a

obj.LongWork();

// ждем нажатия клавиши

Console.ReadLine();

}

// обработчик события

private static void ShowStar(object o, int n) {

Console.WriteLine(" Выполнено {0}%", n);

//Console.Write("*"); // можно просто выводить *

}

}

5. Отношения между классами Описываемые в программах классы обычно находятся в некото-

рых отношениях друг с другом. В объектно-ориентированном програм-

Page 87: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

87

мировании определены два основных типа отношений между классами:

отношение вложенности и отношение наследования:

Классы А и В находятся в отношении вложенности, если одним из

полей класса В является объект класса А. Класс А называется по-

ставщиком (сервером) класса В, а класс В называется клиентом

(пользователем) класса А.

Классы А и В находятся в отношении наследования, если при объ-

явлении класса В класс А указан в качестве родительского класса.

Класс А называется родителем класса В, а класс В называется на-

следником класса А.

Оба отношения – наследования и вложенности – являются тран-

зитивными. Если В – клиент А и С – клиент В, то отсюда следует, что С

– клиент А. Если В – наследник А и С – наследник В, то отсюда следует,

что С – наследник А. Прямые классы – наследники (между которыми

непосредственно задано отношение) часто называются дочерними

классами. Непрямые родители называются предками, а их непрямые

наследники – потомками. Отметим, что цепочки вложенности и насле-

дования могут быть достаточно длинными.

Классы библиотеки FCL связаны как отношением вложенности,

так и отношением наследования.

5.1 Отношение вложенности В качестве примера рассмотрим описание двух простых классов A

и B, связанных отношением вложенности. Класс-поставщик A включа-

ет два поля, конструктор и один метод: public class A {

public int fldA;

public A(int f){

fldA = f;

}

public void MethodA(){

Console.WriteLine("Это класс A");

}

}

Класс B является клиентом класса A, так как одно из его полей

objA является объектом класса A: public class B {

A objA;

public string fldB;

public B(string fB,int fA)

Page 88: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

88

{

objA = new A(fA);

fldB = fB;

}

public void MethodB(){

objA.MethodA();

Console.WriteLine("Это класс B");

}

}

Обратите внимание, что конструктор клиента (класса B) отвечает

за инициализацию своих полей и поэтому создает объект поставщика

(класса A).

5.2. Отношение наследования Большие возможности объектно-ориентированного программиро-

вания основываются на возможности наследовании. Если описан неко-

торый полезный класс, то он может использоваться многократно. Но

часто возникает необходимость расширить его возможности, задать ему

новую функциональность. Если попытаться изменять сам работающий

класс, то это может привести к тому, что перестанут работать те части

приложения, которые уже используют данный класс.

Кроме этого часто может потребоваться настраивать под потреб-

ности новых задач классы, которые уже содержатся в библиотеке FCL

или сборках созданных другими разработчиками

В этом случае и используется отношение наследования. Сущест-

вующий класс можно не менять, а создать на его основе новый класс,

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

5.2.1. Описание производного класса

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

двоеточия и имени базового класса: [режим_доступа] class <имя_потомка> : <имя_родителя>

{

// описание производного класса

}

В языке C# производный класс может иметь только один базовый класс

(т.е. отсутствует множественное наследование).

Производный класс наследует поля и методы базового класса.

Однако, в производном классе возможен прямой доступ только к полям

и методам базового класса с режимом доступа public и protected.

Элементы родительского класса, имеющие режим доступа private, хо-

тя и наследуются, но по-прежнему остаются закрытыми. Методы произ-

водного класса не могут обращаться непосредственно к закрытым эле-

Page 89: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

89

ментам базового класса, а могут использовать их только через насле-

дуемые открытые методы базового класса.

Единственное, что не наследует производный класс – это конст-

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

описаны свои конструкторы. Кроме этого, в производном классе (по-

томке) можно добавлять новые поля и методы. Производный класс не

может ни отменить, ни изменить модификаторы или типы полей, насле-

дованных от родителя – он может только добавить собственные поля.

Добавление новых методов в производном классе имеет особенности,

которые рассмотрены в разделе 5.2.3.

Рассмотрим пример описания классов A и B. public class A {

private string sA;

protected int fldA;

public A(int f){

fldA = f; sA = "X";

}

public float MethodA(){

return fldA/2F;

}

}

Класс B является классом производным от класса A и он наследует поле

fldA и метод MethodA() базового класса: public class B : A {

protected string fldB;

public B(string fB,int fA): base(fA) {

fldB = fB;

}

public float MethodB(){

return fldA*2F;

}

}

// ...

// использование классов

A a = new A(5);

B b = new B("Y",7);

float x1 = b.MethodA(); // x1 = 2.5 – метод класса A

float x2 = b.MethodB(); // x2 = 5 – метод класса B

5.2.2. Конструкторы производного класса

Каждый класс должен описывать свои собственные конструкто-

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

ная особенность. Каждый конструктор создает объект своего класса. Но

Page 90: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

90

производный класс, прежде чем создать собственный объект, вызывает

конструктор базового класса, создавая родительский объект, который

затем будет дополнен полями производного класса. Этот процесс про-

должается до тех пор, пока не будет создан объект самого базового

класса.

Вызов конструктора родителя происходит не в теле конструктора,

а в заголовке, пока еще не создан объект класса. Для вызова конструк-

тора используется ключевое слово base, именующее родительский

класс. Например, для класса Derived может быть описаны следующие

конструкторы: public Derived() {}

public Derived(string name, int cred, int deb)

: base (name,cred) {

debet = deb;

}

Для конструктора без параметров вызов аналогичного конструктора ро-

дителя подразумевается по умолчанию. Для конструкторов с парамет-

рами вызов конструктора с параметрами родительского класса должен

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

через двоеточие после списка параметров конструктора. Конструктору

производного класса передаются все параметры, необходимые для ини-

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

класса для инициализации его полей.

Таким образом, вызов конструктора – производного класса приво-

дит к цепочке вызовов конструкторов – базовых классов. Затем, в об-

ратном порядке, создаются объекты, начиная с объекта самого базового

класса, и выполняются методы соответствующих конструкторов, ини-

циализирующие поля и выполняющие другую работу этих конструкто-

ров. Последним создается объект производного класса и выполняется

тело конструктора производного класса.

5.2.3. Добавление методов в производном классе

В производном классе можно описывать новые методы с имена-

ми, отличными от имен наследуемых методов. В этом случае никаких

особенностей нет. Например: public void DerivedMethod(){

Console.WriteLine("Это метод класса Derived");

}

Производный класс не может изменять поля базового класса

(предков), но может изменять наследуемые им методы. Если в произ-

водном классе описывается метод с именем, совпадающим с именем ме-

тода базового класса, то возможны три ситуации:

Page 91: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

91

перегрузка метода – возникает, когда имя создаваемого метода

совпадет с именем метода базового класса, их сигнатуры отлича-

ется; в этом случае в производном классе будет несколько пере-

груженных методов с одним именем, а вызов нужного метода бу-

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

скрытие метода – возникает, когда имя и сигнатура создаваемо-

го метода совпадает с именем и сигнатурой наследуемых методов,

которые не имеют модификатор virtual или abstract; в этом

случае новый метод производного класса скрывает метод базово-

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

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

класса будет скрыт; но это не значит, что он становится недоступ-

ным; скрытый метод базового класса всегда может быть вызван,

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

замещение метода – возникает, когда имя и сигнатура создавае-

мого метода производного класса совпадает с именем и сигнату-

рой метода базового класса, который имеют модификатор vir-

tual или abstract; чтобы новый метод производного класса пе-

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

модификатор override; в этом случае при обращении к объекту

производного класса с помощью ссылок базового типа всегда бу-

дет вызываться данный метод.

Метод производного класса, скрывающий метод базового класса,

следует сопровождать модификатором new, указывающим на новый ме-

тод. Если этот модификатор опущен, но из контекста ясно, что речь

идет о новом методе, то выдается предупреждающее сообщение при

компиляции проекта. Например, если базовый класс имеет в своем со-

ставе метод View(), то для описания в производном классе метода с та-

ким же именем и сигнатурой, который скрывает метод базового класса

нужно использовать модификатор метода new: new public void View(){

base.View();

Console.WriteLine("Вывод результата");

}

Если не писать модификатор new, то он добавится по умолчанию

с выдачей предупреждающего сообщения о скрытии метода базового

класса.

Для пояснения разницы между скрытыми и замещенными мето-

дами производного класса рассмотрим пример. Опишем базовый класс

A с двумя методами, один из которых имеет модификатор virtual:

Page 92: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

92

public class A {

private string sA;

protected int fldA;

public A(int f)

{ fldA = f; sA = "X"; } // конструктор

// обычный метод

public void MethodA()

{ Console.WriteLine("метод MethodA класса A"); }

// виртуальный метод

public virtual void MethodC(int i)

{ Console.WriteLine("метод MethodC класса A"); }

}

А теперь опишем класс B, производный от класса A, методы которого

скрывают (модификатор new) или замещают (модификатор override)

методы базового класса: public class B : A {

protected string fldB;

public B(string fB,int fA): base(fA)

{ fldB = fB; }

public void MethodB()

{ Console.WriteLine("новый MethodB класса B"); }

// перегрузка метода базового класса

public void MethodA(int i)

{ Console.WriteLine("перегруж. MethodA класса B"); }

// скрытие метода базового класса

public new void MethodA()

{ Console.WriteLine("скрытый MethodA класса B;");}

public override void MethodC(int i)

{ Console.WriteLine("замещен. MethodC класса B"); }

}

Объекты этих классов используем в методе Main() класса Program: class Program {

static void Main(string[] args){

A a = new A(5); // описание объекта класса A

B b = new B("abc", 5); // описание объекта класса B

Console.WriteLine("*** Вызов методов объекта A");

a.MethodA();

a.MethodC(3);

Console.WriteLine(

"*** Вызов методов класса B через ссылку типа B");

b.MethodA(2); //вызов перегруженного метода

b.MethodA(); // вызов скрытого метода

b.MethodB(); // вызов нового метода

b.MethodC(3); // вызов замещенного метода

Page 93: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

93

Console.WriteLine(

"*** Вызов методов класса B через ссылку типа A");

// ссылка базового типа = объект произв. класса

a = b; // разрешенная операция

a.MethodA();

//a.MethodB(); //ошибка – в классе A нет MethodB

a.MethodC(3);

}

}

Результатом работы данной программы будет: *** Вызов методов объекта A

метод MethodA класса A

метод MethodC класса A

*** Вызов методов класса B через ссылку типа B

перегруж. MethodA класса B

скрытый MethodA класса B;

новый MethodB класса B

замещен. MethodC класса B

*** Вызов методов класса B через ссылку типа A

метод MethodA класса A

замещен. MethodC класса B

Из примера видно, что скрытые и замещенные методы ведут себя

по-разному в тех случаях, когда переменной базового типа присваивает-

ся ссылка на объект производного класса (в примере: a = b;). В ре-

зультате этого тип ссылки (A) отличается от типа объекта, на который

она указывает. При использовании такой переменной можно вызывать

для объекта только методы базового класса и замещенные методы про-

изводного класса, но нельзя вызвать скрытые и новые методы произ-

водного класса.

5.3. Абстрактные классы С наследованием тесно связан еще один важный способ проекти-

рования семейства классов – абстрактные классы. Метод класса назы-

вается абстрактным, если при определении метода задана его сигна-

тура, но не задана реализация метода, а класс называется абстракт-

ным, если он имеет, хотя бы один, абстрактный метод. Объявление

абстрактных методов и абстрактных классов должно сопровождаться

модификатором abstract. Поскольку абстрактные классы не являют-

ся полностью определенными классами, то нельзя создавать объекты

абстрактных классов. Абстрактные классы могут иметь потомков,

частично или полностью реализующих абстрактные методы роди-

тельского класса. Абстрактный метод чаще всего рассматривается как

Page 94: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

94

виртуальный метод, переопределяемый потомком, поэтому к ним при-

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

Абстрактные классы являются одним из важнейших инструмен-

тов объектно-ориентированного проектирования классов. В основе лю-

бого класса лежит абстракция данных. Абстрактный класс описывает

эту абстракцию, не входя в детали реализации, ограничиваясь описани-

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

проектирование абстрактного класса Stack, описывающего стек, мо-

жет состоять из рассмотрения основных операций над стеком и не опре-

делять, как будет реализован стек – списком или массивом. Два потом-

ка абстрактного класса – ArrayStack и ListStack могут быть уже

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

стека. Вот описание полностью абстрактного класса Stack: public abstract class Stack {

public Stack(){}

public abstract void put(int item);

public abstract void remove();

public abstract int item();

public abstract bool IsEmpty();

}

Создадим теперь класс ListStack производный от этого абст-

рактного класса Stack, реализация которого основана на списковом

представлении. Данный класс будет включать объекты класса

Linkable, задающего элементы списка. Класс Linkable выглядит

следующим образом: public class Linkable{

public Linkable(){ }

public int info;

public Linkable next;

}

В данном классе описаны два поля и конструктор по умолчанию.

Рассмотрим теперь класс ListStack: public class ListStack: Stack {

public ListStack(){

top = new Linkable();

}

Linkable top;

public override void put(int item){

Linkable newitem = new Linkable();

newitem.info = item;

newitem.next = top;

top = newitem;

Page 95: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

95

}

public override void remove()

{ top = top.next; }

public override int item()

{ return(top.info); }

public override bool IsEmpty()

{ return(top.next == null); }

}

Класс имеет одно поле top класса Linkable и методы, наследо-

ванные от абстрактного класса Stack. Теперь можно описать реализа-

цию операций со стеком. Например: public void TestStack(){

ListStack stack = new ListStack();

stack.put(7); stack.put(9);

Console.WriteLine(stack.item());

stack.remove(); Console.WriteLine(stack.item());

stack.put(11); stack.put(13);

Console.WriteLine(stack.item());

stack.remove(); Console.WriteLine(stack.item());

if(!stack.IsEmpty()) stack.remove();

Console.WriteLine(stack.item());

}

В результате работы этого теста будет напечатана следующая по-

следовательность целых чисел: 9, 7, 13, 11, 7.

6. Другие пользовательские типы Классы являются основными пользовательскими типами. Но кро-

ме них также активно используются такие пользовательские типы, как:

структуры, перечисления и интерфейсы.

6.1. Структуры Структура – это частный случай классов, объекты которых хра-

нятся в стеке, а не в "куче", как у классов.

6.1.1. Описание и использование структур

Объявление структуры выполняется аналогично объявлению

класса: [режим_доступа] [модификаторы]struct имя_структуры

{

// тело_структуры

}

В структурах нельзя инициализировать поля при их объявлении, а

также нельзя объявлять конструкторы без параметров.

Page 96: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

96

Для структур не может быть задан родительский тип (класс или

структура), однако, структура может наследовать интерфейсы. Кроме

этого, для структур неприменим модификатор abstract.

Все, что может быть включено в класс, также может быть вклю-

чено и в структуру: поля, методы и конструкторы. Аналогично классу,

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

может иметь несколько конструкторов. struct PointS { // структура описания точек

public float x, y;

public PointS(float xx, float yy)

{ x = xx; y = yy; }

public override string ToString() {

return "{X:" + x + ";" + "Y:" + y + "}";

}

}

Структуры похожи на классы по своему описанию и ведут себя

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

операции присваивания. Если при присваивании переменных класса

происходит только присваивание ссылок на объекты, то при присваива-

нии переменных структур – создается новый объект в стеке. Console.WriteLine("Присваивание значащего типа!");

PointS pts1 = new PointS(3, 5), pts2;

pts2 = pts1;

Console.WriteLine("pts1:{0}; pts2=pt1:{1}",pts1,pts2);

pts1.x += 10;

Console.WriteLine("pts1.х+=10:{0};pts2:{1}",pts1,pts2);

Console.WriteLine("Присваивание ссылочного типа!");

Point pt1 = new Point(3, 5), pt2;

pt2 = pt1;

Console.WriteLine("pt1:{0}; pt2=pt1: {1}", pt1,pt2);

pt1.x += 10;

Console.WriteLine("pt1.х+=10:{0}; pt2: {1}",pt1, pt2);

При выборе между структурой и классом следует руководство-

ваться следующими правилами: если у класса число полей относитель-

но невелико, а число возможных объектов относительно велико, делай-

те его структурой. В этом случае память объектам будет отводиться в

стеке, не будут создаваться лишние ссылки, что позволит повысить эф-

фективность работы.

6.1.2. Структуры описания временных данных

В пространстве имен System библиотеки FCL имеются встроен-

ные структуры для работы с данными о времени, такие как DateTime и

TimeSpan. Структура DateTime представляет момент времени, кото-

Page 97: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

97

рый обычно задается в виде даты (год, месяц, день) и времени дня (ча-

сы, минуты, секунды). В данной структуре описано большое количество

конструкторов, методом, свойств и операций. Примером конструктора

является DateTime (int year, int month, int day). Например: DateTime dTime = new DateTime(1980, 8, 15);

Часто используемым свойством является Now, которое возвращает

текущее время, установленное на компьютере. Свойство DayOfWeek

возвращает значение перечисления DayOfWeek.

В качестве примера переопределения операции можно привести

переопределение операции сложения: public static DateTime operator+(DateTime d, TimeSpan t)

В данной операции используется еще одна структура TimeSpan, кото-

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

секундах. Пример создания экземпляра интервала времени показан ни-

же: //интервал времени в 17 дней, 4 часа, 2 минуты и 1 сек.

TimeSpan tSpan = new TimeSpan(17, 4, 2, 1);

В этом случае операция сложения может быть выполнена следующим

выражением: DateTime result = dTime + tSpan;

Результатом данного выражения будет момент времени 01.09.1980

4:02:01.

6.2. Перечисления Перечисление задает пользовательский тип, переменные которого

могут принимать заданное конечное множество возможных значений. У

перечислений нет собственных методов, и поэтому объявление этого ти-

па содержит только список возможных значений. Вот формальное оп-

ределение перечислений: [режим_доступа]enum имя_перечисления[:базовый класс]

{<список_возможных_значений>}

Например: public enum MyColors {red, blue, yellow, black, white};

public enum Rainbow {красный, оранжевый, желтый,

зеленый, голубой, синий, фиолетовый};

public enum Sex: byte {man=1, woman};

public enum DayOfWeek {Sunday, Monday, Tuesday,

Wednesday, Thursday, Friday, Saturday};

Список возможных значений задает все значения, которые могут

получать переменные этого типа. Возможные значения перечисления

должны быть идентификаторами, в которых допускаются и буквы рус-

ского алфавита. Можно указать также базовый для перечисления класс.

Page 98: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

98

Реально значения объектов перечисления в памяти задаются зна-

чениями базового класса, так же, как значения класса bool реально

представлены в памяти нулем и единицей, а не константами true и

false, удобными для их использования программистами в тексте про-

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

жество проекции начинается с нуля. Но при желании можно изменить

интервал представления и сам базовый класс. Естественно, на базовый

класс накладывается ограничение. Он должен быть одним из встроен-

ных классов, задающих счетное множество (int, byte, long, другие

счетные типы).

Ниже показан пример работы с переменными – экземплярами раз-

личных перечислений: public void TestEnum() {

MyColors color1 = MyColors.white;

TwoColors color2;

color2 = TwoColors.white;

//if(color1 != color2) color2 = color1;

if(color1.ToString() != color2.ToString())

Console.WriteLine ("Цвета разные: {0}, {1}",

color1, color2);

else Console.WriteLine("Цвета одинаковые: {0},{1}",

color1, color2);

Console.WriteLine ("color1={0}, color2={1},",

color1, color2);

int num = (int)color3;

Sex who = Sex.man;

DayOfWeek first_work_day = (DayOfWeek)(long)1;

Console.WriteLine ("who={0}, first_work_day={1}",

who,first_work_day);

}

6.3. Интерфейсы Еще одним пользовательским типом являются интерфейсы, опи-

сывающие ссылочный тип, который не может иметь какой-либо реали-

зации, для которого нельзя создавать экземпляры, и все методы которо-

го являются открытыми. То, что интерфейс не может иметь реализации,

означает, что все его методы и свойства являются абстрактными. Ин-

терфейсы описывают формальный открытый контракт между провайде-

ром сервиса (классами, которые реализуют интерфейс) и потребителями

сервиса (другими классами, которые используют объекты класса, реали-

зующего интерфейс).

Page 99: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

99

6.3.1. Описание интерфейсов

Описание интерфейса имеет следующий вид: <режим доступа> interface <имя_интерфейса>

{

<тип_результата> имя_метода1(<параметры>);

//

< тип_результата> имя_методаN(<параметры>);

}

Отметим, что в описании интерфейса режим доступа к методам не ука-

зывается – он всегда public. Имя интерфейса обычно начинается с бу-

квы I. Например, можно описать следующий интерфейс: public interface IMyInterface

{

int Method1( );

double Method2( );

void Method3( );

}

Описание интерфейса почти эквивалентно описанию следующего абст-

рактного класса: public abstract class MyInterface

{

public abstract int Method1( );

public abstract double Method2( );

public abstract void Method3( );

}

Однако абстрактные классы и интерфейсы имеют несколько важных

отличий:

интерфейс не может иметь никаких переменных (полей), а абст-

рактные классы могут иметь поля;

интерфейсы не могут иметь никакую реализацию методов, а абст-

рактные классы могут включать реализованные методы;

интерфейс может наследоваться только от интерфейсов, а абст-

рактный класс может быть производным от любого класса;

абстрактные классы могу иметь не public методы и свойства; в

интерфейсе все методы являются открытыми;

абстрактный класс может иметь конструктор, а интерфейс нет;

Реализация методов интерфейса выполняется в классах, которые

их наследуют. Причем класс, наследующий интерфейс, должен описы-

вать реализацию всех методов интерфейса, без исключения. Например: public class MyClass : IMyInterface

{

public int Method1( ) {...}

Page 100: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

100

public double Method2( ) {...}

public void Method3( ) {...}

//другие элементы класса

}

В языке C# класс не может быть наследником нескольких базовых

классов (нет множественного наследования). Однако разрешается

множественное наследование интерфейсов. Это означает, что класс

обязуется выполнять несколько контрактов. При описании класса, реа-

лизующего интерфейс, не нужно использовать такие модификаторы, как

new или override.

Для работы с объектами с помощью интерфейса нужно создать

экземпляр класса, реализующий этот интерфейс, и присвоить его пере-

менной, типом которой является название интерфейса. Например: IMyInterface obj;

obj = new MyClass( );

obj.Method1( );

Однако в этом случае можно работать с методами, реализующими ин-

терфейс и с помощью переменной, типом которой является класс. На-

пример: MyClass obj;

obj = new MyClass( );

obj.Method1( );

6.3.2. Два способа реализации интерфейсов

Способ реализации интерфейса, который был показан в предыду-

щем разделе, называется неявной реализацией интерфейса, так как от-

крытые методы с именем и сигнатурой, соответствующие, методам ин-

терфейса, неявно предполагаются реализацией этих методов интерфей-

са.

Другим способом является явная реализация интерфейсов. При

таком способе для всех методов, реализующих интерфейс, не задается

режим доступа (по умолчанию они считаются закрытыми) и их имя де-

лается составным <имя_интерфейса>.<имя_метода>, т.е. для каждо-

го метода указывается название интерфейса, в котором он описан. На-

пример: public interface IMyInterface {

int Method1( );

double Method2( ) {...}

void Method3( ) {...}

}

public class MyClass : IMyInterface {

int IMyInterface.Method1( ) {...}

double IMyInterface.Method2( ) {...}

Page 101: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

101

void IMyInterface.Method3( ) {...}

//другие методы и элементы

}

Отметим, что в этом случае методы интерфейса должны быть неявно

описаны как закрытые (по умолчанию); для них нельзя использовать

другой режим доступа, включая и private. В этом случае, клиент мо-

жет вызывать явно заданные методы интерфейса только с помощью пе-

ременной интерфейсного типа: IMyInterface obj;

obj = new MyClass( );

obj.Method1( );

6.3.3. Приведение к типу интерфейса

Как уже было сказано, интерфейсы являются абстрактными типа-

ми и поэтому не могут использоваться непосредственно. Для примене-

ния интерфейса нужно выполнить преобразование объекта, который его

реализует, в ссылочную переменную интерфейсного типа. Имеются два

способа преобразования типа: неявное и явное.

При простом присваивании интерфейсной переменной экземпляра

класса выполняется неявное преобразование типов: IMyInterface obj;

obj = new MyClass( );

obj.Method1( );

Если класс MyClass не реализует интерфейс IMyInterface, то при

компиляции будет выдаваться сообщение об ошибке, так как компиля-

тор может читать метаданные класса и поэтому может заранее опреде-

лить, реализует ли класс заданный интерфейс.

Однако существует много ситуаций, когда нельзя использовать

неявное преобразование. В этих случаях нужно использовать явное пре-

образование (кастинг): IMyInterface obj;

// ...

obj = (IMyInterface)new MyClass( );

obj.Method1( );

Однако следует помнить, что если объект, к которому применяется яв-

ное преобразование, не поддерживает требуемый интерфейс, то во вре-

мя выполнения будет формироваться исключение (exception) и если

его не обработать, то работа программы завершаться аварийно.

Для того, чтобы избежать таких ситуаций нужно использовать

операцию as. Например: IMyInterface obj;

MyClass с = new MyClass( );

Page 102: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

102

obj = с as IMyInterface;

Операция as выполняет преобразование к требуемому интерфейсу, если

объект поддерживает этот интерфейс, и присваивает полученное значе-

ние переменной. Если преобразование невозможно, то вместо генерации

исключения (как это происходит при кастинге), данная операция при-

сваивает интерфейсной переменной значение null. Например: SomeType obj1;

IMyInterface obj2;

// некоторый код для инициализации obj1

obj2 = obj1 as IMyInterface;

if(obj2 != null) {

obj.Method1( ); //переменная имеет верное значение

}

else { // объект obj1 не поддерживает нужный интерфейс

//обработка ошибки

}

6.3.4. Встроенные интерфейсы

Интерфейсы позволяют описывать некоторые желательные свой-

ства и методы, которыми должны обладать разные классы. В библиоте-

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

все классы, допускающие сравнение своих объектов, обычно наследуют

интерфейс IComparable, реализация которого позволяет сравнивать

объекты не только на равенство, но и на "больше" или "меньше".

Имеются много интерфейсов, описанных в библиотеке FCL. Они

используются разными классами библиотеки так же, как и классами,

создаваемыми разработчиками.

Примером такого интерфейса является интерфейс IComparable,

который имеет всего один метод CompareTo(object obj), возвра-

щающий целочисленное значение, положительное, отрицательное или

равное нулю, в зависимости от выполнения отношения "больше",

"меньше" или "равно".

Как правило, в классе вначале определяют метод CompareTo, а

после этого вводят перегруженные операции, чтобы выполнять сравне-

ние объектов привычным образом с использованием знаков операций

отношения. Чтобы ввести отношение порядка на классе Person, рас-

смотренном ранее, нужно сделать этот класс наследником интерфейса

IComparable. Для этого нужно реализовать в этом классе метод ин-

терфейса CompareTo: public class Person:IComparable

{

public int CompareTo(object obj){

Page 103: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

103

Person p = prs as Point;

if (!p.Equals(null))

return (string.Compare(fam,p.fam));

else

throw new ArgumentException ("неверный тип");

}

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

}

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

object, то перед выполнением сравнения его нужно привести к типу

Person. Такое приведение выполняктся с помощью операции as, про-

веряющей корректность выполнения приведения типов.

Отношение порядка на объектах класса Person задается как от-

ношение порядка на фамилиях людей. Так как строки наследуют ин-

терфейс IComparable, то для фамилий людей вызывается метод

string.Compare(fam,p.fam), его результат и возвращается в каче-

стве результата метода CompareTo для сравнения людей. Если пара-

метр метода не будет соответствовать нужному типу, то выбрасывается

исключение со специальным уведомлением.

Введенные методы могут быть проверены с помощью следующе-

го тестового примера: public void TestCompare()

{

Person ps1 = new Person("Иванов");

Person ps2 = new Person("Петров");

Person ps3 = new Person("Сидоров");

Console.WriteLine("{0} > {1} = {2}", ps1.Fam,

ps2.Fam, (ps1 > ps2));

Console.WriteLine("{0} < {1} = {2}", ps2.Fam,

ps3.Fam, (ps2 < ps3));

}

Результат: Иванов > Петров = False

Петров < Сидоров = True

Другими интерфейсами, активно используемыми в классах биб-

лиотеки FCL, являются IEnumerable, ICollection и IList, описы-

вающие не обобщенные коллекции объектов, которые могут вызываться

по индексу. Основные методы и свойства интерфейсов ICollection и

IList описаны в табл. 4.3 и 4.4.

Page 104: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

104

Таблица 4.3.

Основные элементы интерфейса ICollection

Элемент Описание Count свойство – количество объектов в коллекции; CopyTo() метод копирования элементов коллекции в массив типа

Array, начинас с заданного индекса массива; GetEnumerator() получение объекта, поддерживающего интерфейс

IEnumerable), который позволяет просматривать всю

коллекцию (для foreach).

Таблица 4.4.

Основные элементы интерфейса IList

Элемент Описание Add() метод добавления объекта к коллекции; Clear() удаление всех объектов из коллекции; Contains() проверка на наличие в коллекции объекта (true/false); IndexOf() определение индекса для заданного объекта; Insert() вставка объекта в место заданное индексом; Remove() удаление первого встреченного экземпляра указанного объекта RemoveAt() удаление всех объектов, начиная с заданного индекса; [index] индексатор, который позволяет получить или задать объекты

по заданному индексу.

Такие интерфейсы поддерживаются разными свойствами классов

библиотеки FCL. Например, свойство Controls в классе форм Form

(для разработки графического интерфейса) или свойство Tables в клас-

се DateSet (для организации отсоединенного режима работы с базами

данных).

7. Коллекции и словари В приложениях объекты классов обычно используются не отдель-

но, а группами. Например: студенты группы, автомобили организации,

преподаватели кафедры и т.п. Для работы с группами объектов исполь-

зуются специальные классы, позволяющие хранить и выполнять разные

операции объектами группы. Платформа .NET предоставляет простран-

ство имен System.Collections, в котором определены различные

коллекции и словари, имеющие разную эффективность в разных сцена-

риях, гибкие возможности сортировки объектов, поддерживающие раз-

ные типы данных и словари, содержащие пары (ключ, значение).

Page 105: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

105

7.1. Коллекции Коллекция это любой класс, который позволяет собирать объекты

в списки и предоставлять некоторый доступ к ним. Все коллекции реа-

лизуют интерфейс ICollection. Наиболее используемыми классами

коллекций FCL из пространства имен System.Collections являются:

ArrayList – простая коллекция (наследуется от интерфейса

IList), которая может хранить объекты любого типа. Экземпля-

ры ArrayList могут хранить произвольное количество объектов,

при необходимости, они увеличивают объем используемой памя-

ти.

Queue – коллекция, которая поддерживает следующий порядок

работы с объектами: «первым пришел, первым вышел» (first-in,

first-out – FIFO). Можно использовать Queue на сервере обработ-

ки сообщений, для временного хранения сообщений перед обра-

боткой, или для хранения информации о клиентах, которые долж-

ны обрабатываться в порядке «Первым – пришел, первым –

ушел».

Stack – коллекция, которая поддерживает следующий порядок

работы с объектами: «Последним пришел – первым ушел» (last-in,

first-out – LIFO). Можно использовать Stack для хранения наибо-

лее новых изменений, чтобы можно было их отменить.

7.1.1. Коллекция ArrayList

Класс ArrayList используется для добавления объектов, кото-

рые можно получить с помощью операции индексации (начинается с 0)

или с помощью цикла foreach. Вместимость объектов класса ArrayL-

ist расширяется по мере необходимости. В приведенном ниже примере

показано, как использовать метод Add класса ArrayList для добавле-

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

дый объект с помощью цикла foreach: ArrayList al = new ArrayList();

al.Add("Привет");

al.Add("Мир");

al.Add(5);

al.Add(new FileStream("delemete", FileMode.Create));

Console.WriteLine("Коллекция содержит " + al.Count + "

элемента:");

foreach (object s in al)

Console.Write(s.ToString() + " ");

Результат работы данного метода будет следующим:

Page 106: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

106

Коллекция содержит 4 элемента:

Привет Мир 5 System.IO.FileStream

Отметим, что коллекции при создании можно инициализировать,

так же, как и массивы. Например: ArrayList al = new ArrayList() {"Привет", "Мир", "это",

"проверка"};

В реальном приложении обычно в коллекцию ArrayList добав-

ляются элементы одного и того же типа. Это позволяет вызывать метод

Sort для сортировки объектов с помощью реализации в них интерфей-

са IComparable. Можно также использовать метод Remove для удале-

ния ранее добавленных объектов, а также метод Insert для добавления

элементов в конкретное место заданное индексом. Это показано в при-

веденном ниже примере: ArrayList al;

al = new ArrayList(){"Привет", "Мир", "это", "проверка"};

al.Remove("это");

al.Insert(1, "наш");

al.Sort();

foreach (object s in al)

Console.Write(s.ToString() + " ");

Результатом работы будет следующая строка: Мир наш Привет проверка

Отметим, что элементы упорядочены по алфавиту (стандартная

реализация интерфейса IComparable для строк) и слово “это” отсут-

ствует. Можно также создать собственную реализацию IComparer для

управления порядком сортировки. Тогда, как метод ICompara-

ble.CompareTo управляет порядком сортировки по умолчанию для

объектов класса, метод IComparer.Compare может использоваться для

задания специальных способов сортировки. Например, рассмотрим про-

стой класс, который только реализует интерфейс IComparer: public class reverseSort : IComparer {

int IComparer.Compare(Object x, Object y) {

return((new

CaseInsensitiveComparer()).Compare(y, x));

}}

Имея такой класс можно передать экземпляр данного класса ме-

тоду Sort класс ArrayList. В приведенном ниже примере показано

как можно это сделать. В данном примере используется метод Ad-

dRange(), с помощью которого к коллекции ArrayList добавляется

массив элементов (если есть отдельные элементы, а массива еще нет, то

он может быть сразу создан и инициализирован):

Page 107: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

107

ArrayList al = new ArrayList();

al.AddRange(new string[] { "Привет", "мир", "это",

"проверка" });

al.Sort(new reverseSort()); //передаем свой IComparer

foreach (object s in al)

Console.Write(s.ToString() + " ");

Этот код выдаст следующий результат: это проверка Привет мир

Для получения обратного порядка элементов в ArrayList ис-

пользуется метод ArrayList.Reverse(). Для поиска конкретного

элемента в ArrayList нужно вызвать метод BinarySearch и пере-

дать ему экземпляр объекта, который ищется. Метод BinarySearch

вернет индекс найденного объекта. Если объект не будет найден, то ре-

зультатом будет -1. Приведенный ниже пример выводит значение 2, так

как строка “это” находится на третьей позиции, а первая позиция имеет

значение 0: ArrayList al = new ArrayList();

al.AddRange(new string[] {"Привет", "Мир", "это",

"проверка"});

Console.WriteLine(al.BinarySearch("это"));

Аналогично, метод Contains() класса ArrayList возвращает

значение true, если в коллекции ArrayList содержится указанный

объект, и значение false, если этого объекта в коллекции нет.

7.1.2. Коллекции Queue и Stack

Классы Queue(очередь) и Stack(стек) хранят объекты, которые

могут быть получены и удалены за один шаг. Очереди используют по-

рядок обработки FIFO, а Stack использует последовательность обра-

ботки LIFO.

Класс Queue использует методы Enqueue и Dequeue для добав-

ления и удаления объектов, а класс Stack использует для этого методы

Push и Pop.

Следующий пример показывает различие между классами: Queue q = new Queue();

q.Enqueue("Привет");

q.Enqueue("мир");

q.Enqueue("просто тестирование");

Console.WriteLine("Использование Queue:");

for (int i = 1; i <= 3; i++)

Console.WriteLine(q.Dequeue().ToString());

Stack s = new Stack();

s.Push("Привет");

Page 108: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

108

s.Push("мир");

s.Push("просто тестирование");

Console.WriteLine("Использование Stack:");

for (int i = 1; i <= 3; i++)

Console.WriteLine(s.Pop().ToString());

Данный пример выдает следующий результат: Использование Queue:

Привет

мир

просто тестирование

Использование Stack:

просто тестирование

мир

Привет

Можно использовать методы Queue.Peek() и Stack.Peek()

для получения объектов из коллекций без их удаления. Метод Clear()

используется во всех коллекциях для удаления всех объектов.

7.2. Словари Классы словарей (dictionaries) задают соответствие между ключа-

ми (key) и значениями (value). Например, можно связать идентифика-

тор сотрудника (например, табельный номер) с объектом класса, описы-

вающего сотрудника. В FCL включены следующие основные классы

словарей:

Hashtable – словарь (хешированная таблица) пар имя/значение,

которые могут быть получены по имени или индексу;

SortedList – словарь, который автоматически сортируется по

ключу;

StringDictionary – словарь Hashtable в котором пары

имя/значение могут быть только строками string.

Словарь SortedList включает пары ключ/значение. И ключ и

значения могут быть объектами любого типа. В этом словаре пары ав-

томатически сортируются по ключу. Например, следующий код создает

экземпляр SortedList с тремя парами ключ/значение. Затем он пока-

зывает описания для Queue, SortedList и Stack в соответствующем

порядке:

SortedList sl = new SortedList();

sl.Add("Stack", "Коллекция объектов типа LIFO.");

sl.Add("Queue", "Коллекция объектов типа FIFO.");

sl.Add("SortedList", "Коллекция пар ключ/значение.");

Page 109: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

109

foreach (DictionaryEntry de in sl)

Console.WriteLine(de.Value);

Отметим, что SortedList является массивом объектов типа

DictionaryEntry. Для доступа к ключу этих объектов используется

свойство DictionaryEntry.Key, а для получения значения – Dictio-

naryEntry.

Можно также получить значения коллекций напрямую с помощью

ключа SortedList[ключ]. Ниже показан пример для получения опи-

сания объекта с ключом “Queue”. Можно также обратиться к элементам

коллекции с использованием индекса с помощью метода GetByIn-dex(n):

Console.WriteLine(sl["Queue"]);

Console.WriteLine(sl.GetByIndex(0));

7.3. Универсальные классы Под универсальностью (generality) понимается способность класса

объявлять используемые им типы как параметры. Класс с параметрами,

задающими типы, называется универсальным классом (generic class).

Объявить класс C# универсальным просто: для этого достаточно

указать в объявлении класса, какие из используемых им типов являются

параметрами. Список типовых параметров класса, заключенный в уг-

ловые скобки, добавляется к имени класса: class MyClass<T1, ... Tn> {...}

Как и всякие формальные параметры, Ti являются именами

(идентификаторами). В описании класса эти имена могут задавать типы

некоторых полей класса, типы параметров и возвращаемых значений

методов класса. В некоторый момент формальные имена типов будут

заменены фактическими параметрами, представляющими уже конкрет-

ные типы – имена встроенных классов, классов библиотеки FCL, клас-

сов, определенных пользователем.

В языке C# универсальными могут быть как классы, так и другие

пользовательские типы: интерфейсы, структуры, делегаты, события.

Класс с универсальными методами

Специальным частным случаем универсального класса является

класс, не объявляющий сам параметров, но разрешающий делать это

своим методам. Рассмотрим универсальность этого частного случая.

Вот как выглядит класс, содержащий универсальный метод swap: class Change {

static public void Swap<T>(ref T x1, ref T x2){

T temp;

temp = x1; x1 = x2; x2 = temp;

Page 110: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

110

}

}

Как видно, сам класс в данном случае не имеет параметров, за-

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

swap, имеющий родовой параметр типа T. Этому типу принадлежат па-

раметры метода и локальная переменная temp. Всякий раз при вызове

метода ему, наряду с фактическими параметрами, будет передаваться и

фактический тип, заменяющий тип T в описании метода.

Рассмотрим тестирующий метод из класса Testing, в котором

используется вызов метода swap для различных типов переменных: public void TestSwap(){

int x1 = 5, x2 = 7;

Console.WriteLine

("до обмена: x1={0}, x2={1}",x1, x2);

Change.Swap<int>(ref x1, ref x2);

Console.WriteLine

("после обмена: x1={0}, x2={1}", x1, x2);

Person pers1 = new Person("Савлов", 25, 1500);

Person pers2 = new Person("Павлов", 35, 2100);

Console.WriteLine("до обмена: ");

pers1.PrintPerson(); pers2.PrintPerson();

Change.Swap<Person>(ref pers1, ref pers2);

Console.WriteLine("после обмена:");

pers1.PrintPerson(); pers2.PrintPerson();

}

Обратите внимание на строки, осуществляющие вызов метода: Change.Swap<int>(ref x1, ref x2);

Change.Swap<Person>(ref pers1, ref pers2);

В момент вызова методу передаются фактические типы и фактические

параметры. В данном примере в качестве фактических типов использо-

вались встроенный тип int и тип Person, определенный пользовате-

лем.

Универсальность потребовала введения в библиотеку FCL соот-

ветствующих классов, интерфейсов, делегатов и методов классов, обла-

дающих этим свойством. В частности в библиотеке FCL создано про-

странство имен System.Collections.Generic, в которое включены

обобщенные классы коллекций.

7.4. Обобщенные классы коллекций Коллекции типа ArrayList, Queue и Stack при работе с храни-

мыми объектами используют базовый класс Object, что позволяет им

работать с объектами любого типа. Однако, для работы с объектами,

Page 111: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

111

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

их типов (кастинг) с базового типа Object в требуемый тип. Это не

только утомительная и приводящая к ошибкам работа, но такие опера-

ции также влияют на эффективность работы программы.

Используя обобщенные классы можно создавать строго типизиро-

ванные коллекции для любого класса, в том числе и пользовательского.

Это упрощает разработку программ и гарантирует использование нуж-

ного типа, а также может улучшить эффективность их выполнения.

Обобщенные классы предоставляют все преимущества строго ти-

пизированных коллекций и могут работать с любыми используемыми

типами. В табл. 7.1 перечислены наиболее используемые классы обоб-

щенных коллекций и соответствующие им коллекций обычных типов.

Таблица 7.1.

Некоторые обобщенные классы коллекций

Обобщенный класс Соответствующие не обобщенные коллекции List<T> ArrayList

Dictionary<T,U> Hashtable, StringDictionary

SortedList<T,U> SortedList

Queue<T> Queue

Stack<T> Stack

В показанном ниже примере создается обобщенная коллекция

SortedList<T,U> используя строки (string) в качестве ключей и це-

лые числа в качестве значений: SortedList<string, int> sl = new

SortedList<string, int>();

sl.Add("One", 1);

sl.Add("Two", 2);

sl.Add("Three", 3);

foreach (int i in sl.Values)

Console.Write(i.ToString() + " ");

Результат выполнения примера: 1 3 2

Обобщенные коллекции можно также инициализировать при соз-

дании, как и обычные коллекции. Например: List < int > myInts =

new List < int > () { 5, 10, 15, 20, 25 };

В этом случае, все числа будут добавлены к коллекции, как если бы ис-

пользовался метод Add().

7.4.1. Использование пользовательских классов

Обобщенные коллекции также могут работать и с пользователь-

скими классами. Рассмотрим пример описания класса:

Page 112: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

112

public class Person {

string firstName, lastName;

public Person(string _firstName, string _lastName)

{ firstName = _firstName; lastName = _lastName; }

override public string ToString()

{ return firstName + " " + lastName; }

}

Можно использовать в обобщенном классе SortedList<T,U>

пользовательский тип, точно так же, как если бы использовался тип

int, как показано ниже: SortedList<string, Person> sl =

new SortedList<string,Person>();

sl.Add("Первый", new Person("Григорий", "Дубина"));

sl.Add("Второй", new Person("Володя", "Ходырев"));

sl.Add("Третий", new Person("Александр", "Луценко"));

foreach (Person p in sl.Values)

Console.Write(p.ToString() + ";");

Результат работы примера: Володя Ходырев;Григорий Дубина;Александр Луценко

7.4.2. Обобщенные коллекции Queue<T> и Stack<T>

Аналогично следующий пример показывает использование обоб-

щенных версий Queue и Stack с классом Person: Queue<Person> q = new Queue<Person>();

q.Enqueue(new Person("Mark", "Hanson"));

q.Enqueue(new Person("Kim", "Akers"));

q.Enqueue(new Person("Zsolt", "Ambrus"));

Console.WriteLine("Queue demonstration:");

for (int i = 1; i <= 3; i++)

Console.WriteLine(q.Dequeue().ToString());

Stack<Person> s = new Stack<Person>();

s.Push(new Person("Mark", "Hanson"));

s.Push(new Person("Kim", "Akers"));

s.Push(new Person("Zsolt", "Ambrus"));

Console.WriteLine("Stack demonstration:");

for (int i = 1; i <= 3; i++)

Console.WriteLine(s.Pop().ToString());

7.4.3. Обобщенная коллекция List<T>

Некоторые методы обобщенных коллекций могут потребовать

реализацию специфических интерфейсов в описании пользовательского

типа. Например, вызов метода List.Sort() требует, чтобы в пользо-

вательском классе был реализован интерфейс IComparable. Показан-

ный ниже пример расширяет описание класса Person, чтобы он под-

Page 113: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

113

держивал интерфейс IComparable и включал метод CompareTo, чтобы

можно было сортировать элементы обобщенной коллекции List<T> с

помощью имени и фамилии человека: public class Person : IComparable {

string firstName, lastName;

public int CompareTo(object obj) {

Person otherPerson = (Person)obj;

if (this.lastName != otherPerson.lastName)

return this.lastName.CompareTo

(otherPerson.lastName);

else

return this.firstName.CompareTo

(otherPerson.firstName);

}

public Person(string _firstName, string _lastName){

firstName = _firstName; lastName = _lastName;

}

override public string ToString() {

return firstName + " " + lastName;

}

}

После добавления интерфейса IComparable к классу Person,

становится возможным сортировать его объекты в обобщенной коллек-

ции List<T>, как показано ниже: List<Person> group = new List<Person>();

group.Add(new Person("Григорий", "Дубина"));

group.Add(new Person("Иван", "Ходырев"));

group.Add(new Person("Александр", "Луценко"));

group.Sort();

foreach (Person p in group)

Console.Write(p.ToString() + ";");

При реализации интерфейса IComparable, можно также исполь-

зовать объекты класса Person в качестве ключей в обобщенной кол-

лекции SortedList<T,U>.

7.5. Некоторые часто используемые классы FCL

7.5.1. Класс массивов Array

Все массивы языка C# являются экземплярами класса Array из

библиотеки FCL. Например: int[] ar1 = new int[5];

double[] ar2 ={5.5, 6.6, 7.7};

int[,] ar3 = new Int32[3,4];

Page 114: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

114

Все переменные ar1, ar2 и ar3 принадлежат к разным классам.

Переменная ar1 принадлежит к классу int[] – одномерному массиву

значений типа int, ar2 – double[] – одномерному массиву значений

типа double, ar3 – двумерному массиву значений типа int.

У всех классов, являющихся массивами, много общего, поскольку

все они являются потомками класса System.Array. Класс System.Array

наследует несколько интерфейсов: ICloneable, IList,

ICollection, IEnumerable, и поэтому должен реализовать все их ме-

тоды и свойства. Помимо наследования свойств и методов класса

Object и вышеперечисленных интерфейсов, класс Array имеет до-

вольно большое число собственных методов и свойств.

В состав класса Array входят как статические (вызываемые с по-

мощью названия класса), так и обычные (вызываемые с помощью ссыл-

ки на объект класса) методы. Основные обычные методы класса Array

приведены в табл. 7.2.

Таблица 7.2.

Основные нестатические методы класса Array

Свойство Описание

Length число элементов массива;

Rank размерность массива;

Метод Описание

min() получить минимальный элемент массива;

max() получить максимальный элемент массива;

Sum сумма элементов массива;

GetLength() получение числа элементов массива по заданному измерению.

Статические методы класса Array позволяют решать самые раз-

нообразные задачи. В табл. 7.3 приведена сводка основных статических

методов класса Array.

Таблица 7.3.

Основные статические методы класса Array

Метод Описание

BinarySearch() двоичный поиск заданного элемента в массиве

Clear() выполняет начальную инициализацию элементов. В зави-

симости от типа элементов устанавливает значение 0 для

арифметического типа, false – для логического типа,

null для ссылок, " " – для строк;

Page 115: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

115

Copy() копирование части или всего массива в другой массив;

описание и примеры даны в тексте;

IndexOf() индекс первого вхождения образца в массив;

LastIndexOf() индекс последнего вхождения образца в массив;

Reverse() перестановка элементов массива в обратном порядке;

Sort() сортировка элементов массива.

Все методы перегружены и имеют несколько вариантов. Боль-

шинство из этих методов применимо только к одномерным массивам. В

ниже приведенном примере показано использование различных методов

класса Array. public void TestCollection(){

//операции над массивами

int[] arr = new int[5] { 5, 1, 3, 2, 4 };

//сортировка, поиск и обращение элементов

int first = Array.IndexOf(arr, 2);

Console.WriteLine("Первое вхождений 2 - {0}",first);

int last = Array.LastIndexOf(arr, 2);

Console.WriteLine("Последнее вхождений 2 - {0}", last);

Array.Reverse(arr);

Console.WriteLine("Перевернутый массив arr:");

PrintArray(arr);

//Копирование массивов

int[] arr1 = new int[5];

Array.Copy(arr, arr1, arr1.Length);

//быстрая сортировка

Array.Sort(arr1);

Console.WriteLine("Отсортированный массив arr1:");

PrintArray(arr1);

}

public static void PrintArray(Array A){

foreach (object item in A )

Console.Write(" {0}", item);

Console.WriteLine();

}//PrintArray

Результат выполнения данного этих методов будет следующим: Первое вхождений 2 – 3

Последнее вхождений 2 – 3

Перевернутый массив arr:

4 2 3 1 5

Отсортированный массив arr1:

1 2 3 4 5

Page 116: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

116

7.5.2. Класс строк String

Основным типом при работе со строками в C# является тип string,

задающий строки переменной длины. Над строками – объектами этого

класса – определен широкий набор операций, соответствующий совре-

менному представлению о том, как должен быть устроен строковый

тип.

Объявление строк и конструкторы класса string

Объекты класса String объявляются как все прочие объекты

простых типов – с явной или отложенной инициализацией, с явным или

неявным вызовом конструктора класса. Чаще всего, при объявлении

строковой переменной конструктор явно не вызывается, а инициализа-

ция задается строковой константой. Но у класса String достаточно

много конструкторов. Они позволяют построить строку с помощью:

символа, повторенного заданное число раз;

массива символов char[];

части массива символов.

Операции над строками

Над строками определены следующие операции:

присваивание (=);

проверка эквивалентности (==) и (!=);

конкатенация или сцепление строк (+);

получение символа по индексу ([]).

Поскольку string – это ссылочный тип, то в результате присваи-

вания создается ссылка на константную строку, хранимую в "куче". С

одной и той же строковой константой в "куче" может быть связано не-

сколько переменных строкового типа. Но эти переменные не являются

псевдонимами – разными именами одного и того же объекта. Дело в

том, что строковые константы в "куче" не изменяются, поэтому, когда

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

константным объектом в "куче". Остальные переменные сохраняют

свои связи. Для программиста это означает, что семантика присваива-

ния строк аналогична семантике значащего присваивания.

В отличие от других ссылочных типов операции, проверяющие

эквивалентность, сравнивают значения строк, а не ссылки. Эти опера-

ции выполняются как над значащими типами.

Бинарная операция "+" сцепляет две строки, приписывая вторую

строку к концу первой.

Возможность взятия индекса при работе со строками дает воз-

можность работать со строкой, как с массивом и получать по индексу

Page 117: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

117

каждый ее символ. Символы строки имеет тип char и доступны только

для чтения, но не для записи. Ниже показан пример, в котором над

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

public void TestOpers()

{ //операции над строками

string s1 ="ABC", s2 ="CDE";

string s3 = s1+s2;

bool b1 = (s1==s2);

char ch1 = s1[0], ch2=s2[0];

Console.WriteLine("s1={0}, s2={1}, b1={2}," +

"ch1={3}, ch2={4}", s1,s2,b1,ch1,ch2);

s2 = s1;

b1 = (s1!=s2);

ch2 = s2[0];

Console.WriteLine("s1={0}, s2={1}, b1={2}," +

"ch1={3}, ch2={4}", s1,s2,b1,ch1,ch2);

//Неизменяемые значения

s1= "Zenon";

//s1[0]='L';

}

Таблица 7.4.

Основные статические методы класса String

Метод Описание

Compare сравнение двух строк; можно сравнивать как строки, так и

подстроки; можно учитывать или не учитывать регистр, и

т.п.;

CompareOrdinal сравнение двух строк; сравниваются коды символов;

Concat конкатенация строк; метод перегружен, допускает

сцепление произвольного числа строк;

Copy создание копии строки;

Format выполнение форматирования в соответствии с заданными

спецификациями формата, аналогично методу

Console.Write() с форматом;

Join соединение массива строк в единую строку; при

конкатенации между элементами массива вставляются

разделители.

Методы Join и Split

Методы Join и Split выполняют над строкой текста взаимно

обратные преобразования. Обычный метод Split позволяет осущест-

Page 118: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

118

вить разбор текста на элементы. Статический метод Join выполняет

обратную операцию, собирая строку из элементов.

Заданный строкой текст зачастую представляет собой совокуп-

ность некоторых элементов (предложений, слов), которые разделены

специальными символами (например, пробелами, запятыми, и т.п.). Ме-

тоды Split и Join облегчают работу с такими строками.

Наиболее часто используемая реализация метода Split имеет

следующий вид: public string[] Split(params char[])

На вход методу Split передается один или несколько символов char[],

интерпретируемых как разделители. Объект string, вызвавший метод,

разделяется на подстроки, ограниченные этими разделителями. Из этих

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

Статический метод Join имеет следующий вид: public static string Join(string delim, string[] items)

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

(конкатенацией) элементов массива items, между которыми вставляет-

ся строка разделителей delim. Как правило, строка delim состоит из

одного символа, который и разделяет в результирующей строке элемен-

ты массива items. Рассмотрим примеры применения этих методов. В

первом из них строка представляет сложноподчиненное предложение,

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

разделяется на слова. Затем производится обратная сборка разобранного

текста. Вот код соответствующего метода: string txt = "раз,два,три";

Console.WriteLine("txt={0}", txt);

Console.WriteLine("Разделение текста на слова:");

string[] Words;

Words = txt.Split(',');

for (int i = 0; i < Words.Length; i++)

Console.WriteLine("Words[{0}]= {1}",i, Words[i]);

string txtjoin = string.Join("; ", Words);

Console.WriteLine("txtjoin={0}", txtjoin);

Результат выполнения данного кода будет следующим: txt=раз,два,три

Разделение текста на слова:

Words[0]= раз

Words[1]= два

Words[2]= три

txtjoin=раз; два; три;

Page 119: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

119

Нестатические методы класса String

Методы класса String позволяют выполнять вставку, удаление,

замену, поиск вхождения подстроки в строку. Наиболее часто исполь-

зуемые методы при работе со строками приведены в табл. 7.5. Следует

помнить, что класс String является неизменяемым. Поэтому

Replace(), Insert() и другие методы представляют собой функции,

возвращающие новую строку в качестве результата, а не меняют строку,

для которой был вызван метод.

Таблица 7.5.

Основные свойства и методы класса String

Метод Описание

Insert() вставка подстроки в заданную позицию;

Remove() удаление подстроку в заданной позиции;

Replace() замена подстроки в заданной позиции на новую под-

строку;

Substring() выделение подстроки начиная с заданной позиции;

IndexOf,

IndexOfAny,

LastIndexOf,

LastIndexOfAny

определение индексов первого и последнего вхожде-

ния заданной подстроки или любого символа из задан-

ного набора;

StartsWith,

EndsWith возврат true или false, в зависимости от того, на-

чинается или заканчивается строка заданной подстро-

кой;

PadLeft, PadRight вставка заданного числа пробелов в начале и в конце

строки;

Trim, TrimStart,

TrimEnd

удаление пробелов в начале и в конце строки, или

только с одного ее конца;

ToCharArray преобразование строки в массив символов.

8. Графический интерфейс приложений Любое приложение всегда имеет набор средств для взаимодейст-

вия с пользователем – интерфейс пользователя. С помощью интерфейса

приложения пользователь может описать решаемую задачу, выполнить

ее решение, сохранить полученные результаты на внешнем устройстве.

Ранее в пособии при рассмотрении примеров использовался консоль-

ный интерфейс (Console User Interface – CUI).

В данной главе будет рассмотрены вопросы разработки графиче-

ского интерфейса (Graphic User Interface – GUI). Приложения, исполь-

зующие графический интерфейс называются Windows приложениями

Page 120: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

120

(или Windows Forms Application) и для взаимодействия с пользователя-

ми они используют не класс Console, а класс Form. Так как в платфор-

ме .Net все основано на объектно-ориентированном подходе, то и гра-

фический интерфейс реализован в виде набора специальных классов

FCL из пространства имен System.Windows.Forms.

8.1. Описание графического интерфейса Общая схема приложений разрабатываемых для ОС Windows по-

казана на рис. 8.1.

Рис. 8.1. Структура типичного приложения.

В соответствие с данной схемой программа состоит из классов

(типов), которые условно разделены на классы, реализующие взаимо-

действие приложения с пользователем (интерфейс пользователя), и

классы, моделирующие предметную область решаемой задачи. Кроме

собственных классов приложение использует классы библиотеки плат-

формы FCL. Результаты работы приложения обычно сохраняются на

внешних устройствах, в файлах или базах данных.

8.1.1. Описание графического интерфейса ОС Windows

Графический интерфейс пользователя реализует и поддерживает

операционная система. Основным понятием GUI является «окно» (win-

dow), схема которого показана на рис. 8.2. На экране окна представляют

собой некоторые участки (обычно прямоугольные) в которых выполня-

ется ввод и вывод информации и где могут размещаться другие окна

(дочерние), с помощью которых выполняется управление программой.

Созданием и управлением окнами занимается сама операционная

система, которая первой получает информацию от всех устройств ком-

пьютера (нажатие клавиши, движение «мыши», поступление данных по

Page 121: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

121

сети). Обо всех этих события ОС рассылает сообщения приложениям,

окна которых являются активными в текущий момент.

Рис. 8.2. Основные элементы окна.

Для этого во всех Windows приложениях, работающих с окнами,

операционная система, создает специальные коллекции типа FIFO (оче-

реди сообщений), в которые она записывает все сообщения о событиях,

произошедших в компьютере, и связанных с окнами данного при-

ложения. Приложение постоянно следит за сообщениями, поступаю-

щими в эту очередь, и выполняет их обработку.

Окно приложения включает следующие основные части:

строка заголовка окна, в которой размещаются такие стандарт-

ные элементы управления окном, как: системное меню, появляю-

щееся при щелчке на иконке приложения в левой части строки;

три системные кнопки для свертывания окна, раскрытия окна на

весь экран и закрытия окна.

строка меню приложения – набор команд (пунктов меню), при

выборе которых приложение будет выполнять различные задачи;

строка меню может располагаться в разных частях окна, чаще все-

го находится в его верхней части;

инструментальные полосы (строки с наборами специальных до-

черних окон – элементов управления), с помощью которых также

можно вызывать выполнение различных задач приложения; инст-

рументальных полос в интерфейсе приложения может быть много

и они могут размещаться в разных частях окна;

клиентская область – часть окна, в которой можно размещать

Page 122: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

122

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

сование;

строка состояния, в которую выводятся сообщения информи-

рующие пользователя о ходе работы приложения.

Приложение обычно исполь-

зует большое количество окон. Все

окна можно разделить на группы:

основные окна, которые ис-

пользуются для представления

всего приложения; они вклю-

чают основные элементы ин-

терфейса и инициируют соз-

дание других окон;

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

предназначены для получения

информации и запуска на вы-

полнения разных вспомога-

тельных задач приложения;

элементы управления (con-

trol), это дочерние окна, ко-

торые используются для вы-

полнения элементарных опе-

раций по отображению ин-

формации (например, тексто-

вые окна – TextBox, окна со

списками строк – ListBox,

окна с изображениями

PictureBox) или для получе-

ния некоторых команд поль-

зователя (например, нажатия

кнопок «мыши», кнопок But-

ton, пунктов меню).

Платформа .Net предоставляет набор объектно-ориентированных

средств для удобной и простой реализации всех частей графического

интерфейса.

8.1.2. Реализация графического интерфейса в .Net

В платформе .Net для реализации графического интерфейса ис-

пользуются разные технологии, такие как: Windows Forms и Windows

Presentation Foundation (WPF). В пособии рассматривается только тех-

Рис. 8.3. Иерархия классов элемен-

тов управления.

Page 123: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

123

нология Windows Forms. Данная технология включает множество типов

(классы, структуры, перечисления, делегаты), которые объединены в

два основных пространства имен System.Windows.Forms (для реализа-

ции элементов интерфейса) и System.Drawing (для рисования в клиент-

ской области).

Основными элементами графического интерфейса являются спе-

циальные классы, называемые элементами управления (ЭУ), которые

обладают двумя особенностями:

1. реализуют работу с различными типами окон ОС Windows;

2. поддерживают работу в двух режимах:

режим проектирования (design mode), в котором с ними работает

среда разработки;

режим выполнения (run mode), в котором выполняется взаимодей-

ствие пользователей с ЭУ.

Базовым классом для всех элементов управления является класс

Control, реализующий самую базовую функциональность. Класс Con-

trol задает важные свойства, методы и события, наследуемые всеми

его потомками. Все классы элементов управления (ЭУ) являются на-

следниками класса Control. Базовый класс Control содержит доста-

точно большой интерфейс (79 свойств, 56 методов, 67 событий), кото-

рый доступен во всех производных классах. Так как основные элементы

данного класса используются при работе со всеми элементами управле-

ния, то они приведены в табл. 8.1.

Таблица 8.1.

Основные элементы базового класса Control

Свойства Описание

Name имя элемента для работы с ЭУ в системе разработки;

Left,Top,

Right,Bottom

координаты точек левого верхнего угла и правого нижнего

угла (по умолчанию в пикселях относительно окна родите-

ля);

Location задать или получить координаты левого верхнего угла в ви-

де объекта структуры Point (по умолчанию в пикселях);

Height,Width высота и ширина окна (по умолчанию в пикселях);

Size размер окна в виде объекта структуры Size;

Controls коллекция дочерних элементов управления, которые будут

отображаться в текущем окне; поддерживает интерфейс IList;

ContextMenu ссылка на контекстное меню;

Cursor ссылка на курсор;

Page 124: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

124

Parent ссылка на родительское окно (если оно есть);

Text заголовок окна;

Font используемый шрифт;

TabIndex порядок перехода между ЭУ при нажатии клавиши Tab;

Dock,

DockStyle

может ли элемент управления прикрепляться к сторонам

родительского окна (true, false); свойство DockStyle зада-

ет стороны, к которым должен прикрепляться ЭУ: Top,

Bottom , Right , Left , Fill или None;

Anchor

AnchorStyle

связывание со стороной родительского окна; отличается от

причаливания Dock тем, что не прикрепляется к стороне, а

находится на фиксированном расстоянии от нее; значения

AnchorStyle такие же, как и у DockStyle;

BackColor,

ForeColor

фоновый цвет и цвет рисования;

Методы Описание

Show() отображение окна на экране;

Hide() срытие окна;

CreateGrafics создание объекта класса Graphics для рисования в окне;

Update() обновление содержания окна;

Invalidate() объявление содержание окна испорченным, что заставляет

ОС отправить событие WM_PAINT о необходимости пере-

рисовки содержания окна;

События Описание

Paint событие о перерисовке пользовательской области окна;

GetFocuse,

LostFocuse

события о получении и потере окном фокуса ввода (данных

с клавиатуры);

Click событие о щелчке левой клавишей «мыши» в области окна

MouseMove событие о малом перемещении курсора «мыши» в окне;

KeyDown,

KeyUp сообщения о нажатии и освобождении клавиши клавиатуры

KeyPress сообщение о вводе символа с помощью клавиатуры;

8.1.3. Система координат и единиц измерения

Система координат для задания положения и размеров ЭУ, осно-

вывается на координатах устройства и основной единицей измерения

при рисовании является пиксель (pixel) – наименьший логический эле-

мент двумерного цифрового изображения в растровой графике. Растро-

вое компьютерное изображение состоит из пикселей, расположенных по

строкам и столбцам. Точки экрана описываются парами координат (x,

Page 125: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

125

y), где координата x увеличивается слева на право, а координата y уве-

личивается сверху вниз. Размещение начала координат зависит от того,

задаются ли экранные координаты (координаты экрана) или клиентские

координаты (координаты клиентской области окна родителя). Экранные

координаты задают позицию окна на экране дисплея и их началом явля-

ется верхний – левый угол экрана. Полное положение окна задается

структурой Rectangle, содержащей экранные координаты двух точек,

определяющих верхний – левый и правый – нижний углы окна. Клиент-

ские координаты задают положение точки в клиентской области окна.

Их начальной точкой является верхний – левый угол клиентской облас-

ти (рис. 8.2) формы или ЭУ.

Для работы с координатами и размерами ЭУ используются опре-

деленные в библиотеке FCL структуры Point, PointF, Size, SizeF и

Rectangle активно используемые при работе с графическими объекта-

ми. Первые четыре структуры имеют два открытых поля X и Y

(Height и Width), задающие для точек – структур Point и PointF –

координаты, целочисленные или в форме с плавающей точкой. Для раз-

меров – структур Size и SizeF – они задают высоту и ширину, цело-

численными значениями или в форме с плавающей точкой. Структуры

Point и Size позволяют задать прямоугольную область – структуру

Rectangle. Конструктору прямоугольника можно передать в качестве

аргументов две структуры – точку, задающую координаты левого

верхнего угла прямоугольника, и размер – высоту и ширину прямо-

угольника.

8.1.4. Стандартный тип обработчика события

В библиотеке классов FCL все события описываются с помощью

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

параметрами и не возвращающую значение: public delegate void <имя_делегата>

(object sender, <тип_параметров> args);

Первый параметр делегата задает ссылку на объект, который ини-

циирует событие. Второй параметр args задает ссылку на параметры,

связанные с возникшим событием, передаваемые обработчику. Тип это-

го параметра должен задаваться классом описания параметров события

EventArgs, который содержится в .Net Framework или производным от

него классом (например, PaintEventArgs, MouseEventArgs и т.п.).

Если обработчику никаких дополнительных параметров не передается,

то следует просто указать класс EventArgs, передавая null в качестве

фактического параметра при включении события.

Page 126: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

126

8.1.5. Взаимодействие пользователя с приложением

Взаимодействие пользователя лучше всего описывается в виде

различных событий, которые инициируют ЭУ (на основе сообщений ОС

о действиях пользователя) и на которые отвечает (обрабатывает) при-

ложение. Наиболее часто используемыми событиями являются сле-

дующие:

Click – щелчок левой кнопки «мыши» в области окна;

DoubleClick – два щелчка левой кнопки «мыши» с интервалом

меньше некоторого заданного значения;

KeyDown – нажатие клавиши клавиатуры;

KeyPress – нажатие и отпускание клавиши, в результате которых

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

Validating – проверки введенных данных;

Paint – необходимо перерисовать клиентскую область.

События от устройства «мышь», такие, как Click , DoubleClick,

MouseDown, MouseUp, MouseEnter, MouseLeave и MouseHover, свя-

заны с различными действиями пользователей над областью ЭУ.

Для событий Click и DoubleClick передается параметр типа

EventArgs, а для событий MouseDown и MouseUp передается параметр

типа MouseEventArgs, который содержит такую полезную информа-

цию (свойства класса), как текущие координаты курсора в клиентской

области, описание нажатой кнопки, количество нажатий кнопки, коли-

чество щелчков при вращении колеса «мыши».

События клавиатуры работают аналогично: количество переда-

ваемой информации зависит от типа обрабатываемого события. Напри-

мер, для события KeyPress в метод обработки события передается па-

раметр KeyPressEventArgs, который содержит свойство KeyChar -

значение типа char, которое представляет символ нажатой клавиши.

Свойство Handled используется для определения того, было ли

обработано данное событие. Если свойству Handled задано значение

true, то данное событие не будет передаваться ОС для стандартной об-

работки. События KeyDown или KeyUp больше подходят для обработки,

если требуется получить больше информации о нажатой клавише, так

как они получают параметр KeyEventArgs. Параметр KeyEventArgs

включает свойства о том, какие клавиши Ctrl, Alt или Shift были

нажаты. Свойство KeyCode возвращает значение перечисления Keys,

которое указывает на виртуальный код нажатой клавиши. В отличие от

KeyPressEventArgs.KeyChar свойство KeyCode передает виртуаль-

Page 127: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

127

ный код любой нажатой клавиши клавиатуры, а не алфавитно-цифровой

символ клавиши.

Свойство KeyData возвращает значение перечисления Keys, а

также состояние дополнительных клавиш. Например, была ли нажата

клавиша Shift или Ctrl. Свойство KeyValue содержит целое значе-

ние перечисления Keys. Свойство Modifiers содержит значения Keys,

которые соответствуют кодам дополнительно нажатых клавиш. Если

было нажато несколько клавиш, то они объединяются с помощью опе-

рации OR. События, связанные с клавишей инициируются в следующем

порядке: 1) KeyDown; 2) KeyPress; 3) KeyUp.

События Validating, Validated, Enter, Leave, GotFocus и

LostFocus имеют отношение к получению ЭУ фокуса ввода (когда ЭУ

становится активным) или потери фокуса. Это происходит, когда поль-

зователь нажимает клавишу Tab для перехода к нужному ЭУ или выби-

рает этот элемент с помощью «мыши». Кажется, что события Enter,

Leave, GotFocus и LostFocus очень сходны по выполняемой работе.

События GotFocus и LostFocus являются событиями более низкого

уровня, которые связаны с WM_SETFOCUS и WM_KILLFOCUS сообщения-

ми ОС. Обычно лучше использовать события Enter и Leave. События

Validating и Validated возникают при проверке значения в ЭУ. Они

получают параметр типа CancelEventArgs. С его помощью можно

прервать следующие события, если задать свойству Cancel значение

true. Если разработчик задает собственный код проверки введенных

значений и проверка оказалась не успешной, то можно задать свойству

Cancel значение true и ЭУ не будет терять фокус ввода (не будет вы-

полняться переход к следующему ЭУ формы). Событие Validating

возникает в ходе проверки, а событие Validated возникает после вы-

полнения проверки. Эти события возникают в следующем порядке: 1)

Enter; 2) GotFocus; 3) Leave; 4) Validating; 5) Validated; 6)

LostFocus.

8.2. Пример простой программы с графическим интерфейсом Обычное Windows приложение содержит несколько форм, кото-

рые создаются с помощью объектов класса Form. Данный класс также

является ЭУ и наследуется от базового класса Control. Одни формы

приложения открываются в процессе работы, другие закрываются. В

каждый текущий момент на экране может быть открыта одна или не-

сколько форм, пользователь может работать с одной формой или пере-

ключаться по ходу работы с одной на другую.

Page 128: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

128

Форма, открываемая в методе Main() при вызове метода Run()

класса Application, называется главной формой приложения. Ее за-

крытие приводит к закрытию всех остальных форм и завершению Win-

dows приложения. Завершить приложение можно и программно, вызвав

статический метод Application.Exit(). Закрытие других форм не

приводит к завершению проекта. Обычно главная форма приложения

всегда открыта, в то время как остальные формы открываются и закры-

ваются (или скрываются).

Для создания формы лучше всего (что и делается в Visual Studio)

создать новый класс, производный от класса Form. В конструкторе

класса можно задать требуемые значения свойств данного класса и

включить в данную форму все требуемые объекты. В приведенном ни-

же примере создан класс MyForm. Объекты данного класса будут соот-

ветствовать окну, которое содержит нужные элементы управления и об-

рабатывает события, связанные с окном и элементами управления. using System;

using System.Windows.Forms;

namespace WindowsApp {

class Program{

static void Main(string[] args){

MyForm frm = new MyForm("Первое окно");

Application.Run(frm);

}

}

class MyForm : Form {

Button btn1 = new Button();

public MyForm(string s){

this.Text = s;

btn1.Top = 10; btn1.Left = 20;

btn1.Text = "Нажми";

Controls.Add(btn1);

btn1.Click +=new EventHandler(btn1_Click);

}

public void btn1_Click(object o, EventArgs ea){

MessageBox.Show("Привет, Мир!");

}

}

}

Как уже было сказано, для передачи сообщений из очереди при-

ложения в главную форму используется метод Application.Run(),

которому передается ссылка на созданный объект формы. Данный ме-

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

Page 129: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

129

прекращает свою работу только при поступлении в очередь сообщения

WM_QUIT, которое поступает при закрытии основного окна формы (на-

жатие кнопки ) или выборе команды меню для окончания работы с

приложением (например: Выход или Exit).

Для отображения элементов управления в форме необходимо соз-

дать соответствующие им объекты, задать нужные значения их свойст-

вам и добавить их в коллекцию формы Controls. Например: Button btn1 = new Button();

btn1.Top = 10; btn1.Left = 20; // задание расположения

btn1.Text = "Нажми"; // задание текстового заголовка

Controls.Add(btn1); // занесение в коллекцию элементов

Для интересующих пользователей событий, которые иницииру-

ются формой и содержащимися в ней элементами управления, нужно

создать обработчики событий и присвоить их значения соответствую-

щим событиям этих объектов.

Например, для обработки события нажатия описанной ранее

кнопки, описывается обработчик события, который присваивается пе-

ременной события класса: // описание обработчика события

public void btn1_Click(object o, EventArgs ea){

MessageBox.Show("Привет, Мир!");

}

// ...

// присвоение ссылки на данный метод переменной события

btn1.Click += new EventHandler(btn1_Click);

Результат работы данной программы показан на рис. 8.4.

Рис. 8.4. Результат работы программы WindowsApp.

Page 130: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

130

8.3. Класс форм Form Класс Form добавляет значительную функциональность базовому

классу Control. Клиентское Windows приложения может содержать

одну форму или множество форм. Эти формы могут быть приложения-

ми, основанными на Single Document Interface (SDI) или Multiple Docu-

ment Interface (MDI) интерфейсе. Класс Form является производным от

класса ContainerControl, который в свою очередь является произ-

водным от ScrollableControl (производный от класса Control). В

связи с этим, можно понять, что форма может быть контейнером для

других ЭУ и обладает возможностью выполнять скроллинг, когда кли-

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

также обладает многими другими свойствами, методами и событиями.

8.3.1. Создание и уничтожение формы

В ходе процесса создания и уничтожения формы – объекта класса

Form инициируется следующая последовательность событий: 1) Load;

2) Activated; 3) Deactivated; 4) Closing; 5) Closed.

Событие Load возникает после инициализации объекта, но перед

тем, как форма станет видимой на экране. При обработке данного собы-

тия можно считать, что все внутренние объекты формы уже созданы.

Событие Activated возникает, когда форма уже стала видимой и те-

кущей, но пользовательская область еще не отрисована. Событие Acti-

vated возникает, когда форма перестает быть активной (при переходе

пользователя к работе с другим приложением). Событие Closing воз-

никает в процессе закрытия формы и предоставляет возможность

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

качестве второго параметра объект типа CancelEventArgs, который

имеет свойство Cancel. Если этому свойству задать значение true, то

форма не будет закрываться. Событие Closed возникает после закры-

тия формы, позволяя освободить все выделенные ресурсы (закрыть

файлы, закрыть соединение с базой данных). Если в программе вызыва-

ется метод Application.Exit() и имеются открытые формы, то со-

бытия Closing и Closed возникать не будут.

8.3.2. Модальные и немодальные формы

При разработке графического интерфейса важным является поня-

тие модального и немодального окна. Модальное окно не позволяет, ес-

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

данного приложения. Выйти из модального окна можно, только закрыв

его. Немодальные окна допускают параллельную работу в разных окнах

приложения. Форма называется модальной или немодальной в зависи-

Page 131: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

131

мости от того, какое у нее окно. Тип открываемого окна зависит от того,

какой метод для открытия формы используется. Метод Show() откры-

вает форму как немодальную, а метод ShowDialog() – как модальную.

Название метода отражает основное назначение модальных форм – они

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

не завершится, покидать форму не разрешается.

Если переменная frm хранит ссылку на объект класса производ-

ного от Form, то после вызова метода frm.Show(), выполнение про-

граммы сразу переходит к следующему оператору. При вызове метода

frm.ShowDialog() переход к следующему оператору программы про-

изойдет только после закрытия формы frm. Следующий пример пока-

зывает создание формы (объект с именем MyFormClass) и ее отображе-

ние на экране: MyFormClass frm = new MyFormClass();

frm.Show();

Другим отличием этих методов является то, что метод Show()не

возвращает никакого значения при завершении работы, а метод Show-

Dialog()при завершении возвращает в качестве результата одно из

значений перечисления DialogResult. Перечисление DialogResult

является списком идентификаторов, поясняющих причину закрытия

диалога. К основным значениям этого перечисления относятся:

OK – работа с формой завершилась успешно (пользователь выпол-

нил требуемую задачу);

Cancel – работа с формой завершилась не успешно (пользователь

не выполнил требуемую задачу);

Yes – пользователь ответил утвердительно на заданный вопрос;

No – пользователь ответил отрицательно на заданный вопрос.

Для того, чтобы форма вернула значение DialogResult, ее свой-

ству DialogResult нужно задать одно из значений перечисления Di-

alogResult или должны быть заданы свойства DialogResult у кно-

пок, которые вызывают закрытие формы.

Предположим, что в приложении нужно ввести номер телефона

пользователя. Для этого создается форма, которая имеет текстовое поле

(TextBox) ввода и две кнопки (Button); на одной написано «Сохра-

нить», а на другой «Отменить». Тогда, если свойству DialogResult

кнопки с надписью «Сохранить» задать значение DialogResult.OK, а

свойству DialogResult кнопки с надписью «Отменить» задать значе-

ние DialogResult.Cancel, то если одну из них нажать, то форма ста-

новится невидимой (но не уничтожается) и в качестве результата воз-

Page 132: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

132

вращается одно из заданных значений. Отметим, что форма не уничто-

жается, а просто свойству Visible задается значение false. Это дела-

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

данные с помощью свойств класса формы. В результате описания свой-

ства данной диалоговой формы для номера телефона, родительская

форма может получить значение, а уже затем вызвать метод Close()

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

формы: class Phone : Form {

TextBox txtPN = new TextBox();

Button btnOK = new Button();

Button btnCancel = new Button();

public Phone() { // конструктор

Text = "Введите номер телефона.";

Size = new Size(180, 120);

txtPN.Location = new Point(10,10);

txtPN.Size = new Size(150,10);

btnOK.Location = new Point(10,40);

btnOK.Text = "Сохранить";

btnOK.DialogResult = DialogResult.OK;

btnCancel.Location = new Point(90,40);

btnCancel.Text = "Отменить";

btnCancel.DialogResult = DialogResult.Cancel;

Controls.AddRange(

new Control[] { txtPN, btnOK, btnCancel });

}

public string PhoneNumber { // свойство класса

get { return txtPN.Text; }

set { txtPN.Text = value; }

}

}

Отметим, что обработчики событий для кнопок не задаются, так

как для них заданы свойства DialogResult и

форма будет становиться невидимой после их

нажатия. В классе также описано единственное

добавленное свойство PhoneNumber. Ниже

показан пример вызова данного Phone диало-

га: string s;

Phone frm = new Phone(); //создаем экземпляр диалога

frm.ShowDialog(); // показываем диалог

if (frm.DialogResult == DialogResult.OK)

{ s = "Номер телефона " + frm.PhoneNumber; }

Page 133: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

133

else if (frm.DialogResult == DialogResult.Cancel)

{ s = "Номер телефона не введен.";}

frm.Close();

8.3.3. Многодокументный интерфейс Multiple Document Interface

Многодокументный интерфейс (Multiple Document Interface, MDI)

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

новной формы показывать много экземпляров одного или разных типов

форм, которые показывают разное содержание (документы). Например,

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

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

под документом понимается не только текст, но и информация любого

другого типа (изображение, таблицы данных, видео и т.п.). Используе-

мые дочерние формы не выходят за границы основной формы приложе-

ния.

Для организации MDI интерфейса следует в основной форме при-

ложения задать свойству IsMDIContaner значение true. А для того,

чтобы дочерние окна вели себя соответствующим образом (открывались

в родительском окне), у них нужно присвоить свойству MdiParent

ссылку на данную родительскую форму. Например, в методе основной

формы можно создать метод, который будет показывать дочерние фор-

мы: private void ShowMdiChild(Form childForm) {

childForm.MdiParent = this; // this – основная форма

childForm.Show();

}

В основной форме может быть открыто одновременно много до-

черних форм. Свойство формы ActiveMdiChild ссылается на окно, с

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

свойство можно получить все данные связанные с этой формой. Дочер-

ние формы можно упорядочивать, вызывая метод LayoutMdi(), кото-

рый в качестве параметра принимает значения перечисления Mdi-

Layout. Возможными значениями данного перечисления являются:

Cascade (упорядочит каскадом), TileHorizontal (упорядочить гори-

зонтально) и TileVertical (упорядочить вертикально).

В системе разработки Visual Studio имеется специальный вид

форм, у которого уже задано не только свойство IsMDIContaner =

true, но и созданы меню и инструментальная полоса для выполнения

стандартных операция с дочерними формами.

Page 134: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

134

8.3.2. Стандартные диалоги В библиотеке FCL имеется набор стандартных модальных форм,

позволяющие выполнять стандартные действия:

OpenFileDialog – диалог выбора и открытия файла для считы-

вания данных;

SaveFileDialog – диалог выбора и открытия файла для записыи

данных;

PrintDialog – диалог вывода на печать;

PrintPreviewDialog – диалог предварительный просмотр ре-

зультата печати;

ColorDialog – диалог выбора цвета;

FontDialog – диалог выбора шрифта;

FolderBrowserDialog – диалог работы с каталогами.

Все эти формы поддерживаются стандартными окнами операци-

онной системы. Логика работы с этими формами однотипная и состоит

в следующем:

описание и создание объекта нужного класса форм;

задание свойств данного объекта для его визуального представле-

ния и логики работы;

вызов у данного объекта метода ShowDialog() и сравнение по-

лученного результата со значением DialogResult.OK;

если завершение успешное, то значения нужных свойств диалога

сохраняются в переменных приложения.

Например, диалог OpenFileDialog имеет следующие основные

свойства:

Title – заголовок диалоговой формы;

Filter – “описание1 | фильтр1 | описание2 | фильтр2”; например: "txt files (*.txt) | *.txt | All files (*.*) | *.*"

FilterIndex – индекс фильтра, используемый по умолчанию;

InitialDirectory – начальная папка для просмотра;

FileName – имя выбранного файла (результат выбора).

Ниже показан пример использования диалога OpenFileDialog для

считывания данных из выбранного файла: string input;

OpenFileDialog oFileDlg = new OpenFileDialog();

oFileDlg.InitialDirectory = "c:\\";

oFileDlg.Filter = "txt files (*.txt)|*.txt|All files

(*.*)|*.*";

if (oFileDlg.ShowDialog() == DialogResult.OK){

// чтение данных из выбранного файла

Page 135: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

135

StreamReader sr = File.OpenText(oFileDlg.FileName);

input = sr.ReadToEnd();

}

Вид созданной диалоговой формы показан на рис. 8.5.

Кроме перечисленных стандартных диалогов также часто исполь-

зуется простое модальное окно сообщений (message box), которое мож-

но показать на экране с помощью перегруженного метода Show() (12

вариантов метода) статического класса Sys-

tem.Windows.Forms.MessageBox. Как и обычное модальная форма,

данное окно сообщений возвращает результат типа DialogResult.

Рис. 8.5. Диалоговое окно открытия файла.

Например, один из методов Show имеет следующую сигнатуру: DialogResult Show(string, string, MessageBoxButtons,

MessageBoxIcon, MessageBoxDefaultButton), где

перечисление MessageBoxButtons – это комбинации кнопок на

форме, таких как Ok, OkCancel, RetryCancel, Yes, No, YesNo-

Cancel, AbortRetryIgnore.

перечисление MessageBoxIcon – это иконка, выводимая в

форме, такая как: None (нет), Hand (рука), Question (?), Excla-

mation (!), Asterisk (*), Stop (крест), Error (крест), Warning

(!), Information (i);

Page 136: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

136

перечисление MessageBoxDefaultButton – номер кнопки, ко-

торый будет выбран по умолчанию; допустимые значения: But-

ton1, Button2, Button3.

Ниже приведен пример вызова окна сообщений с данным набором

параметров: MessageBox.Show("Продолжать выполнение программы?",

"Редактор текста", MessageBoxButtons.YesNoCancel,

MessageBoxIcon.Question,MessageBoxDefaultButton.Button2);

Результат выполнения данного метода показан на рис. 8.6.

Рис. 8.6. Простое диалоговое окно сообщений.

8.4. Основные классы элементов управления В пространстве имен System.Windows.Forms имеется множест-

во классов реализующих элементы управления. На панели Toolbar

можно видеть большинство типов таких ЭУ. Рассмотрим кратко основ-

ные ЭУ.

8.4.1. Класс текстовых меток Label

Объекты данного класса обычно используются для вывода на

форме некоторого поясняющего текста. Такой текст может быть связан

с другими ЭУ или пояснять текущее состояние

приложения. Обычно текст выводится вместе с тек-

стовыми полями (TextBox), для пояснения того,

какие входные данные должен ввести пользователь

в текстовое поле. Пользователь не может редактировать текстовое зна-

чение Label, которое содержится в свойстве Text. Однако значение

этого свойства можно изменить в коде программы. Если задать свойства

UseMnemonic значение true, то знак амперсанда (&) в свойстве Text

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

(она будет подчеркнута). При нажатии клавиш ALT в комбинации с на-

значенным символом приведет к тому, что фокус будет перенесен на

ЭУ, стоящим следующим в последовательности переходов за ЭУ

Label. В связи с этим нужно правильно устанавливать порядок переда-

чи фокуса между ЭУ формы.

Page 137: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

137

8.4.2. Класс кнопок Button

Класс Button соответствует простым командным кнопкам. Свой-

ство Text задает надпись на кнопке.

Наиболее часто для кнопок задаются

обработчики события Click. Ниже

показан пример, реализующий обработчик события Click, который по-

казывает простое диалоговое окно сообщений (message box), которое

содержит название кнопки: private void btnTest_Click(object o, EventArgs e){

MessageBox.Show("Нажата кнопка " +

(Button)(o).Name);

}

С помощью метода PerformClick() можно имитировать собы-

тие Click для кнопки без реального щелчка на кнопке.

8.4.3. Класс флажков CheckBox

Класс флажков CheckBox также является производным от класса

ButtonBase и представляет собой флажки, которые принимают два ос-

новных состояния, которые задаются значением перечисления Check-

State: Checked – флажок выбран, имеет специальную отметку; Un-

checked – флажок не выбран.

Основное событие данного ЭУ это CheckedChanged, которое

возникает при смене пользователем состояния ЭУ. Обработка этого со-

бытия может быть полезной для задания других значений, на основе но-

вого состояния CheckBox. Например: private void checkBoxChanged(object o, EventArgs e){

CheckBox checkBox = (CheckBox)o;

MessageBox.Show("Новое значение " + checkBox.Name +

" = " + checkBox.Checked.ToString());

}

В данном примере, при изменении состояния флажка checkBox

показывается сообщение с именем ЭУ, состояние которого изменилось

вместе с названием нового состояния.

8.4.4. Класс переключателей RadioButton

Объекты класса переключателей обычно исполь-

зуются в группах. Они позволяют пользователю выбрать

только одну из доступных опций. Когда имеется набор

ЭУ RadioButton в контейнере, то только один ЭУ будет

в выбранном состоянии. Например, есть возможность

выбора одного из трех цветов: Красный, Зеленый или

Синий, тогда если выбран цвет Красный, то при выборе

Рис. 8.7. Командная кнопка.

Page 138: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

138

пользователем другого цвета (например, Синий), ранее сделанный вы-

бор (в данном случае Красный) автоматически сбросится.

Событие CheckedChanged возникает при выборе пользователем

одного из переключателей. У выбранного переключателя свойство

Checked будет равно true (у невыбранных – false).

В качестве контейнера для переключателей можно использовать

объекты класса контейнера GroupBox. В коллекцию Controls данного

ЭУ должны быть добавлены объекты класса RadioButton. Например: groupBox1.Controls.Add(this.radioButton1);

В этом случае свойства Top и Left задают положение ЭУ относительно

контейнера GroupBox.

8.4.5. Классы текстовых полей TextBox, RichTextBox и MaskedTextBox

Все классы TextBox, RichTextBox и MaskedTextBox являются

производными от класса TextBoxBase. Основные элементы этих клас-

сов приведены в табл. 8.2.

Таблица 8.2.

Основные элементы текстовых полей

Элемент Описание MultiLine если задано значение true, то TextBox может ото-

бражать текст более чем в одной строке; Lines массив строк, хранящихся в элементе; Text хранящийся текст в виде одной строки; TextLength общая длина текста в виде одной строки; MaxLength максимально возможная длина текста; SelectedText выбранный пользователем текст в ЭУ (данный текст

подсвечивается, когда ЭУ получает фокус); SelectionLength длина выбранного пользователем текста; SelectionStart номер символа, с которого начинается выбранный текст AcceptsReturn если задано значение true, то нажатие Enter вызовет соз-

дание новой строки, а не переход к кнопке по умолча-

нию; CharacterCasing задает используемый регистр; возможные значения пе-

речисления CharacterCasing: Normal (обычный

текст), Lower (маленькие буквы), или Upper (большие

буква); PasswordChar символ, который используется для отображения вместо

букв для ввода паролей.

Класс RichTextBox позволяет хранить и показывать текст в

формате Rich Text Format (RTF), который поддерживает специальные

возможности форматирования текста. В этом классе имеются такие до-

Page 139: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

139

полнительные свойства, как: SelectionFont – используемый шрифт и

SelectionColor – используемый цвет. Текст данного ЭУ можно по-

лучить с помощью свойства Text (просто текст, без форматирования)

или свойства Rtf (текст с форматированием). Метод LoadFile() по-

зволяет ввести текст из заданного файла. Метод SaveFile() сохраняет

текст из ЭУ в заданный текстовый файл. Если такой файл уже сущест-

вует, то он переписывается.

Класс MaskedTextBox позволяет ограничить вводимые пользова-

телем текстовые строки, а также автоматически форматировать вводи-

мые данные. Свойство Mask содержит строку шаблона вводимой стро-

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

формат вводимых числовых данных (например: 0 – цифа; 9 – цифра или

пробел; # – цифра, пробел, + или –; L – буква; ? – буква или пробел). А

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

сто отсутствующих символов (по умолчанию – „_‟).

Свойство Text возвращает текст, введенный в данный ЭУ. Вид

полученного текста зависит от свойства TextMaskFormat, которое мо-

жет получать одно из значений перечисления MaskFormat:

IncludeLiterals – включать символы из шаблона;

IncludePrompt – включать заданные символы (prompt) для от-

сутствующих символов.

Если, например, шаблон задает телефонный номер, то строка

Mask возможно будет следующей “(999)000-0000”. И эти символы

будут включены в строку Text, если свойству IncludeLiteral назна-

чено значение true.

8.4.6. Классы списков ListBox, ComboBox и CheckedListBox

Все эти классы являются производными от класса ListControl.

Данный класс предоставляет некоторую базовую функциональность

управления списками элементов произвольного типа. Внешний вид спи-

сочных ЭУ показан рис. 8.8.

Рис. 8.8. Элементы управления ListBox, ComboBox и CheckedListBox

Одними из наиболее важных возможностей списочных ЭУ явля-

ются добавление данных к списку и выбор данных из списка. Какой

Page 140: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

140

список использовать зависит от того, как список будет использоваться и

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

ность множественного выбора элементов списка или если пользователю

нужно видеть несколько элементов сразу, то лучше использовать ЭУ

ListBox. Если выбираться будет только один элемент списка, то можно

использовать ComboBox. Классы списочных ЭУ содержит коллекцию

объектов, к которой можно обратиться с помощью свойства Items. С

этой коллекцией можно работать, как с любой другой коллекцией (см.

раздел 7.1). Так как коллекции хранят объекты типа object, то любой

объект, встроенного или пользовательского типа, может быть включен в

список. Доступ к элементам списка выполняется с помощью операции

индексации Items[i], однако возвращаемый объект будет иметь тип

object и для работы с ним необходимо выполнять явное преобразование

к требуемому типу объектов. Основные элементы списочных классов

приведены в табл. 8.3.

Таблица 8.3.

Основные элементы списочных классов

Элемент Описание SelectionMode одно из значений перечисления SelectionMode; Items доступ к коллекции объектов ЭУ (часто используется

для доступа по индексу Items[i] или добавления эле-

ментов Items.Add(o); DataSource источник данных, для заполнения коллекции ЭУ SelectedItems коллекция выбранных элементов, если задана возмож-

ность выбора нескольких элементов; SelectedIndices коллекция индексов в списке выбранных элементов, ес-

ли задана возможность выбора нескольких элементов; DisplayMember название свойства элементов списка, которое будет ис-

пользоваться для показа на экране; ValueMember название свойства элементов списка, которое будет воз-

вращаться, как SelectedValue (только при использо-

вании DataSource); SelectedItem cсылка на выбранный элемент списка (тип object); SelectedIndex cсылка на индекс выбранного элемента списка; SelectedValue значение свойство выбранного элемента заданное в

свойстве ValueMember.

Количество элементов списка, которые может выбрать пользова-

тель, зависит от значения свойства SelectionMode. Данное свойство

может иметь одно из значений перечисления SelectionMode:

None – ни один элемент не может быть выбран;

Page 141: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

141

One – только один элемент может быть выбран (по умолчанию);

MultiSimple – несколько элементов можно выбрать;

MultiExtended – несколько элементов можно выбрать и при

этом можно использовать клавиши SHIFT, CTRL и стрелки.

Для занесения данных в коллекцию этих ЭУ можно использовать

один из следующих способов:

добавлять объекты используется метод Items.Add(<объект>);

создать любую коллекцию объектов (массив, ArrayList,

List<>); занести в нее элементы требуемых типов и указать в ка-

честве источника данных для свойства DataSource.

Если к списку добавляются элементы некоторого типа, то текст, кото-

рый будет показываться в списке будет получаться либо с помощью их

методов ToString(), либо с помощью того свойства, название которо-

го задано в свойстве DisplayMember. Если данные в ЭУ заносятся с

помощью связывания с некоторой коллекцией элементов, то можно

также задать свойству ValueMember название свойства элементов кол-

лекции, которое будет возвращать некоторое значение выбранного эле-

мента. Например, если в список включаются, например, объекты разра-

ботанного класс Person, который содержит свойства Name и Pasport-

Num, то эти свойства могут быть заданы следующим образом: list1.DisplayMember = "Name";

list1.ValueMember = "PasportNum";

Эти значения сообщают списочному ЭУ, что пользователю необходимо

показывать значения свойства Name. А в качестве выбранного значения

будет возвращаться значение свойства PasportNum. Ниже приведен

пример заполнения списочного ЭУ ListBox и получения выбранных

пользователем значений: private void Form1_Load(object sender, EventArgs e){

List<Person> arr = new List<Person>()

{new Person("Иванов", 111),

new Person("Петров", 222),

new Person("Сидоров", 333)};

listBox1.DataSource = arr;

listBox1.DisplayMember = "Name";

listBox1.ValueMember = "PassportNum";

}

private void listBox1_SelectedIndexChanged(object

sender, EventArgs e) {

int i = (int)listBox1.SelectedIndex;

if (listBox1.SelectedValue is int) {

int s = (int)listBox1.SelectedValue;

Page 142: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

142

// . . . используем полученное значение s

}

}

class Person {

public string Name { get; set; }

public int PassportNum { get; set; }

public Person(string nm, int num)

{ Name = nm; PassportNum = num; }

}

Элемент управления ComboBox является комбинацией ЭУ Text-

Box и ListBox. Он позволяет просматривать список элементов, делать

выбор и выполнять их изменение. Поведение ComboBox задается с по-

мощью свойства DropDownStyle, которое может принимать одно из

следующих значений перечисления DropDownStyle:

DropDown – текстовая часть ЭУ может редактироваться пользова-

телем. Можно также делать щелчок на кнопке для показа списка.

Simple – аналогичен DropDown, но список элементов всегда ви-

ден.

DropDownList – текстовую часть ЭУ редактировать нельзя.

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

8.4.7. Класс выбора даты и времени DateTimePicker

ЭУ DateTimePicker дает пользователям возможность выбрать

значение даты и времени в разных форматах. Можно показать значение

типа DateTime (раздел 6.1.2) в любом стан-

дартном формате представления даты и вре-

мени. Свойству Format может быть задано

одно из значений перечисления DateTime-

PickerFormat: Long, Short, Time или Cus-

tom. Если задано значение DateTimePick-

erFormat.Custom, то свойству CustomFor-

mat нужно задать строку форматирования.

Свойство Text возвращает текстовое представление значения Da-

teTime, а свойство Value возвращает объект типа DateTime.

Когда пользователь щелкает кнопку со стрелкой вниз, то появляется ка-

лендарь, позволяющий выбрать конкретную дату. В ЭУ есть свойства,

позволяющие изменить внешний вид календаря, путем задания фоново-

го цвета заголовка и поверхности (background colors), а также цвета вы-

водимого текста (foreground colors).

Page 143: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

143

8.4.8. Класс картинок PictureBox

Класс PictureBox используется для показа изображений, храня-

щихся в одним из следующих форматов: BMP, JPEG, GIF, PNG, мета-

файл или иконка. Основным свойством данного ЭУ является Image, ко-

торому следует присвоить объект типа Image.

Для задания изображения в PictureBox нужно вначале создать

объект типа Image. Например, загрузить JPEG файл в PictureBox: Bitmap myJpeg = new Bitmap("mypic.jpg");

pictureBox1.Image = (Image)myJpeg;

Тип Image это абстрактный базовый класс для класса Bitmap (про-

странство имен System.Drawing.Imaging). А Bitmap – основной

класс для работы с изображениями в оперативной памяти. Отметим, что

перед присвоением myJpeg необходимо выполнить преобразование в

производный тип Image.

Размер клиентской области ЭУ PictureBox можно задать с по-

мощью свойства ClientSize. Свойство SizeMode использует пере-

числение PictureBoxSizeMode для задания размера изображения и

его позиционирования в PictureBox. Перечисление PictureBoxSi-

zeMode имеет следующие значения:

Normal – изображение показывается в своем обычном размере

начиная с левого верхнего угла PictureBox;

CenterImage – изображение показывается в середине Picture-

Box, если изображение больше, то края изображения теряются;

AutoSize – размер PictureBox становится равным размеру

изображения;

StretchImage – размер изображения преобразовать к размеру

PictureBox.

8.4.9. Класс индикаторов ProgressBar

ЭУ ProgressBar визуально отображает ход выполнения долго-

временных операций. Он показывает пользователям,

что какой-то процесс выполняется, и он должен

ждать. У данного элемента есть свойства Minimum и

Maximum, которые соответствуют левому (Minimum) и правому (Maxi-

mum) полосы индикатора. Кроме этого есть свойство Step для задания

шага изменения значения индикатора при вызове метода

PerformStep(). Свойство Value возвращает текущее состояние инди-

катора ProgressBar. Также можно использовать свойство Text для

информирования пользователя о процентах выполненных операций или

Page 144: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

144

количестве времени, которое осталось ждать. Внешний вид данного ЭУ

можно изменит с помощью свойства BackgroundImage.

8.4.10. Класс панелей Panel

Класс Panel это просто ЭУ, который содержит другие ЭУ. Путем

объединения ЭУ в группы и их размещения на панели немного проще

управлять ими. Например, можно сделать все эти ЭУ неактивными (dis-

abled), если сделать неактивным сам объект Panel, в который они

включены.

Так как класс Panel является производным от класса Scrollab-

leControl, то можно пользоваться преимуществами свойства Auto-

Scroll. Если имеется слишком много ЭУ для отображения в доступной

области экрана, то размещая их в Panel и задавая свойству Auto-

Scroll значение true можно выполнять скроллирование имеющихся

элементов управления. По умолчанию Panel не показывает границы,

но задавая свойству BorderStyle любое значение кроме none можно

использовать Panel для визуальной группировки ЭУ с помощью гра-

ниц.

8.4.11. Класс разделяющихся панелей SplitContainer

Элемент управления SplitContainer в действительности явля-

ется объединением трех элементов управления. Он имеет два ЭУ Panel

с разделителем между ними Splitter. Пользова-

тель может передвигать разделитель и менять раз-

меры панелей. При размещении курсора над разде-

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

разделителя может перемещаться. ЭУ SplitCon-

tainer может содержать любые ЭУ и другой SplitContainer.

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

с помощью свойств SplitterDistance и SplitterIncrement.

Свойство SplitterDistance задает начальное положение разделителя

относительно левой или верхней границы ЭУ. Свойство SplitterIn-

crement задает, на какое количество пикселей разделитель будет пере-

мещаться при перетаскивании. Для панелей можно задать их минималь-

ные размеры в пикселях с помощью свойств Panel1MinSize и Pan-

el2MinSize. ЭУ Splitter инициирует два события, которые связаны

с его перемещением: SplitterMoving и SplitterMoved. Первое воз-

никает, когда разделитель перемещается, а второе после того как пере-

мещение закончилось. Они получают параметр типа SplitterEven-

tArgs, который содержит свойства для координат x и y верхнего левого

угла Splitter (SplitX и SplitY) и курсора «мыши» (X and Y).

Page 145: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

145

8.4.12. Классы страниц с ярлыками TabControl и TabPages

Класс TabControl позволяет группировать связанные ЭУ в на-

бор страниц с ярлыками (tab page). Данный класс управляет коллекцией

Controls объектов класса TabPage. Для добав-

ления новой страницы к TabControl нужно соз-

дать объект класса TabPage и добавить его к

коллекции объекта TabControl.

Свойству Appearance задаются значения

перечисления TabAppearance, для задания внешнего вида ярлыков:

FlatButtons, Buttons или Normal. Свойство Text у ЭУ TabPage за-

дает текст, который будет показываться на ярлыке. Свойство

SelectedTab указывает на страницу, которая в выбрана в текущий мо-

мент.

8.5. Работа с меню и инструментальными полосами Важными составляющими графического интерфейса являются

меню и инструментальные полосы с элементами управления. Под тер-

мином меню понимается структура иерархически организованных ко-

манд. Меню состоит из пунктов меню. Каждый пункт меню может быть

либо подменю, состоящим из своих пунктов, либо конечным элементом

меню – командой, при выборе которой выполняются определенные дей-

ствия.

Главным меню называется строка, содержащая элементы меню

верхнего уровня и обычно появляющаяся в вершине окна приложения –

в данном случае, в вершине формы. Как правило, главное меню всегда

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

не задает команду, под ним появятся подпункты меню, заданные этим

элементом, т.е. появляется выпадающее меню. Поскольку каждый из

пунктов выпадающего меню может быть тоже меню, то при выборе это-

го пункта соответствующее выпадающее меню появляется слева или

справа от него.

Кроме структуры, заданной главным меню, в форме и в элементах

управления можно создать контекстные меню, появляющиеся при на-

жатии правой кнопки «мыши».

Инструментальные полосы с элементами управления (например,

кнопками, выпадающими списками) дополняют меню, и позволяют уп-

ростить выполнение команд меню. Они устроены проще, чем меню, так

как здесь нет иерархии. На полосах располагаются ЭУ, выбор которых

(событие Click) запускает на выполнение заданные обработчики со-

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

полос, соответствуют наиболее часто используемым командам меню и

Page 146: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

146

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

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

дами меню.

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

достаточно создать соответствующие им объекты и включить их в кол-

лекцию Controls формы, в которой они будут использоваться.

Ссылки на объекты соответствующие меню, кроме добавления в

коллекцию Controls, также должны быть присвоены специальным

свойствам формы:

MainMenuStrip – ссылка на объект типа MenuStrip, который

будет использоваться в качестве главного меню формы;

ContextMenuStrip – ссылка на объект типа MenuStrip, кото-

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

8.5.1. Классы меню и инструментальных полос

Рассмотрим классы, используемые при работе с инструменталь-

ными полосами и меню. Эти классы в значительной степени связаны

между собой.

Рис. 8.9. Иерархия классов инструментальных полос.

Классы производные от класса ToolStrip

Объекты класса ToolStrip являются контейнерами ЭУ, исполь-

зуемыми для создания структур меню, инструментальных полос

(toolbar) и строк состояния (status bar). Объекты ToolStrip использу-

ются напрямую для инструментальных полос (toolbar) и служат в каче-

стве базового класса для ЭУ MenuStrip и StatusStrip.

Все классы производные от класса ToolStrip содержат в своей

коллекции Items набор ЭУ, производных от класса ToolStripItem.

Класс ToolStripItem соответствует элементам, которые могут разме-

щаться в меню и инструментальных полосах и инициировать разные со-

бытия.

Класс ToolStripItem является производным от класса Sys-

tem.ComponentModel.Component, а не от класса Control. В этом

классе имеется два основных свойства: Text и Image. C их помощью

Page 147: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

147

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

сте.

Рис. 8.10. Иерархия классов элементов инструментальных полос.

Класс пунктов меню ToolStripMenuItem

Структура любых типов меню строится на основе объектов класса

ToolStripMenuItem. Каждый объект этого класса представляет один

пункт меню – команду. И каждый объект ToolStripMenuItem имеет

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

DownItems, и которая может включать дочерние пункты меню (подме-

ню). Так как класс ToolStripMenuItem является производным от

класса ToolStripItem, то у него есть все свойства форматирования

базового класса. Изображения (images) появляются в виде маленьких

иконок справа от текста пункта меню.

Пункты меню могут иметь отметки (check marks) показывающие-

ся со свойствами Checked и CheckState. Горячие клавиши могут быть

назначены для каждого пункта меню, например, такие как Ctrl+C

(обычно используется для операции копирования). После того, как го-

рячие клавиши назначены, их можно показывать в пункте меню, задавая

свойству ShowShortCutKey значение true.

Для каждого пункта меню задаются обработчики события Click.

Если используется свойство Checked, то могут использоваться события

CheckStateChanged и CheckedChanged для определения изменений

состояния пункта.

Page 148: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

148

8.5.2. Создание главного меню

Класс MenuStrip представляет собой контейнер для структуры

меню приложения. Система меню стоится путем добавления объектов

класса ToolStripMenu к коллекции Items объекта класса MenuStrip.

Объекты класса ToolStripMenuItem добавляются в коллекцию Items

и для каждого из них задается обработчик события Click для выполне-

ния некоторой задачи приложения. Для создания подменю (меню кото-

рое открывается при выборе некоторого элемента меню), нужно также

создать элементы этого подменю и добавить их к

коллекции DropDownItems элемента меню (объект

класса ToolStripMenuItem). Для связывания меню

с формой нужно добавить его к коллекции ЭУ Con-

trols, а также присвоить его свойству форму

MainMenuStrip. Например: // Описывает объекты, составляющие меню

MenuStrip mnuMainMenu = new MenuStrip();

ToolStripMenuItem mnuFile =

new ToolStripMenuItem("&File");

ToolStripMenuItem mnuFileExit =

new ToolStripMenuItem("E&xit");

//...

// Добавляем File меню в основное меню

mnuMainMenu.Items.Add(mnuFile);

// Теперь добавляем Exit меню в File меню

mnuFile.DropDownItems.Add(mnuFileExit);

mnuFileExit.Click += mnuFileExit_Click;

// задаем меню форме

Controls.Add(this.mnuMainMenu);

MainMenuStrip = this.mnuMainMenu;

8.5.3. Создание контекстного меню Для создания контекстного меню, которое появляется при нажа-

тии правой кнопки «мыши» используется класс ContextMenuStrip.

Как и MenuStrip, класс ContextMenuStrip является контенером для

объектов типа ToolStripMenuItem. Контекстное меню создается так

же, как и главное меню – MenuStrip. Объекты класса ToolStripMe-

nuItem добавляются в коллекцию Items и для каждого из них задается

обработчик события Click для выполнения какой-то задачи. Для свя-

зывания контекстного меню с некоторым ЭУ нужно присвоить его

свойству ContextMenuStrip данного элемента управления. После это-

го при нажатии пользователем правой кнопки, данное меню будет появ-

Page 149: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

149

ляться, и нужные обработчики будут выполняться при выборе в нем ко-

манд.

8.5.4. Создание инструментальной полосы и строки состояния Для создания инструментальной полосы (или строки состояния)

нужно создать экземпляр класса ToolStrip

(или StatusStrip) и добавить его в коллекцию

Controls формы, на которой она должна появ-

ляться. Затем нужно создавать объекты классов,

производных от ToolStripItem, задавать их

свойства и добавлять к коллекции Items. Для всех элементов инстру-

ментальной полосы можно задать соответствующие обработчики собы-

тий. Ниже показан пример создания инструментальной полосы и вклю-

чения в нее текстового поля и кнопки: // описание элементов интерфейса

ToolStrip ts = new ToolStrip();

ToolStripTextBox tstb = new ToolStripTextBox();

ToolStripButton tsbtn = new ToolStripButton("Выход");

// ...

// в конструкторе формы задаем инструментальную полосу

ts.Items.Add(tstb); // добавляем текстовое поле

ts.Items.Add(tsbtn); // добавляем кнопку

// задание обработчика для кнопки

tsi.Click += mnuFileExit_Click;

Controls.Add(ts); // занесение в коллекцию формы

Напомним, что напрямую только ЭУ производные от ToolStri-

pItem могут включаться в коллекцию Items объектов класса Tool-

Strip. Однако класс ToolStripControlHost позволяет включать в

инструментальную полосу и элементы, которые не являются производ-

ными от класса ToolStripItem. Ниже показан пример того, как в объ-

ект типа ToolStrip может быть вставлен ЭУ DateTimePicker (в дан-

ном примере, при выборе даты также вычисляется количество дней, ча-

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

(свойство DateTime.Now)): ToolStrip tStrip1 = new ToolStrip();

ToolStripLabel tSLabel1 = new ToolStripLabel();

tStrip1.Items.Add(tSLabel1);

Controls.Add(tStrip1);

ToolStripControlHost dtCtl;

dtCtl = new ToolStripControlHost(new DateTimePicker());

((DateTimePicker)dtCtl.Control).ValueChanged +=

delegate { tSLabel1.Text =

Page 150: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

150

((DateTimePicker) dtCtl.Control).Value.

Subtract(DateTime.Now).ToString(); };

dtCtl.Width = 200;

dtCtl.DisplayStyle = ToolStripItemDisplayStyle.Text;

tStrip1.Items.Add(dtCtl);

Отметим, что при создании

экземпляра ToolStripCon-

trolHost в его конструктор пе-

редается объект, который будет в

нем содержаться. Далее задается

обработчик события Value-

Changed для ЭУ DateTime-

Picker. Доступ к данному ЭУ

можно получить с помощью

свойства Control класса ToolStripHostControl. В результате будет

получен объект базового типа Control и поэтому нужно указать пра-

вильный тип этого ЭУ. После этого все свойства и методы вставленного

ЭУ становятся доступными для использования. Метод Sub-

tract(DateTime.Now) структуры DateTime выполняет вычитание

текущей даты из выбранной даты.

8.6. Разработка Windows приложений в Visual Studio Система разработки Microsoft Visual Studio .Net позволяет значи-

тельно автоматизировать процесс разработки графического интерфейса.

Пользователь практически может разработать графический интерфейс

приложения без написания кода, а используя только специальные сред-

ства системы разработки – дизайнеры, которые на основе манипуляций

с изображениями ЭУ формирует программный код.

8.6.1. Создание проекта и работа с формой

В системе разработки Microsoft Vis-

ual Studio .Net проект для разработки

Windows приложения, создается также как

и консольное приложение, но в окне соз-

дания нового проекта (New Project) нужно

выбрать шаблон «Windows Forms Applica-

tion» и задать ему имя. Как и при создании

консольного проекта, по умолчанию соз-

дается решение, содержащее единствен-

ный проект, в котором имеется единст-

венное пространство имен (все три кон-

Page 151: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

151

струкции имеют совпадающие имена). В пространство имен вложен

единственный класс Form1, но это уже не такой простой класс, как ра-

нее. Класс Form1 является наследником класса Form и автоматически

наследует его функциональность – свойства, методы, события. При соз-

дании объекта этого класса, ответственного за создание и работу с фор-

мой, одновременно Visual Studio создает визуальный образ объекта –

окно, в котором можно размещать элементы управления.

В режиме проектирования наполнение формы элементами управ-

ления выполняется с помощью специального инструментария – дизай-

нера форм (Form Designer). Доступные элементы управления, отобра-

жаемые на специальной панели (Toolbox) и с помощью «мыши» пере-

таскиваются на форму. При размещении на форме элемента управле-

ния, немедленно в тексте класса появляются соответствующие строки

кода по объявлению переменных соответствующих классов, созданию

их экземпляров, задание некоторых свойств и занесения в коллекцию Controls.

Рис. 8.11. Инструменталь-

ныя панель.

Рис. 8.12. Вы-

бранный на

форме ЭУ.

Рис. 8.13. Панель со свой-

ствами и событиями вы-

бранного ЭУ.

8.6.2. Анализ создаваемого Windows приложения

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

файла, в которых записывается создаваемый код приложения:

в файле с именем Program.cs описывается статический класс

Program с методом Main(), в котором выполняется создание фор-

мы и передача ей сообщений ОС с помощью метода

Application.Run();

в двух файлах Form1.cs и Form1.Designer.cs описывается

класс формы Form1 (с модификатором класса partial);

Page 152: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

152

основным содержанием файла Form1.Designer.cs является объ-

явление в классе Form1 всех элементов управления, добавляемых

к форме и метод InitializeComponent()в котором содержится

автоматически формируемый код по созданию этих объектов, зада-

нию их свойств, добавлению в нужные коллекции и заданию обра-

ботчиков событий; ручное изменение содержания этого файла де-

лать не рекомендуется;

основным содержанием файла Form1.cs является конструктор

класса Form1, в котором вы-

зывается метод Initiali-

zeComponent() и все соз-

даваемые с помощью панели

свойств обработчики собы-

тий, код которых создает и

корректирует разработчик;

для перехода к содержанию

данного файла нужно вы-

брать в окне Solution Ex-

plorer в контекстном меню

элемента Form1.cs команду View Code.

8.6.3. Разработка меню приложения

Для построения в режиме проектирования главного меню и свя-

занной с ним структуры достаточно перенести на форму элемент управ-

ления, называемый MenuStrip.

После этого

иконка данного эле-

мента управления

появляется ниже

формы, а на форме

появляется элемент

меню с информаци-

онным полем, в ко-

тором можно задать

название пункта меню, и двумя указателями на следующие пункты ме-

ню того же уровня или пункт меню более низкого уровня. Такая проце-

дура создания меню интуитивно понятна и обычно не вызывает про-

блем. На рис. 8.14 показан процесс создания меню.

Рис. 8.14. Дизайнер создания меню.

Page 153: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

153

8.6.4. Создание инструментальных панелей

Для создания инструментальной по-

лосе или строки состоянии на форму следу-

ет перенести элемент управления Tool-

Strip или StatusStrip. В результате

этого на форме появится полоса, на кото-

рой размещен выпадающий список, позво-

ляющий выбрать добавляемый в инстру-

ментальную полосу ЭУ. После выбора тре-

буемых элементов им нужно задать тре-

буемые параметры и обработчики событий.

8.7. Рисование в форме В клиентской области формы можно не только размещать ЭУ, но

и выполнять рисование. В Framework .Net реализован расширенный

графический интерфейс GDI+, обладающий широким набором возмож-

ностей. В пространства System.Drawing и System.Drawing2D биб-

лиотеки FCL включено большое количество типов (классы, структуры и

перечисления), которые позволяют выполнить рисование в форме.

Рис. 8.15. Система координат и базовые элементы.

В классе Control (базовый класс для Form) имеется набор

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

ентской области. Ниже показаны примеры двух свойств и одного мето-

да формы (this – ссылка на текущий объект Form): // определение размера клиентской области формы

Size s = this.ClientSize;

// задание фонового цвета клиентской области

this.BackColor = Color.White;

// задание перерисовки формы при изменениях размера

this.SetStyle(ControlStyles.ResizeRedraw, true);

Клиентская

область формы

+x

+y

(0,0)

1 ед. = 1 пиксель

Надпись

Прямоуго- льник: Линия: Строка: Эллипс:

Page 154: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

154

Для рисования в формах нужно иметь объект класса Graphics, ме-

тоды которого позволяют выполнять рисование в формах, и такие инст-

рументы рисования, как – цвет, перо, кисть, шрифт.

8.7.1. Класс Graphics

Класс Graphics – это основной класс, с помощью которого выпол-

няется рисование. Объекты этого класса зависят от контекста устройст-

ва, (результат рисования не обязательно отображается на дисплее ком-

пьютера, она может выводиться на принтер, графопостроитель или дру-

гие устройства), поэтому создание объектов класса Graphics выполня-

ется не обычным способом, с помощью операции new, а с помощью ме-

тодов CreateGraphics (базового класса Control) у тех форм, на ко-

торых будет выполняться рисование. Например, если нужно на форме

нарисовать круг вокруг точки, где выполнен щелчок кнопки «мышки»,

то можно использовать следующий обработчик события: void Form1_MouseClick(object sender, MouseEventArgs e){

Graphics gfx = CreateGraphics();

gfx.DrawEllipse(Pens.Blue,e.X - 10,e.Y - 10,20, 20);

}

В данном классе имеется большое количество элементов (61 ме-

тод, большинство из которых перегружено, и 17 свойств). Прежде чем

рассматривать эти методы рисования, следует рассмотреть событие, при

возникновении которого инициируется перерисовка клиентской области

формы, систему координат, в которой выполняется рисование и специ-

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

8.7.2. Событие Paint для перерисовки клиентской области

Событие Paint возникает всякий раз, когда клиентская область

формы, в которой происходило рисование, портится. Причины этого

могут быть разные – пользователь свернул форму, изменил ее размеры,

произошло перекрытие другой формой, был вызван метод формы In-

validate() – во всех этих случаях требуется перерисовать область.

Задачей обработчика события Paint является перерисовка клиентской

области формы. Для повышения эффективности можно анализировать

поврежденную область и выполнять рисование только в ее пределах.

Первый раз событие Paint возникает при открытии формы. В об-

работчик события в качестве второго параметра передается объект типа

PaintEventArgs. Этот объект содержит параметры испорченной об-

ласти формы ClipRectangle и ссылка на объект класса Graphics, с

помощью которого можно выполнять рисование. Например, в обработ-

чике события Paint, занимающегося перерисовкой, этот объект можно

получить следующим образом:

Page 155: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

155

void Form1_Paint(object sender, PaintEventArgs e){

Graphics gfx = e.Graphics;

//перерисовка, использующая методы объекта gfx

}

8.7.3. Системы координат

В форме по умолчанию задается стандартная система координат,

показанная на рис. 8.15. Однако в классе Graphics имеется набор

свойств и методов для изменения такой системы координат области ри-

сования. Можно выделить следующие основные свойства:

свойство PageUnit для задания единиц измерения, которое мо-

жет принимать такие основные значения перечисления Graphic-

sUnit, как Pixel (по умолчанию), Millimeter, Point (1/72

дюйма), Inch (дюйм – 2.54 см);

свойство Transform для задания матрицы трансформации коор-

динат, которая описывается с помощью класса Matrix; рассмот-

рение данного класса выходит за пределы данного пособия, но

отметим, что с помощью такой матрицы можно создать обычную

систему координат с центром в точке (x, y) следующим образом: Graphics gfx = CreateGraphics(); gfx.Transform = new Matrix(1, 0, 0, -1, x, y);

К основным методам изменеия системы координат можно отнести:

перенос начала системы координат в точку (x, y) – Translate-

Transform(x, y);

поворот системы координат относительно начальной точки на x

градусов по часовой стрелке RotateTransform(х);

масштабирование по оси x и оси y – ScaleTransform(x, y);

8.7.4. Классы для описания геометрических объектов

В пространстве имен System.Windows.Drawing описан набор

структур описания таких элементарных геометрических объектов, как

точки, размеры и прямоугольники. Различают два вида таких структур –

с целочисленными полями (Point, Size и Rectangle) и с полями типа

float (PointF, SizeF и RectangleF). Рассмотрим далее структуры с

целочисленными полями.

Структура точек Point описывает точку с заданными положи-

тельными или отрицательными координатами, например: Point pt = new Point (100, 75);

Структура размеров Size описывает ширину и высоту некоторой

области, которые могут быть только положительными. Например: Size sz = new Size (60,100);

Page 156: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

156

Структура Rectangle описывает прямоугольники, которые зада-

ются координатами левого верхнего угла, а также шириной и высотой: Rectangle rec = new Rectangle(x, y, width, height);

Или объектами типа Point и Size, например: Rectangle rec =

new Rectangle(new Point(50,70),new Size(40,40));

8.7.5. Инструменты рисования

Методы рисования используют такие специальные инструменты,

как:

цвет (color) – задает цвет пикселей области рисования …

перо (pen) – задает тип (шаблон) линии, с помощью которой вы-

полняется рисование графического объекта;

кисть (brush) – задает шаблон заполнителя замкнутых областей

формы;

шрифт (font) – задает способ рисования символов.

Работа с цветом

Для задания цвета проще всего воспользоваться статической

структурой Color, в которой описаны 140 наиболее часто используе-

мых цветов. Например: Color.Black (черный цвет) или Color.Blue

(синий цвет). Также возможно создать и новый цвет с помощью метода Color.FromArgb (int red, int green, int blue);

Здесь red, green и blue это значения красного, зеленого и синего цве-

тов, заданные в интервале от 0 до 255. Например: Color с = Color.FromArgb(128, 128, 128);

Класс перьев Pen

Для описания перьев имеется класс Pen. В конструкторе этого

класса можно задать цвет и толщину

линии: Pen (Color clr, float width);

Основным свойством данного класса

является DashStyle, с помощью ко-

торого задается стиль линии, значения

перечисления DashStyle, такие, как

Solid, Dot, Dash, DashDot, DashDotDot.

Вместо создания нового пера можно использовать свойство ста-

тического класса Pens, в котором описаны предопределенные систем-

ные перья толщиной в 1 пиксель. Например, Pens.White – перо белого

цвета толщиной в 1 пиксель.

Page 157: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

157

Класс кистей Brush

Для описания кистей используется абстрактныq класс Brush и та-

ких основных производных от него классов, как:

SolidBrush – кисть для одноцветной закраски;

HatchBrush – кисть для закраски с геометрическим, регулярным

рисунком;

TextureBrush – кисть для закраски заданным изображением

(image);

LinearGradientBrush – кисть для закраски градиентной залив-

кой. Первые два класса кистей находятся в пространстве имен

System.Drawing, остальные – в System.Drawing.Drawing2D. У ка-

ждого из этих классов свои конструкторы.

Для работы с одноцветными кистями имеется статический класс

Brushes, в котором заданы одноцветные

кисти стандартных цветов. Например:

Brushes.Red – кисть для закраски

сплошным красным цветом. Также можно

создать собственную кисть класса Solid-

Brush с помощью следующего конструк-

тора: Color clr = Colors.Blue;

SolidBrush shadowBrush = new

SolidBrush(clr);

Для создания кисти класса Hatch-

Brush со стандартным геометрическим

рисунком можно использовать следую-

щие конструкторы: public HatchBrush(HatchStyle

hStyle, Color forecolor)

public HatchBrush(HatchStyle hstyle, Color forecolor,

Color backcolor)

Здесь backcolor это цвет фона, forecolor – цвет рисования узора,

HatchStyle – перечисление, которое может принимать значения, пока-

занные на рис. 8.16.

Класс шрифтов Font

Для выполнения вывода текста в клиентской области формы нуж-

но указать используемый шрифт, который задаются с помощью класса

Font. В данном классе имеются следующие основные конструкторы: Font (string strFamily, float size)

Рис. 8.16. Стандартные

геометрические узоры.

Page 158: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

158

Font (string strFamily, float size, FontStyle fs)

Здесь параметры конструкторов:

strFamily – строка с названием семества шрифтов, которые ус-

тановлены на компьютере, например, такие, как "Times New

Roman", "Arial" или "Courier New";

fs – параметр, который может принимать комбинации значений

перечисления FontStyle, такие как: Regular (обычный, по

умолчанию), Bold (жирный), Italic (курсив), Underline (под-

черкнутый), Strikeout (перечеркнутый).

Например: Font font = new Font ("Times New Roman", 24) ;

Font font1 = new Font ("Courier New", 12,

FontStyle.Bold | FontStyle.Italic;)

8.7.6. Методы рисования

В классе Graphics имеются следующие три основные группы

методов рисования: 1) методы рисование текста; 2) методы рисование

линий и 3) методы рисование и заполнения фигур.

Методы вывода текста

Для вывода текста на экран относительной левой верхней точки

(рис. 8.17) можно использовать следующтий метод: DrawString(string str, Font fn, Brush br,

float x, float y)

Рис. 8.17. Вывод текста относи-

тельно точки.

Рис. 8.18. Вывод текста в прямо-

угольнике.

Для вывода текста в заданном прямоугольнике (рис. 8.18) можно

использовать следующий метод: DrawString(string str, Font fn, Brush br,

RectangleF ptf, StringFormat sf)

Параметрами данных методов являются:

str – выводимая строка текста (константа или объект класса

string);

fnt – используемый фонт (объект класса Font);

br – используемая кисть (объект класса Brush);

x и y – координаты левого верхнего угла области вывода;

Page 159: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

159

sf – объект типа StringFormat, который описывает расположе-

ние строки в заданной прямоугольной области; в данной структу-

ре есть два свойства: sf.Alignment – положение по горизонтали

(значения перечисления StringAlignment: Near (слева),

Center (по центру), Far (справа) и свойство LineAlignment –

положение вертикали (значения перечисления

StringAlignment: Near (сверху), Center (по центру), Far (сни-

зу).

Например: StringFormat sf = new StringFormat();

sf.LineAlignment = StringAlignment.Center;

sf.Alignment = StringAlignment.Center;

gfx.DrawString("Вывод текста", fnt, Brushes.Blue,

new RectangleF(0, 0, 200, 100), sf);

В программе часто нужно определять размер области, занимае-

мый выводимым текстом. Это можно сделать с помощью метода

MeasureString(string, Font) класса Graphics. Например: Sizef s = gfx.MeasureString(str, Font);

Для рисования текста под углом вначале необходимо перенести

начало координат в левый-верхний угол области вывода, выполнить по-

ворот на заданный угол и только затем выполнить вывод текста. На-

пример: // Перенос системы кооординат

float x = 50f, y = 200f;

gfx.TranslateTransform(x, y);

//Поворот системы кооординат

gfx.RotateTransform(-45);

//Вывод текста

gfx.DrawString("Вывод под -45 градусов",

fnt, Brushes.Black, 50,200);

Методы рисования простых фигур

Все эти методы начинаются со слова Draw. Для рисования линий

используются следующие методы: DrawLine(Pen pn, Point pt1, Point pt2);

DrawLine(Pen pn, int x, int y, int x1, int y1);

DrawLines(Pen pn, Point [] pt);

При рисовании задаются или координаты точек, или объ-

екты типа Point. Метод DrawLines соединяет точки мас-

сива (линия от последней до первой точки не рисуется).

Например: Point[] points = {new Point(10,10),

Page 160: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

160

new Point(10,50), new Point(50,50)};

gfx.DrawLines(Pens.Blue, points);

Для рисования простых фигур с помощью заданного пера исполь-

зуется следующий набор перегруженных методов:

DrawEllipse(...) – рисование эллепса;

DrawPoligon(...) – рисование многоугольника;

DrawPie(...) – рисование сектора круга;

DrawRectangle(...) – рисование прямоугольника;

DrawPie(...) – рисование части круга.

Эти методы рисуют только контуры фигуры, но не закрашивают их

внутреннюю область. На рис. 8.19 показан результат работы этих мето-

дов.

Рис. 8.19. Результаты работы методов рисования и закраски фигур.

Для закраски областей заданной кистью используется набор пере-

груженных методов, названия которых начинаются со слова Fill, такие

как:

FillRectangle(...) – закраска прямоугольной области;

FillEllipse(...) – закраска эллипса;

FillPoligon(...) – закраска прямоугольника;

FillPie(...) – закраска сектора круга.

На рис. 8.19 показан результат работы этих методов. Ниже приведен

пример рисования и закраски прямоугольной области: Graphics gfx = e.Graphics;

Bitmap image1 =

(Bitmap)Image.FromFile(@"C:\sponge.gif");

TextureBrush txtr =

new TextureBrush(image1);

Rectangle rec = new Rectangle(10, 10,

100, 100);

gfx.FillRectangle(txtr, rec);

gfx.DrawRectangle(Pens.Blue, rec);

Page 161: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

161

Работа с изображениями

Классы для работы с изображениями описаны в пространстве

имен System.Drawing.Imaging. Основным классом для работы с

изображениями является класс Image – абстрактный базовый класс, со-

держащий набор методов для чтения и записи изображений в файл. Ос-

новным классом для создания изображения в оперативной памяти явля-

ется класс Bitmap, производный от класса Image, который содержит

данные и атрибуты пикселей составляющих графическое изображение.

Данный класс поддерживает такие форматы, как: BMP, GIF, JPEG, PNG

и TIFF.

Для чтения изображения из файла используется либо конструктор

класса Bitmap или метод FromFile(): Bitmap bmp = new Bitmap("c:\\globe.gif");

или bmp = (Bitmap)Bitmap.FromFile("c:\\globe.gif");

Для записи изображения в файл используется метод в файл

Save(). Например: bmp.Save("c:\\globe.jpg",

System.Drawing.Imaging.ImageFormat.Jpeg);

Для рисования в форме изображения используется метод

DrawImage(). Например: Bitmap bmp = new Bitmap("C:\\globe.gif");

// рисование относительно точки 0,0

g.DrawImage(bmp,0,0);

Примеры рисования

Рассмотрим два примера рисования в форме. Примеры построены

с использованием типа проекта Windows Application. Никакие элементы

на форме не размещаются. Ниже показано содержание файла Forma1.cs.

В первом примере выполняется рисование

графика функции Sin()в интервале изменение

аргумента от -2π до +2π: using System;

using System.Drawing;

using System.Windows.Forms;

using System.Drawing.Drawing2D;

namespace FunctionSample{

public partial class Form1 : Form {

int steps = 200; // кол-во шагов

public Form1() {

Page 162: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

162

InitializeComponent();

this.SetStyle(ControlStyles.ResizeRedraw, true);

this.BackColor = Color.White;

}

private void Form1_Paint(object sender,

PaintEventArgs e) {

Graphics gfx = e.Graphics;

Size s = this.ClientSize;

//перенос в середину и зеркально по Y

gfx.Transform = new Matrix(1, 0, 0, -1,

s.Width / 2, s.Height / 2);

//масштабирование по X и Y

gfx.ScaleTransform((float)(s.Width/15.0),

(float) (s.Height/2.5));

// задание интервала и шага

double minX = -2 * Math.PI;

double maxX = 2 * Math.PI;

double step = (maxX - minX) / steps;

// создание перьев

Pen pen = new Pen(Color.Crimson, 0.01F);

Pen pen1 = new Pen(Color.Black, 0.01F);

// рисование осей

gfx.DrawLine(pen1, -7.5F, 0F, 7.5F, 0F);

gfx.DrawLine(pen1, 0F, -1.25F, 0F, 1.25F);

// вычисление первой точки

PointF p1 = new PointF((float)minX,

(float)Math.Sin(minX));

PointF p2 = new PointF();

// цикл вычисления и рисования функции

for (double x = minX; x < maxX; x += step) {

// вычисление 2 точки

p2.X = (float)x; p2.Y = (float)Math.Sin(x);

gfx.DrawLine(pen, p1, p2);

p1 = p2; // сохранение в 1 точке

}}}}

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

ны ранее. При задании масштабирования ис-

пользовалась область размером (15 2.5), учи-

тывая, что область значений функции Sin() от -

1 до +1, а аргумент меняется в интервале от -2π

до +2π.

Во втором примере показывается анима-

ция движения шарика (круг синего цвета) в кли-

ентской области формы с отскоком от ее границ

Page 163: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

163

под углом 45 градусов:

using System;

using System.Drawing;

using System.Windows.Forms;

namespace MoveBall {

public partial class Form1 : Form {

Graphics gfx;

Random rnd = new Random();

int x, y;

int dx, dy;

int pWidth = 20, pHeight = 20;

Timer timer1 = new Timer();

public Form1(){

InitializeComponent();

gfx = CreateGraphics();

}

private void Form1_Load(object sender, EventArgs e) {

x = rnd.Next(ClientSize.Width);

y = rnd.Next(ClientSize.Height);

dx = dy = 5;

timer1.Tick += timer1_Tick;

timer1.Interval = 20;

timer1.Start();

}

private void Form1_Paint(object sender,

PaintEventArgs e) {

gfx.DrawEllipse(Pens.Blue, x, y, pWidth, pHeight);

}

private void timer1_Tick(object sender, EventArgs e){

if((x+pWidth+dx) > ClientSize.Width || (x+dx) < 0)

dx *= -1;

if((y+pHeight+dy) > ClientSize.Height || (y+dy)<0)

dy *= -1;

x += dx;

y += dy;

Invalidate();

}}}

Начальные координаты шарика определяются с помощью равномерно

распределенных случайных чисел. Перемещение шарика выполняется с

использованием событий Tick, которые инициирует объект класса Ti-

mer. Данный класс является компонентом и его объекты можно разме-

щать на форме с помощью дизайнера Visual Studio. Однако для просто-

Page 164: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

164

ты в данном приложении объект данного класса описан и создан вруч-

ную. Основными методами данного класса являются

int Interval(n) – задание интервала между событиями Tick,

которые инициируют объекты данного класса в миллисекундах

(тысячные доли секунда);

void Start() – начать инициирование событий Tick;

void Stop() – приостановить инициирование событий Tick.

При обработке события Tick вначале проверяются условия выхо-

да шарика за пределы формы и определяется направление следующего

его перемещения по координатам x и y. После этого координаты шари-

ка пересчитываются и вся клиентская область объявляется испорченной

с помощью метода формы Invalidate(). Это вызывает событие

Paint при обработке которого выполняется рисование шарика для но-

вых координат.

9. Работа с файлами и папками При разработке реальных приложений обязательным является

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

между сеансами работы с приложением. В библиотеку платформы .Net

– FCL имеется пространство имен System.IO содержащее набор клас-

сов для работы с папками (директориями, каталогами) и файлами на

внешних устройствах. В это пространство имен включены классы Di-

rectory и File, которые содержат методы для создания, удаления, ко-

пирования и перемещения папок и файлов с помощью статических ме-

тодов. С этими классами тесно связаны классы FileInfo и Directo-

ryInfo, которые содержат аналогичную функциональность, но исполь-

зуют методы экземпляров класса (поэтому, для работы с ними должны

быть созданы объекты этих классов с помощью операции new). Классы

FileInfo и DirectoryInfo являются производными от абстрактного

класса FileSystemInfo. Классы FileInfo и DirectoryInfo лучше

использовать для получения всех детальных описаний файлов и папок

(например, время их создания, возможности чтения/записи, и т.п.), так

как методы этих классов возвращают строго типизированные объекты.

А методы классов Directory и File чаще возвращают простые стро-

ковые значения, а не строго типизированные объекты.

9.1. Абстрактный базовый класс FileSystemInfo Классы DirectoryInfo и FileInfo получают много методов от

абстрактного класса FileSystemInfo. Элементы класса FileSyste-

Page 165: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

165

mInfo в основном используются для получения общих характеристик

(таких, как время создания, различные атрибуты и т.д.) о конкретном

файле или папке.

Таблица 9.1.

Свойства класса FileSystemInfo

Свойство Описание Attributes получение или задание атрибутов, связанных с теку-

щим файлом или папкой, которые представляются в

виде перечисления FileAttributes;

CreationTime получение или задание времени создания текущего

файла или папки;

Exists может использоваться для определения того, сущест-

вует ли заданный файл или папка;

Extension получение расширения файла;

FullName получение полного пути папки или файла;

LastAccessTime получение или задание времени, когда текущая папка

или файл в последний раз использовались;

LastWriteTime получение или задание времени, когда в текущую

папку или файл в последний раз выполнялась запись;

Name получение имени текущей папки или файла.

В классе FileSystemInfo также определен метод Delete(),

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

папки или файла с внешнего устройства. А также определен метод Re-

fresh(), вызов которого перед получением информации об атрибутах,

гарантирует, что эта информация не устарела.

9.2. Работа с классом DirectoryInfo Класс DirectoryInfo содержит набор методов для создания, пе-

ремещения, удаления и выполнения перечисления всех папок и подпа-

пок. В данном классе к функциональности базового класса FileSyste-

mInfo добавлены следующие основные возможности (табл. 9.2).

Таблица 9.2.

Основные элементы класса DirectoryInfo

Элементы Описание Create(),

CreateSubdi-

rectory()

создание папки (или набора подпапок) с использовани-

ем заданного имени;

Delete() удаление папки и всего ее содержания;

GetDirec-

tories()

получение массива срок, которые содержат названия

всех подпапок текущей папки;

GetFiles() получение массива объектов типа FileInfo, которые

Page 166: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

166

соответствуют файлам конкретной папки;

MoveTo() перемещение папки и ее содержания в новое место; Parent получение объекта DirectoryInfo родительской пап-

ки для текущей папки.

Root получение объекта DirectoryInfo для корневой пап-

ки текущей папки

9.2.1. Создание объектов класса DirectoryInfo

Работа с классом DirectoryInfo начинается с создания объекта

данного класса с помощью конструктора, параметром которого является

путь к конкретной папке. Для получения объекта соответствующего те-

кущей папке (например, директории выполняемого приложения), ис-

пользуйте строка ".". Например: // получение текущей рабочей папки

DirectoryInfo dir1 = new DirectoryInfo(".");

// получение объекта соответстующего папке C:\Windows

DirectoryInfo dir2 = new DirectoryInfo(@"C:\Windows");

Если указанной папки не существует, то объект будет создан, од-

нако при работе с ним будет выдаваться исключение

System.IO.DirectoryNotFoundException.

Если создан объект для не существующей папки, то нужно вы-

звать метод Create() прежде, чем продолжать работу с объектом: // Создаем объект для не существующей папки

DirectoryInfo dir3 = new DirectoryInfo(@"C:\MyFolder");

// Затем создаем эту папку

dir3.Create();

После создания объекта класса DirectoryInfo, можно выпол-

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

мые от класса FileSystemInfo. Например, объект класса Directo-

ryInfo, связанный с папкой C:\Windows, позволяет получить много

полезной информации о ней: DirectoryInfo dir = new DirectoryInfo(@"C:\Windows");

Console.WriteLine("FullName: {0}", dir.FullName);

Console.WriteLine("Name: {0}", dir.Name);

Console.WriteLine("Parent: {0}", dir.Parent);

Console.WriteLine("Attributes: {0}", dir.Attributes);

9.2.2. Перечисление файлов с помощью класса DirectoryInfo

Для получения информации о файлах, содержащихся в папке, ис-

пользуется метод GetFiles(), параметром которого является маска

имен нужных файлов (например, “*.*” – все файлы, “*.jpg” – файлы с

расширением jpg). Данный метод возвращает массив объектов типа Fi-

Page 167: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

167

leInfo, каждый из которых соответствует одному файлу, расположен-

ному в папке. Например: DirectoryInfo dir =

new DirectoryInfo(@"C:\Windows\Web\Wallpaper");

// Получить все файлы с расширением *.jpg

FileInfo[] imageFiles = dir.GetFiles("*.jpg");

// Сколько файлов было найдено?

Console.WriteLine("Found {0} *.jpg files\n",

imageFiles.Length);

// Вывод на экран информацию о каждом файле.

foreach (FileInfo f in imageFiles) {

Console.WriteLine("File name: {0}", f.Name);

Console.WriteLine("File size: {0}", f.Length);

}

9.2.3. Создание подпапок с помощью класса DirectoryInfo

Для создания новых подпапок в текущей папке используется ме-

тода CreateSubdirectory(). При одном вызове этого метод за один

раз создается одна или несколько вложенных подпапок. Например, для

создания подпапки с именем MyFolder: DirectoryInfo dir = new DirectoryInfo(".");

// создаем поддиректорию \MyFolder

DirectoryInfo myDataFolder =

dir.CreateSubdirectory("MyFolder");

// Создаем две вложенных поддиректории

dir.CreateSubdirectory(@"MyFolder2\Data");

Результатом работы метода CreateSubdirectory() является ссылка

на объект класса DirectoryInfo, который соответствует созданной

подпапке.

9.3. Работа с классом Directory Возможности класса Directory во много аналогичны возможно-

стям класса DirectoryInfo. Однако класс Directory является стати-

ческим классом и не позволяет создавать объекты, а только позволяет

вызывать его методы. Кроме этого отметим, что элементы Directory

обычно возвращают строки (string), а не объекты конкретных типов, та-

ких, как FileInfo или DirectoryInfo.

В примере приведенном ниже показано использование класса Di-

rectory для вывода не экран название всех устройств компьютера (с

помощью статического метода GetLogicalDrives()) и для удаления

ранее созданной папки с именем \MyFolder помощью статического

метода Delete(): // Перечисление всех драйверов компьютера

Page 168: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

168

string[] drives = Directory.GetLogicalDrives();

Console.WriteLine("Драйверы компьютера:");

foreach (string s in drives)

Console.WriteLine("--> {0} ", s);

// Удаление папки С:\MyFolder

try {Directory.Delete(string.Format(@"C:\MyFolder"));}

catch (IOException e) { Console.WriteLine(e.Message); }

9.4. Работа с классом DriveInfo В пространстве имен System.IO содержится класс с именем

DriveInfo. Так же, как и Directory.GetLogicalDrives(), стати-

ческий метод DriveInfo.GetDrives() позволяет получить имена

драйверов компьютера. Однако в отличие от Directo-

ry.GetLogicalDrives(), DriveInfo предоставляет множество дру-

гих данных (такие как тип драйвера, свободное место, метки тома и

т.п.). Рассмотри следующий пример: // Получение информации обо всех драйверах

DriveInfo[] myDrives = DriveInfo.GetDrives();

// Вывод на экран их данных

foreach(DriveInfo d in myDrives) {

Console.WriteLine("Имя: {0}", d.Name);

Console.WriteLine("Тип: {0}", d.DriveType);

// проверка, смонтирован ли драйвер

if (d.IsReady) {

Console.WriteLine("Свободное место: {0}",

d.TotalFreeSpace);

Console.WriteLine("Формат: {0}", d.DriveFormat);

Console.WriteLine("Метка: {0}", d.VolumeLabel);

Console.WriteLine();

}

}

9.5. Работа с классом FileInfo Класс FileInfo позволяет получать описания файлов, имеющих-

ся на внешнем устройстве (например, время создания, размер, атрибуты

файлов и т.д.) и помогает создавать, копировать, перемещать и уничто-

жать файлы. Класс FileInfo, помимо возможностей базового класса

FileSystemInfo, включает набор собственных элементов (табл. 9.3).

Отметим, что большинство методов класса FileInfo возвращают

объекты конкретных классов (FileStream, StreamWriter и т.п.), ко-

торые позволяют выполнять чтение/запись данных разных форматов из

(или в) связанный с ними файл.

Page 169: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

169

Таблица 9.3.

Основные элементы класса FileInfo

Элементы Описание AppendText() создание объекта класса StreamWriter (рассмотрен

далее), который добавляет текст к файлу; CopyTo() копирование существующего файла в новый файл; Create() создание нового файла и возвращение объекта типа File-

Stream (рассмотрен далее) для работы с ним; CreateText() создание объекта класса StreamWriter, который пишет

в новый текстовый файл; Delete() удаление файла; Directory получение объекта, соответствующего родительской

папке; DirectoryName получение полного пути к родительской папке; Length получение размера текущего файла или папки; MoveTo() перемещение файла в новое место, с возможностью

изменения имени файла; Name получение имени файла; Open() открытие файла с различными привилегиями на

чтение/запись и совместное использование; OpenRead() создание объекта класса FileStream только для чтения; OpenText() создание объекта класса StreamReader, который

позволяет читать из существующего текстового файла; OpenWrite() создание объекта класса FileStream только для записи.

9.5.1. Метод Create()

Метод Create()класса FileInfo является основным методом

для создания новых файлов. Он возвращает ссылку на объект типа Fi-

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

данного файла. После окончания работы с объектом FileStream, не-

обходимо выполнить закрытие данного объекта (потока), для освобож-

дения ресурса: // Создаем новый файл на устройстве C

FileInfo f = new FileInfo(@"C:\Test.dat");

FileStream fs = f.Create();

// . . . выполняем работу с новым файлом

// Закрываем поток файла

fs.Close();

9.5.2. Метод Open()

Для открытия существующего файла или создания нового файла

можно использовать метод Open() класса FileInfo. Метод Open()

позволяет более точно описать создаваемый файл, чем метод Create().

Page 170: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

170

Результатом выполнения метода Open(), является ссылка на объект

класса FileStream: // Создаем новый файл с помощью метода Open()

FileInfo f2 = new FileInfo(@"C:\Test2.dat");

FileStream fs2 = f2.Open(FileMode.OpenOrCreate,

FileAccess.ReadWrite, FileShare.None);

// . . .

fs2.Close();

Имеются несколько перегруженных методов Open(). Основной

вариант данного метода принимает три параметра.

Первый параметр описывает общую информацию об открытии

файла (например, создание нового файла, открытие существующего

файла, добавление данных в конец файла, и т.п.), что задается с помо-

щью перечисления FileMode (табл. 9.4): public enum FileMode {CreateNew, Create, Open,

OpenOrCreate, Truncate, Append}

Таблица 9.4.

Элементы перечисления FileMode

Элемент Описание CreateNew создание нового файла, Если файл с таким именем уже

существует, то будет формироваться исключение IOException;

Create создание нового файл. Если файл с таким именем уже

существует, то он будет переписан;

Open открытие существующего файла. Если такого файла нет, то

будет сформировано исключение FileNotFoundExcep-

tion;

OpenOrCreate открытие файла, если он уже существует; если его нет, то

он создается;

Truncate открытие файла и удаление его содержания до размера в 0

байт; Append открытие файла, перемещение в конец содержания файла и

переход в режим записи (это значение можно использовать

только с потоком для записи – write-only stream).

Если такого файла еще нет, то он создается.

Вторым параметром является значение перечисления FileAc-

cess, который определяет виды выполняемых операций ввода/вывода: public enum FileAccess { Read, Write, ReadWrite }

И, наконец, третий параметр, FileShare, задает режим совмест-

ной работы с данным файлом разными приложениями: public enum FileShare { None, Read, Write, ReadWrite }

Page 171: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

171

9.5.3. Методы OpenRead() и OpenWrite()

Метод Open() позволяет очень подробно описать режим работы с

файлом и получит ссылку на объект класса FileStream. Однако в

классе FileInfo также имеются методы OpenRead() и OpenWrite(),

которые позволяют получить ссылку на объект класса FileStream,

настроенные только на чтение и запись (соответственно), без необходи-

мости описывать различные параметры. Например: // Получить ссылку FileStream для чтения из файла

FileInfo f3 = new FileInfo(@"C:\Test3.dat");

FileStream readOnlyStream = f3.OpenRead();

// . . . использование объекта FileStream

readOnlyStream.Close();

// теперь получаем объект FileStream для записи

FileInfo f4 = new FileInfo(@"C:\Test4.dat");

FileStream writeOnlyStream = f4.OpenWrite();

//. . . использование объекта FileStream

writeOnlyStream.Close();

9.5.4. Метод OpenText()

Другим методом класса FileInfo предназначенным для откры-

тия файла является метод OpenText(). В отличие от методов

Create(), Open(), OpenRead()и OpenWrite(), метод OpenText()

возвращает ссылку на объект класса StreamReader, а не класса File-

Stream. Класс StreamReader предоставляет возможность читать тек-

стовые данные из файла. Например: // Получаем ссылку на объект класса StreamReader

FileInfo f5 = new FileInfo(@"C:\boot.ini");

StreamReader sreader = f5.OpenText();

// . . . использование объекта StreamReader

sreader.Close();

9.5.5. Методы CreateText() и AppendText()

Методы CreateText() и AppendText() возвращают ссылку на

объекты класса StreamWriter. Класса StreamWriter предоставляет

возможности для записи текстовых данных в файл. Метод

CreateText() позволяет создать новый файл и возвращает ссылку на

объект, позволяющую выполнять запись текстовых данных в новый

файл. А метод AppendText()возвращает ссылку на объект, позволяю-

щую выполнять запись текстовых данных в конец существующего фай-

ла. Например: FileInfo f6 = new FileInfo(@"C:\Test5.txt");

StreamWriter swriter = f6.CreateText();

// . . . использование объекта StreamWriter

Page 172: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

172

swriter.Close();

FileInfo f7 = new FileInfo(@"C:\FinTst.txt");

StreamWriter swAppend = f7.AppendText();

// . . . использование объекта StreamWriter

swAppend.Close();

9.6. Работа с классом File Класс File предоставляет почти те же возможности, которые есть

в классе FileInfo, но использует для этого статические методы. Так

же как и класс FileInfo, класс File предоставляет методы Append-

Text(), Create(), CreateText(), Open(), OpenRead(), Open-

Write() и OpenText(). Фактически, во многих случаях, классы File

и FileInfo могут использоваться взаимозаменяемо. В качестве демон-

страции, можно приведенные ранее примеры для класса FileStream

переписать с использованием класса File: // Получаем объект FileStream с помощью File.Create().

FileStream fs = File.Create(@"C:\Test.dat");

// Получаем объект FileStream с помощью File.Open().

FileStream fs2 = File.Open(@"C:\Test2.dat",

FileMode.OpenOrCreate, FileAccess.ReadWrite,

FileShare.None);

// Получаем объект FileStream только для чтения

FileStream readOnlyStream =

File.OpenRead(@"Test3.dat");

// Получаем объект FileStream только для записи

FileStream writeOnlyStream =

File.OpenWrite(@"Test4.dat");

// Получаем объект StreamReader

StreamReader sreader = File.OpenText(@"C:\boot.ini");

// Получаем объект StreamReader для нового файла

StreamWriter swriter =

File.CreateText(@"C:\Test3.txt");

// Получаем объект StreamReader для добавления текста

StreamWriter swAppend =

File.AppendText(@"C:\FinTst.txt");

Дополнительные методы класса File

Класс File также поддерживает несколько специальных методов,

которые поясняются в табл. 9.5, которые значительно упрощают про-

цесс чтения и записи текстовых данных.

Использование этих методов класса File позволяет читать и за-

писывать порцию данных с помощью нескольких операторов.

Page 173: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

173

Таблица 9.5.

Методы класса File

Метод Описание ReadAllBytes() открывает указанный файл, возвращает бинарные

данные в виде массива типа byte и затем закрывает

файл;

ReadAllLines() открывает указанный файл, возвращает символьные

данные в виде массива строк и затем закрывает файл; ReadAllText() открывает указанный файл, возвращает символьные

данные в виде одной строки и затем закрывает файл;

WriteAllBytes() открывает указанный файл, записывает массива типа

byte и затем закрывает файл;

WriteAllLines() открывает указанный файл, записывает массив строк и

затем закрывает файл; WriteAllText() открывает указанный файл, записывает символьную

строку и затем закрывает файл.

Например, в следующем примере символьные данные записыва-

ются в новый файл: string[] myTasks =

{"Перевести текст","Позвонить маме"};

// Запись всех данных в файл.

File.WriteAllLines(@"C:\tasks.txt", myTasks);

// Чтение всех записанных данных и вывод их на экран

foreach (string task in

File.ReadAllLines(@"C:\tasks.txt"))

Console.WriteLine("Нужно сделать: {0}", task);

Рис. 9.1. Классы производные от класса Stream.

Page 174: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

174

9.7. Абстрактный класс Stream Для того, чтобы не только получать информацию о файлах и вы-

полнять с ним разные операции (например, создание, копирование, пе-

ремещение и удаление), а также читать или записывать данные в файлы,

используется понятие «поток» (stream).

В области ввода/вывода данных, поток представляется виде пор-

ции данных передаваемых между источником и получателем. Потоки

предоставляют общий способ взаимодействия с последовательностью

байт, независимо от того, какой тип устройства (файл, сетевое соедине-

ние, принтер и т.п.) хранит или отображает эти байты. Абстрактный

класс System.IO.Stream определяет набор элементов, предоставляю-

щих поддержку разных типов взаимодействия с устройствами хранения

данных (например, некоторый файл или участок памяти).

Классы, производные от абстрактного базового класса Stream

(рис. 9.1) представляют данные в виде необработанного потока байт,

поэтому работа напрямую с ними достаточно трудоемка. Некоторые

классы производные от Stream поддерживают поиск, т.е. выполняют

определение и изменение текущей позиции в потоке. Основные элемен-

ты класса Stream, показаны в табл. 9.6.

Таблица 9.6.

Элементы класса Stream

Элемент Описание CanRead,

CanWrite

определение, поддерживает ли текущий поток операции по

чтению или записи;

Close() закрытие текущего потока и освобождение всех выделенных

ресурсов (таких, как сокеты и дескрипторы файлов)

связанные с текущим потоком;

Flush() обновление источника данных или хранилища с помощью

текущего состояния буфера и затем очистка буфера;

Length определение размера потока в байтах;

Position определение текущей позиции в потоке; Read(),

ReadByte()

чтение последовательности байт из потока, начиная с

текущей позиции и перемещение текущей позиции в потоке

на количество прочитанных байтов;

Seek() задание текущей позиции в потоке;

SetLength() задание размера потока;

Write(),

WriteByte()

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

текущей позиции в потоке на количество записанных байтов

Для работы с файлами на основе абстрактного класса Stream реа-

лизован класс FileStream, который определяет методы наиболее под-

ходящие для работы с файлами. Это достаточно простой поток, который

Page 175: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

175

может только читать и писать один или массив байт. При составлении

программ этот класс используется редко, чаще используются различные

потоки, основанные на классе FileStream, такие как StreamWriter и

StreamReader для работы с текстовыми данными, а также Binary-

Writer и BinaryReader для работы с бинарными данными.

9.8. Работа с классами StreamWriter и StreamReader Классы StreamWriter и StreamReader используются для чте-

ния и записи символьных данных (например, строк – string). Оба эти

класса по умолчанию работают с системой кодировки символов Un-

icode; однако, можно изменить систему кодировки с помощью задания

правильно сконфигурированного объекта класса System. Text.Enco-

ding.

Класс StreamWriter является производным от абстрактного

класса TextWriter, основные элементы которого описаны в табл.9.7.

Таблица 9.7.

Основные элементы класса TextWriter

Элемент Описание NewLine свойство, задающее константу для начала новой строки

(newline constant); по умолчанию это символ возврата

каретки, за которой следует символ окончания строки

(\r\n);

Write() перегруженный метод, который записывает данные в

текстовый поток без константы начала строки; WriteLine() перегруженный метод, который записывает данные в

текстовый поток с константой начала строки.

В классе StreamWriter переопределены разные методы базового

класса, в том числе и метод Write(). В примере, приведенном ниже,

создается новый файл с именем reminders.txt с помощью метода

File.CreateText(). Используя метод Write()полученного объекта

StreamWriter в новый файл добавляются текстовые данные: StreamWriter writer =

File.CreateText(@"c:\reminders.txt");

writer.WriteLine("Не забудь cделать перевод.");

writer.WriteLine("Не забудь позвонить маме.");

for(int i = 0; i < 5; i++) writer.Write(i + " ");

// вставляем символ новой строки

writer.Write(writer.NewLine);

Класс StreamReader (так же, как и связанный с ним класс

StringReader, рассмотренный ниже) является производным от класса

Page 176: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

176

от абстрактного класса TextReader, основные элементы которого опи-

саны в табл. 9.8.

Таблица 9.8.

Основные элементы класса TextReader

Элемент Описание Peek() чтение следующего доступного символа без изменения позиции

в потоке; значение -1 указывает на окончание потока;

Read() чтение данных из потока;

ReadBlo

ck()

чтение заданного количества символов из текущего потока и

запись данных в буфер, начиная с заданного индекса;

ReadLin

e()

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

строки (если строка пустая, то это “конец файла” – EOF); ReadToE

nd()

xтение всех символов, начиная с текущей позиции и до конца

потока и передача их в виде одной символьной строки.

Для чтения данных из файла с помощью объектов класса Strea-

mReader, который является производным от класса TextReader, ис-

пользуется переопределенный метод ReadLine(): StreamReader sr = File.OpenText(@"c:\reminders.txt");

string input = null;

while ((input = sr.ReadLine()) != null)

Console.WriteLine (input);

Результат: Не забудь cделать перевод.

Не забудь позвонить маме.

1 2 3 4 5

9.9. Работа с классами BinaryWriter и BinaryReader Для записи и чтения данных в бинарном (не текстовом) формате

используются классы BinaryReader и BinaryWriter.

Таблица 9.9.

Основные элементы класса BinaryWriter

Элемент Описание BaseStream cвойство только для чтения, предоставляет доступ к базовому

потоку, который используется объектом BinaryWriter;

Close() закрытие бинарного потока;

Flush() запись буфера памяти в бинарный поток; Seek() задание позиции в текущем потоке;

Write() запись значения в текущую позицию;

Класс BinaryWriter определяет имеющий большое количество

вариантов (сильно перегруженный) метод Write() для записи типов

Page 177: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

177

данных базовый поток. Кроме метода Write(), BinaryWriter содер-

жит дополнительные элементы, которые описаны в табл. 9.9.

Следующий пример записывает набор данных разных типов в но-

вый файл: //открываем бинарный writer для файла.

FileInfo f = new FileInfo(@"C:\BinFile.dat");

BinaryWriter bw = new BinaryWriter(f.OpenWrite());

// выводим на экран тип объекта BaseStream.

Console.WriteLine("Base stream is: {0}",bw.BaseStream);

// создаем некоторые данные для сохранения в файле

double aDouble = 1234.67;

int anInt = 34567;

string aString = "A, B, C";

// записывем данные в файл

bw.Write(aDouble);

bw.Write(anInt);

bw.Write(aString);

Отметим, что объект FileStream полученный из метода Open-

Write()передается конструктору класса BinaryWriter. Конструктор

BinaryWriter принимает объект любого класса, производного от

Stream (например, FileStream, MemoryStream и т.п.). Таким обра-

зом, если требуется хранить данные в оперативной памяти, то нужно

просто передать правильный объект типа MemoryStream.

Класс BinaryReader дополняет функциональность предлагае-

мую классом BinaryWriter. Основные элементы этого класса описаны

в табл. 9.10.

Таблица 9.10.

Основные элементы класса BinaryReader

Элемент Описание BaseStream свойство только для чтения; предоставляет доступ к базовому

потоку, который используется объектом BinaryReader;

Close() закрытие бинарного потока;

PeekChar() получение следующего доступного символа с перемещением

текущей позиции в потоке; Read() чтение заданного количества байт или символов и сохранение

их в заданном массиве;

ReadXxxx() множество методов, которые читают следующее значение

заданного типа из потока (например, ReadBoolean(),

ReadByte(), ReadInt32() и т.п.).

Например, для чтения данных из файла BinFile.dat можно вы-

полнить следующим образом:

Page 178: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

178

FileInfo f = new FileInfo(@"C:\BinFile.dat");

...

// Чтение данных из бинарного файла

BinaryReader br = new BinaryReader(f.OpenRead());

Console.WriteLine(br.ReadDouble());

Console.WriteLine(br.ReadInt32());

Console.WriteLine(br.ReadString());

10. Работа с базами данных Большинству приложений требуется выполнять работу с базами

данных (БД), расположенными либо локально на клиентских машинах

либо на удаленных серверах. Для этих целей в составе библиотеки FCL

имеется набор классов пространства имен System.Data (и связанных с

ним), который называется технологией ADO.NET. Данная технология

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

данным, с помощью которых можно максимально полно использовать

ресурсы системы.

Данная технология позволяет реализовать два режима работы с

данными: присоединенный и отсоединенный режимы. В присоеди-

ненном режиме приложение открывает соединение с БД и не закрывает

его до завершения работы. Однако, такой режим постоянного соедине-

ния с СУБД, является неудобным по следующим причинам:

поддержание соединения с СУБД требует использования систем-

ных ресурсов: чем больше открытых соединений приходится под-

держивать, тем ниже производительность системы;

приложения, использующие доступ к данным через постоянное

соединение, очень плохо масштабируются. Такое приложение хо-

рошо обслуживает соединения с двумя клиентами, с трудом

справляется с 10 и совершенно не работает со 100 клиентами.

В ADO.NET эти проблемы решаются с помощью отсоединенного

режима работы с БД. В этом режиме соединение с источником данных

открыто только до завершения необходимых действий над данными.

Например, если приложение запрашивает данные из БД, соединение ус-

танавливается только на время загрузки данных, после чего сразу же за-

крывается. Аналогично при обновлении БД соединение открывается на

время исполнения команды UPDATE, а затем закрывается. Поддерживая

соединения открытыми, в течение минимально необходимого времени,

ADO.NET экономно использует системные ресурсы и позволяет мас-

штабировать инфраструктуру доступа к данным – при этом производи-

тельность снижается незначительно.

Page 179: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

179

10.1. Архитектура технологии ADO.NET

Архитектура ADO.Net показана на рис. 10.1. Здесь под проваде-

ром данных понимается набор классов, выполняющих функции посред-

ника при взаимодействии программы и базы данных и эффективно реа-

лизующих основные операции работы с БД.

Рис. 10.1. Основные классы ADO.Net.

В настоящее время с .NET Framework поставляются 4 провайдера

данных:

SQL Server .NET Data Provider – используется для работы с SQL

Server версии 7.0 и выше (System.Data.SqlClient);

OleDb .NET Data Provider – используется для работы с БД с

помощью COM технологии OLEDB (System.Data.OleDb);

Odbc Data Provider – используется для работы с БД с помощью

старой технологии ODBC, основанной на драйверах баз данных

(System.Data.Odbc);

Oracle Data Provider – используется для работы с базами данных

Oracle (System.Data.OracleClient).

Любой провайдер включает собственные реализации следующих

универсальных классов (рис. 10.1):

Connection – обеспечивает подключение к БД;

Command – применяется для управления источником данных; по-

зволяет исполнять команды, не возвращающие данных, например

INSERT, UPDATE и DELETE, либо команды, возвращающие объект

DataReader (такие, как SELECT);

Page 180: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

180

DataReader – предоставляет доступный только для однонаправ-

ленного чтения набор записей, подключенный к источнику дан-

ных;

DataAdapter – заполняет отсоединенный объект DataSet или

DataTable и обновляет его содержимое.

В разных провайдерах к названиям этих классов добавляются

префиксы, такие как Sql, OleDb, Odbc, Oracle (для перечисленных вы-

ше провайдеров). Если название класса провайдера приводится без пре-

фикса, указывающего его версию, то сказанное в равной мере относится

ко всем версиям провайдеров данных. Например, Command означает как

OleDbCommand, тaк и SqlCommand.

Присоединенный режим работы с БД реализуется с помощью

только провайдеров данных (Data Provider). Доступ к данным осуществ-

ляется следующим образом, объект Connection устанавливает соеди-

нение между приложением и БД. С помощью объекта Connection на-

прямую взаимодействие с БД выполняют объекты Command. Данный

объект позволяет выполнять команды к БД. Если исполненная команда

возвращает несколько записей, то Command открывает к ним доступ че-

рез объект DataReader. Используя объект DataReader можно после-

довательно прочитать и сохранить в программе все полученные из БД

записи.

Отсоединенный режим работы с базой данных реализуется с

помощью класса DataSet. Объекты данного класса содержат выборки

данных из различных таблиц базы данных в оперативной памяти ком-

пьютера, изолированно от реальной БД. Такие объекты можно также

рассматривать как локальные копии фрагментов БД.

Данные в объект DataSet проще всего загрузить при помощи

объекта DataAdapter, который управляет набором команд к БД (для

выполнения выборки, изменения, удаления и добавления новых дан-

ных). Объект DataSet хранится в памяти, с его содержимым можно

выполнять различные действия (читать, изменять, удалять, добавлять)

независимо от БД. Все изменения сделанные локально в объект класса

DataSet может передать в серверную БД при помощи объектов Da-

taAdapter.

10.2. Провайдеры данных

10.2.1. Описание классы провайдеров данных

Провайдеры данных включают следующие основные классы:

Page 181: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

181

Класс Connection – задает и реализует соединение с БД. Все дан-

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

ConnectionString, в которой записывается строка подключения к ба-

зе данных. Основными методами данного класса являются: Open()–

открытие соединения и Close()– закрытие соединения.

Класс Command – реализует выполнение команд к БД, используя

для обмена данными открытое соединение. При помощи объектов Com-

mand можно выполнять команды SQL, хранимые процедуры (stored pro-

cedure), а также операторы, возвращающие отдельные таблицы. Объект

Command поддерживает три метода:

ExecuteNonQuery() – исполняет команды, не возвращающие

данные, например INSERT, UPDATE и DELETE;

ExecuteScalar() – исполняет запросы к БД, возвращающие

единственное значение;

ExecuteReader() – возвращает результирующий набор через

объект DataReader.

Класс DataReader – предоставляет доступ к выборке записей БД,

доступный только для однонаправленного чтения. В отличие от других

компонентов провайдера данных, создавать экземпляры DataReader

напрямую нельзя, их можно получать только при помощи метода Ex-

ecuteReader() объекта Command. Если записывать содержимое объ-

екта DataReader на диск не требуется, строки с данными можно пере-

давать приложению напрямую, поскольку в любой момент времени в

памяти находится только одна строка, использование объекта Data-

Reader почти не снижает производительность системы, но требует мо-

нопольного доступа к открытому объекту Connection в течение вре-

мени жизни объекта DataReader.

Класс DataAdapter – основной класс, обеспечивающий формиро-

вание отсоединенных данных. В сущности, он выполняет функции по-

средника во взаимодействии между БД и объектом DataSet. При вызо-

ве метода Fill() объект DataAdapter заполняет объект DataTable в

DataSet данными, полученными из БД. После обработки данных, за-

груженных в память, можно перенести измененные записи в БД, вызвав

метод Update() объекта DataAdapter. У класса DataAdapter име-

ются четыре свойства, представляющих команды БД:

SelectCommand – содержит текст или объект команды, осущест-

вляющей выборку данных из БД; при вызове метода Fill() эта

Page 182: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

182

команда исполняется и заполняет объект DataTable или объект

DataSet;

InsertCommand – содержит текст или объект команды, осущест-

вляющий вставку строк в таблицу;

DeleteCommand – содержит текст или объект команды, осущест-

вляющий удаление строки из таблицы;

UpdateCommand – содержит текст или объект команды, осущест-

вляющий обновление значений в БД.

При вызове метода Update() все измененные данные переносят-

ся из объекта DataSet в БД с исполнением соответствующей команды

InsertCommand, DeleteCommand или UpdateCommand.

10.2.2. Соединение с базой данных

Для соединения с базой данных нужно создать объект соответст-

вующего класса Connection. Задать строку подключения к БД – свой-

ство ConnectionString. А затем вызвать метод Open() – для выпол-

нения соединения. После окончания использования данного соединения

нужно вызвать его метод Close() для освобождения ресурсов сервера.

Например: // Объявить класс OleDbConnection и создать экземпляр.

OleDbConnection myCon = new OleDbConnection();

// Создать строку подключения, в которой задан тип БД.

// БД Microsoft Access и указан путь к файлу БД.

myCon.ConnectionString =

@"Provider=Microsoft.Jet.OLEDB.4.0; DataSource=" +

@"С:\Northwind\Northwind.mdb";

myCon.Open();

// . . .

myCon.Close();

Следует отметить, что набор параметров задаваемых в строке

подключения может меняться в зависимости от типа и конфигурации

провайдера данных, поэтому рекомендуется по возможности создавать

соединения с помощью графических инструментов Visual Studio .NET.

Например, для соединения с базой данных Northwind СУБД SQL

Express, которая установлена на компьютере Master строка соединения

может выглядеть следующим образом: @"Data Source=Master\SQLEXPRESS; Initial Catalog =

Northwind; Integrated Security=True"

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

в ОС и в базе данных – Integrated Security=True.

Page 183: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

183

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

Для выполнения команд в СУБД используются объекты класса

Command. Эти объекты содержат ссылку на оператор SQL или храни-

мую процедуру БД и могут выполнить эти команды в СУБД, используя

заданное открытое соединение (объект Connection). Объекты Com-

mand позволяют очень быстро и эффективно выполнять следующие ви-

ды взаимодействия с различными БД:

команды SQL SELECT (выборка записей), и хранимые процеду-

ры, которые возвращают набор записей (выборку);

команды SQL, не возвращающие значения, например INSERT

(вставка записей), UPDATE (обновление записей) и DELETE

(удаление записей);

команды SQL, возвращающие единственное значение;

команды языка изменения структуры базы данных (Database Defi-

nition Language, DDL), например CREATE TABLE или ALTER.

Создание команды

Для выполнения команды к БД необходимо создать экземпляр

объекта Command соответствующего типа и задать значения его свойст-

вам. Свойство CommandType определяет тип команды, содержащийся в

свойстве CommandText, оно может принимать одно из следующих зна-

чений перечисления CommandType:

Text – в этом случае свойству CommandText, должен быть задан

задан текст допустимой команды SQL;

StoredProcedure – в этом случае в свойство CommandText

должно быть занесено имя хранимой процедуры, которая будет

вызываться на выполнение;

TableDirect – в этом случае в свойство CommandText должно

быть задано имя таблицы БД и при исполнении данной команды

будут возвращены все столбцы и строки данной таблицы.

Свойству Connection следует задать ссылку на соответствую-

щий объект класса Connection.

Исполнение команд

Для выполнения команды можно использовать три метода класса

Command: ExecuteNonQuery(), ExecuteScalar() и

ExecuteReader(). Все эти методы исполняют команду, заданную в

свойствах CommandType и CommandText. Отличаются эти методы

только возвращаемым значением.

Page 184: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

184

Метод ExecuteNonQuery() – самый простой из методов выпол-

нения команды, он возвращает только количество обработанных запи-

сей БД. Данный метод применяют для вызова команд SQL и хранимых

процедур, таких, как INSERT, UPDATE или DELETE, а также команд из-

менения схемы БД – DDL.

Метод ExecuteScalar() возвращает только значение первого

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

строк было получено.

Метод ExecuteReader() возвращает объект DataReader, кото-

рый позволяет выполнять последовательное чтение данных от начала до

конца выборки. Если не требуется модифицировать содержимое БД, то

этот способ извлечения данных является самым быстрым и эффектив-

ным.

Формирование запросов SQL во время выполнения

Иногда содержание оператора запроса SQL становится известным

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

строку поиска, введенную пользователем, или возвращать столбцы или

таблицы, определенные программно в период выполнения. Для решения

этих задач соответствующие команды SQL должны создаваться, на-

страиваться и исполняться динамически, во время выполнения про-

граммы.

Для такого динамического формирования SQL команд описывают

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

которые добавляются в период выполнения приложения. Для объедине-

ния строк используйте оператор конкатенации “+”. Например: string Cmd = "SELECT * FROM Employees WHERE Name = '" +

aString + "'";

Отметим, что любые строковые переменные, передаваемые базе

данных при помощи конструкции WHERE оператора SELECT необходимо

заключать в одинарные кавычки ('). Если одинарные кавычки находятся

внутри такой переменной, следует заменить их парой одинарных кавы-

чек (''), в противном случае, исполнение запроса закончится неудачей.

Следующий пример демонстрирует динамическое формирование

команды DELETE, в которую вставляется значение поля Name удаляе-

мых записей: //Этот пример предполагает наличие объекта myConnection

// строка aStr содержит значение поля Name

public void DeleteRecord(string aStr)

{

string Cmd;

Page 185: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

185

Cmd = "DELETE * FROM Employees WHERE Name = ‘" +

aStr + "’";

OleDbCommand myCommand =

new OleDbCommand(Cmd, myConnection);

// осталось открыть соединение и исполнить команду,

myConnection.Ореn();

myCommand.ExecuteNonQuery( ) ;

// Не забудете закрыть соединение!

myConnection.Close();

}

Работа с параметрами команды

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

параметры, при этом значения некоторых параметров часто становятся

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

учета товаров в книжном магазине следует предусмотреть функцию по-

иска книг по названию, которую можно реализовать посредством запро-

са к БД с использованием следующего оператора SQL:

SELECT * FROM Books WHERE (Title LIKE [value]).

При этом значение value должно задаваться пользователем и заранее

оно не известно. В связи с этим необходимо иметь некоторый способ

передачи введенных значений оператору SQL и хранимым процедурам

во время выполнения программы.

Параметры – это значения, которыми заполняют поля подста-

новки, введенные в текст команды во время разработки. Параметры яв-

ляются объектами класса Parameter, которые должны добавляться к

свойству (коллекции) Parameters объекта Command. В период выпол-

нения значения параметров считываются из этой коллекции и подстав-

ляются в SQL оператор, либо передаются хранимой процедуре. В кол-

лекцию Parameters должны заносится объекты класса Parameter со-

ответствующего типа (например, SqlParameter и OleDbParameter).

На объекты коллекции Parameters можно ссылаться по индексу, либо

по имени, заданному свойством ParameterName. Ниже показаны два

способа установки значения первого по счету параметра с именем my-

Parameter. //устанавливаем значение параметра по индексу

DleDbCommand1.Parameters[0].Value = "Иванов А.И.";

//устанавливаем значение параметра по имени

OleObCommand1.Parameters["myParameter"].Value =

"группа 8551";

В классе OleDbParameter имеется взаимосвязанные свойства

DbType и OleDbType. Первое представляет тип параметра в платформе

Page 186: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

186

.Net, а второе – как он представлен в БД; это необходимо, поскольку не

все БД совместимы с типами платформы. Объект Parameter выполня-

ет преобразование параметров из типа, используемого в приложении, в

тип, используемый в БД. Поскольку эти свойства взаимосвязаны, при

изменении значения одного из них, значение другого автоматически

изменяется и преобразуется в соответствующий поддерживаемый тип.

Аналогичным образом связаны свойства DbType и SqlType объектов

SqlParameter, свойство SqlType указывает тип БД SQL, представ-

ленный этим параметром.

Свойство Direction класса Parameter указывает, является ли

этот параметр входным или выходным. Возможными значениями этого

свойства являются: Input (входные), Output (выходные), InputOut-

put (входные и выходные) или ReturnValue (возвращаемый в качест-

ве результата).

Свойства Precision, Scale и Size – определяют точность и

размер значения параметров. Свойства Precision и Scale применя-

ются с числовыми и десятичными параметрами и определяют разряд-

ность и длину дробной части значения свойства Value, соответственно,

a свойство Size применяется с двоичными и строковыми параметрами

и представляет максимальную длину такого поля. Свойство параметра

Value содержит значение параметра.

Если свойство CommandType объекта Command установлено в

Text, необходимо предусмотреть поля подстановки для всех парамет-

ров оператора SQL.

У объектов класса OleDbCommand поля подстановки обозначается

символами «?», например: SELECT Empld, Title, FirstName, LastName

FROM Employees WHERE (Title = ?)

Можно указывать несколько параметров: SELECT Empld, Title, FirstName, LastName

FROM Employees WHERE (FirstName = ?) AND (LastName = ?)

В этом случае, порядок заполнения параметров определяется порядком

элементов коллекции Parameters.

У объектов класса SqlCommand применяют именованные пара-

метры. Для создания поля подстановки именованного параметра, необ-

ходимо указать имя параметра (так как оно задано свойством

ParameterName), предварив его символом «@». Ниже показан пример

оператора SQL, в котором объявлено поле подстановки для именован-

ного параметра Title:

Page 187: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

187

SELECT EmpId, Title, FirstName, LastName

FROM Employees WHERE (Title = @Title)

SqlParameter parameter = new SqlParameter();

parameter.ParameterName = "@Title";

parameter.SqlDbType = SqlDbType.NVarChar;

parameter.Direction = ParameterDirection.Input;

Порядок работы с объектами Command

Для выполнения команды к БД нужно создать объект класса

Command. И задать значения его параметров. Для исполнения оператора

SQL свойству CommandType нужно задать значение перечисления

CommandType.Text, для исполнения хранимой процедуры свойству

CommandType нужно задать значение – CommandType.Stored-

Procedure. Свойству CommandText нужно задать строку оператора

SQL или имя хранимой процедуры, соответственно.

Для выполнения с помощью объекта Command команд, не возвра-

щающующих значений (операторы INSERT, UPDATE и DELETE, а также

операторы DDL, такие, как CREATE TABLE и ALTER) нужно вызвать ме-

тод Command.ExecuteNonQuery(), например: // одинаково, как для OleDbCommand, так и SqlCommand

myCommand.ExecuteNonQuery();

Для выполнения команды с помощью объекта Command, возвра-

щающей единственное значение необходимо вызвать метод

Command.ExecuteScalar(), например, так: // одинаково, как для OleDbCommand, так и SqlCommand

Object o = myCommand.ExecuteNonScalar();

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

набор записей, используется метод ExecuteReader(), возвращающий

объект DataReader. Данный объект обеспечивает быстрое и эффектив-

ное последовательное чтение данных от первой к последней записи.

Объект DataReader позволяет перебирать записи полученной выборки,

передавая нужные значения напрямую коду приложения. При этом объ-

ект DataReader намного эффективнее, но менее гибок, в сравнении с

DataSet. Данные, полученные через объект DataReader, доступны

только для чтения, поэтому их нельзя модифицировать средствами

DataReader. При этом данные разрешается просматривать только в

одном направлении: нельзя вернуться к записи, прочитанной ранее.

Кроме того, объект DataReader ориентирован на использование посто-

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

па к активному соединению.

Page 188: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

188

Создание объекта DataReader

Объект DataReader создается с помощью вызова метода

ExecuteReader() объекта Command. Например: // должен быть объект mySqlCommand

System.Data.SqlClient.SqlDataReader mySqlReader;

mySqlReader = mySqlCommand.ExecuteReader();

При вызове метода ExecuteReader() объект Command исполня-

ет представленную им команду и создает объект DataReader соответ-

ствующего типа, который можно записать в переменную ссылочного

типа.

Доступ к данным с помощью объекта DataReader

Получив ссылку на объект DataReader, можно просматривать

записи, загружая нужные данные в память. У нового объекта

DataReader указатель чтения устанавливается на первую запись полу-

ченной выборки. Чтобы сделать ее доступной, следует вызвать метод

Read(). Если запись доступна, метод Read() переводит указатель объ-

екта DataReader к следующей записи и возвращает True, в противном

случае, метод Read() возвращает значение false. Тогда, перебор всех

записей полученной выборки в цикле while может быть выполнен сле-

дующим образом: while(myDataReader.Read())

{

// Этот код будет однократно исполнен

// для каждой записи полученной выборки.

}

При чтении записи с помощью объекта DataReader значения от-

дельных полей доступны через индексатор класса, к элементам которо-

го можно обращаться по числовому индексу либо по имени поля, на-

пример: while (myDataReader.Read()){

object myObject = myDataReader[3];

object myOtherObject = myDataReader["CustomerID"];

}

В данном случае DataReader предоставляет все значения в виде

элементов типа object, хотя с помощью DataReader можно получать

и типизированные данные. После выполнения данного цикла, следует

вызвать метод Close(), чтобы закрыть DataReader, в противном слу-

чае объект DataReader будет удерживать монопольный доступ к ак-

тивному соединению, сделав его недоступным другим объектам: myDataReader.Close();

Page 189: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

189

Следующий пример демонстрирует перебор записей полученной

выборки записей для вывода содержимого одного из столбцов в окне

Console. Этот пример предполагает наличие объекта OleDbCommand с

именем myOleDbCommand, свойство Connection которого определяет

соединение с именем myConnection. // Открыть активное соединение,

myConnection.Ореn();

// Создать объект DataReader

System.Data.OleDb.OleDbDataReader myReader;

myReader = myOleDbCornmand.ExecuteReader();

// Вызвать метод Read перед попыткой чтения данных.

while (myReader.Read())

{

// Получить доступ к полю можно по имени

Console.WriteLine(myReader["Customers"].ToString());

}

myReader.Close(); //закрываем DataReader

myConnection.Close; //закрываем соединение

Извлечение типизированных данных с помощью DataReader

Объекты класса DataReader не только предоставляют данные

типа object, но и позволяют извлекать из полученной выборки и дан-

ные других типов с помощью специальных методов. Имена этих мето-

дов образуются из префикса Get и имени типа извлекаемых данных.

Например, метод, извлекающий значения типа Boolean, назван Get-

Boolean(). Например: string myString = myDataReader.GetBoolean(3);

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

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

поля, можно определить его порядковый номер, вызвав метод GetOr-

dinal(), например, так: int CustomerID;

string Customer;

CustomerID = myDataReader.GetOrdinal("CustomerID");

Customer = myDataReader.GetString(CustomerID);

10.3. Отсоединенный режим работы с базой данных Отсоединенный режим работы с БД реализуется с помощью двух

основных класса DataSet и DataAdapter.

Page 190: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

190

10.3.1. Класс DataSet

Класс является базовым классом, для работы с базами данных в

отсоединенном режиме. Объекты данного класса содержат некоторую

часть базы данных в оперативной памяти компьютера, изолированно от

реальной БД. В DataSet

можно загрузить данные из

любой БД, с которой мож-

но связаться с помощью

провайдеров. Объект Da-

taSet хранится в памяти и

его содержимое можно ме-

нять независимо от БД.

При необходимости объект

класса DataSet может ис-

пользоваться для обновле-

ния серверной БД. Объект

класса DataSet содержит

набор объектов DataTable

(этот набор может быть и пустым, то есть не содержать ни одного объ-

екта DataTable). Основным свойством данного класса явлется коллек-

ция Tables, которая содержит объекты класса DataTable.

Для создания и заполнения таблиц в объектах DataSet исполь-

зуются объекты класса DataAdapter, который будет рассмотрен ниже.

Для выполнения работы с таблицами объекта DataSet нужно получить

ссылку на нее в коллекции Tables[]. В качестве индекса можно ис-

пользовать либо индекс таблицы в коллекции, либо ее название. Напри-

мер, ссылку на таблицу с именем Orders можно получить следующим

образом: DataSet myDataSet = new DataSet();

// создание объекта da класса DataAdapter

da.Fill(myDataSet, "Orders");

DataTable tbl = myDataSet.Tables["Orders"];

10.3.2. Класс DataTable

Объекты класса DataTable представляют в оперативной памяти

таблицы базы данных. Основным свойством данного класса является

коллекция DataRows, в которой и хранятся объекты типа DataRow, со-

держащие данные записей таблицы БД. Также класс DataTable вклю-

чает коллекцию DataColumns, которая описывает столбцы таблицы.

Над содержимым коллекции DataRows объекта DataTable мож-

но выполнять следующие основные действия:

Рис. 10.2. Основные свойства класса DataSet

Page 191: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

191

изменять значения объектов DataRow;

добавлять объекты DataRow;

удалять объекты DataRow.

Однако сделанные изменения не будут переноситься в базу дан-

ных до тех пор, пока не будет выполнено ее обновление с помощью

объекта DataAdapter. При вызове метода Update() объекта DataA-

dapter на основе анализа сделанных изменений формируются SQL ко-

манды UPDATE, INSERT и DELETE, необходимые для обновления базы

данных.

Ограничения

Объекты Constraint определяют правила вставки строк в Da-

taTable и управления содержимым этих объектов. Ограничения быва-

ют двух типов – UniqueConstraint и ForeignKeyConstraint.

Ограничение UniqueConstraint запрещает добавлять в таблицу

дублирующиеся элементы, обычно с этим ограничением определяют

первичный ключ таблицы. ForeignKeyConstraint определяет прави-

ла обновления дочерних строк при изменении родительской строки.

Для создания ограничения сначала нужно создать экземпляр объ-

екта, представляющего ограничение соответствующего типа, а затем

добавляют его в коллекцию Constraints таблицы, на поле которого

налагается ограничение. Ограничение будет действовать, только если

значение свойства EnforceConstraints объекта DataSet установле-

но в true.

10.3.3. Класс DataAdapter

Объекты класса DataAdapter обеспечивают связь между источ-

ником данных и объектом DataSet, который создает экземпляр части

базы данных в оперативной памяти. Объекты DataAdapter способны

извлекать данные, заполнять объекты DataTable в DataSet и при не-

обходимости обновлять содержимое источника данных. В DataSet мо-

жет храниться несколько таблиц и для каждого объекта DataTable, до-

бавляемого к объекту DataSet, следует создавать собственный объект

DataAdapter.

В классе DataAdapter описаны четыре основных свойства, кото-

рым должны присваиваться ссылки на объекты класса Command:

SelectCommand – команда для выборки данных;

InsertCommand – команда для вставки новых данных;

UpdateCommand – команда для обновления данных;

DeleteCommand – команды для удаления данных.

Page 192: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

192

Создание объектов класса DataAdapter

Объекты класса Command, которые используются в объекте Da-

taAdapter можно создавать разными способами:

обычным способом с помощью операции new, как рассматрива-

лось ранее, и присваивать соответствующим свойствам;

использовать конструктор класса DataAdapter для создания ко-

манды выборки SelectCommand с помощью заданного оператора

SELECT;

если задана команда SelectCommand, то для автоматического

создания команд InsertCommand,UpdateCommand и Delete-

Command можно использовать вспомогательного класс Command-

Builder.

Обязательной командой в DataAdapter является команда Se-

lectCommand. Если адаптер не будет сохраняться изменения, сделан-

ные в объекте DataSet в БД, то другие команды можно не задавать.

Для создания этой команды, можно в конструктор класса DataAdapter

передать текст SQL оператора Select и ссылку на объект

Connection. Например: SqlDataAdapter prodAdapter = new SqlDataAdapter

("SELECT * FROM Products", prodConnection);

Для автоматического создания других команд (если это простые

операторы SQL), можно использовать вспомогательный класс Com-

mandBuilder (он также входит в состав провайдера и требует указа-

ния префикса). Он автоматически формирует команды вставки, обнов-

ления и удаления на основе заданного оператора SELECT. Для создания

всех этих команд достаточно передать ссылку на объект класса DataA-

dapter в конструктор класса CommandBuilder. Например: SqlDataAdapter adapter = new SqlDataAdapter();

adapter.SelectCommand =

new SqlCommand(queryString, connection);

SqlCommandBuilder builder =

new SqlCommandBuilder(adapter);

Извлечение данных с помощью объектов DataAdapter

Для заполнения объекта DataSet данными необходимо создать

объект DataAdapter, задать ему требуемые свойста (прежде всего Se-

lectCommand), а затем вызвать метод Fill(). Этот метод исполняет

команду, заданную свойством SelectCommand, с использованием со-

единения, указанного свойством Connection, создает и заполняет в

DataSet объект DataTable, данными, которые возвращает исполненная

Page 193: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

193

команда. Методу Fill() необходимо передать целевой объект, кото-

рым может быть DataSet либо DataTable, например: SqlDataAdapter oDA = new OleDbDataAdapter(conn);

// автоматически строим команды Insert, Update,Delete

SqlCommandBuilder oCBld =

new SqlCommandBuilder(oDA);

// создаем экземпляр класса DataSet

DataSet oDS = new DataSet();

// создаем и заполняем таблицу "Products"

oDA.Fill(oDS, "Products");

decimal newPrice =

(decimal) tbl.Rows[0]["UnitPrice"]+ 1.0M;

tbl.Rows[0]["UnitPrice"] = newPrice;

oDA.Update(oDS, "Products");

Отметим, что взаимодействия с объектом Connection не проис-

ходит. При исполнении команды, вызванной методом Fill(), соедине-

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

же закрывается. Таким образом, после извлечения данные становятся

отсоединенными и с ними можно работать в программе независимо от

БД, а при необходимости ее можно и обновить.

Для каждой таблицы с данными создается отдельный объект Da-

taAdapter. Если нужно загрузить содержимое нескольких таблиц в

один объект DataSet, следует задействовать несколько объектов Da-

taAdapter. Один и тот же объект DataSet может быть многократно

передан методу Fill(), при вызове которого у каждого объекта Da-

taAdapter сознается новый объект DataTable, заполняется данными

и добавляется к объекту DataSet.

Однако следует отметить, что при добавлении к объекту DataSet

связанных таблиц DataAdapter не создает связи между ними, поэтому

в DataSet их придется восстановить с помощью создания новых объек-

тов DataRelation.

10.3.4. Работа с объектами DataSet

Выборка записей из объектов DataTable

Можно получить массив записей типа DataRow из объекта

DataTable, которые удовлетворяют заданным условиям и отсортиро-

ваны заданным способом с помощью метода Select(string fltr,

string sort), где это fltr, описание условий отбора записей

(фильтра), а sort – это порядок сортироки (можно не задавать). Напри-

мер: string filterStr = "id = 1";

Page 194: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

194

DataRow[] view = emploees.Select(filterStr);

if(sotr.Length != 0){

// ...

}

Изменение данных в записях объектов DataTable

Изменение данных объекта DataRow выполняется путем задания

значений свойству Item объекта DataRow, представляющего значения

полей записи таблицы базы данных. Например: // Item - это свойство объекта DataRow по умолчанию

// поэтому ссылаться на него явно не обязательно.

myDataRow[2] = "продавец";

myDataRow["Customer"] = "Иванов А.И.";

Каждый объект DataRow поддерживает две версии состояния:

исходную версию, содержащую первоначальные значения Data-

Row;

измененную версию, содержащую все изменения, сделанные в ре-

зультате выполнения программы.

В любой момент с помощью вызова метода

RejectChanges()можно отменить (откатить) любые изменения, вне-

сенные в объект DataRow. Для внесения изменений в объект DataRow

нужно вызвать метод AcceptChanges(). В результате этого, модифи-

цированная версия объекта DataRow записывается поверх исходной: myDataRow.AcceptChanges();

Извлечение связанных записей

Объекты DataRelation позволяют извлекать строки, содержа-

щие родительские и дочерние поля. Для этого нужно вызвать методы

GetChildRows() или GetParentRow() объекта DataRow, которые

требуют передать им действительный объект DataRelation. Метод

GetChildRow() возвращает массив дочерних строк, заданных объек-

том DataRelation.

Метод GetParentRow() отличается от него лишь тем, что воз-

вращает только одну строку – родительскую, также заданную объектом

DataRelation. Вызов этих методов иллюстрируется следующим при-

мером, который предполагает наличие таблиц Customers и Orders,

связанных посредством объекта DataRelation с именем Customer-

sOrders; обе эти таблицы располагаются в объекте DataSet с именем

myDataSet: DataRow[] ChildRows;

DataRow ParentRow;

Page 195: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

195

//Вернуть все дочерние строки первой строки табл. Customers,

//заданные объектом DataRelation с именем CustomersOrders

ChildRows = myDataSet.Tables

["Customers"].Rows[1].GetChildRows(CustomersOrders);

// Вернуть дочернюю строку пятой строки таблицы Orders,

// заданную объектом DataRelation с именем

CustomersOrders.ParentRow = myDataSet.Tables

["Orders"].Rows[5].GetParentRow(CustomsrsOrders);

Создание новых строк в объектах DataTable

Для добавления новых строк в таблицу DataTable нужно доба-

вить новые объекты DataRow в коллекцию Rows данной таблицы. Объ-

екты DataRow представляют строки таблицы БД. Создавать объекты

класса DataRow с использованием операции new нельзя, вместо этого

следует вызвать метод NewRow() объекта DataTable, к которому до-

бавляется объект DataRow. Например: DataRow myRow ;

myRow = tbl.NewRow();

Созданный объект DataRow можно заполнить данными, введен-

ными пользователями или полученными из любого источника данных

(коллекции или массива). Для доступа к полям записи используется

коллекция Item объекта DataRow. Для занесения заполненного объекта

DataRow в таблицу, его нужно добавить к коллекции Rows объекта Da-

taTable. Следующий пример показывает заполнение объекта DataRow

данными и сохранение его в таблице: DataSet ds = new DataSet();

DataTable tbl = ds.Tables["MyBooks"];

DataRow row = tbl.NewRow();

myRow ["id"] = 44;

myRow ["authir"] = "Терентьев И.А.";

myRow ["year"] = 2003;

tbl.Rows.Add(row);

Удаление записей из объектов DataTable

Для удаления записей из таблицы можно использовать метод

Remove() коллекции Rows класса DataTable. Например: // здесь tbl объект класса DataTable

tbl.Rows.Remove(row);

Кроме этого, можно использовать метод Delete() класса DataRow.

Например: // здесь row объект класса DataRow

row.Delete();

Page 196: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

196

Обновление базы данных

Закончив работу с копией базы данных в объекте DataSet, можно

обновить БД, вызвав метод Update() соответствующего объекта Da-

taAdapter, с помощью которого выполянялось заполнение данной

таблицы. Например: DataSet ds = new DataSet();

// создаем и заполняем DataAdapter da

Da.Fill(ds, "Product");

// выполняем изменения таблицы Product в ds

myDataAdapter.Update(ds, "Product");

myOtherDataAdapter.Update();

В результате вызова данного метода все изменения, внесенные в

объект DataSet, переносятся в реальную БД. При этом можно указать

объект DataSet, DataTable или массив объектов DataRows, подле-

жащий обновлению: myDataAdapter.Update(myDataSet);

myDataAdapter.Update(myDataTable);

myDataAdapter.Update(myDataRows);

10.3.5. Класс DataView

Класс DataView позволяет создать разные (альтернативные)

представления (view) одной и той же таблицы. Он включает дополни-

тельные возможности для сортировки и фильтрации записей таблицы.

Основными свойствами данного класса являются RowFilter (фильтр),

которое задает условие отбора записей из DataTable и Sort (сорти-

ровка), которое задает правило сортировки записей (перечисление по-

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

сортировки: по умолчанию – по возрастанию; если задано DESC – по

убыванию). Например: DataView view = new DataView(myDT);

view.Sort = "LastName ASC, FirstName ASC, Salary DESC";

view.RowFilter = "City = 'Томск'";

Объект класса DataView связать с элементами графического интерфей-

са, например, DataGridView используя свойства DataSource.

Пример создания и связывания с DataView: // создаем объект DataView для объекта DataTable

DataView view = new DataView(employee);

// задаем правила сортировки и условия фильтра отбора

view.Sort = "LastName ASC, FirstName ASC, Salary DESC";

view.RowFilter = "LastName like 'A%' and Salary > 15";

// если есть ЭУ для отображения DataGridView

dgv.DataSource = view;

Page 197: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

197

10.4. Типизированные классы DataSet Класс DataSet не являются строго типизированным, так как в

нем каждый элемент данных имеет тип object. В связи с этим, для вы-

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

этот объект следует преобразовать в соответствующий тип (выполнять

кастинг). При использовании нестрого типизированных переменных ве-

роятны ошибки из-за несоответствия типов, которые весьма непросто

найти и устранить. ADO.NET также поддерживает типизированный

класс DataSet, альтернатива применению нестрого типизированных

переменных.

У объекта типизированного класса DataSet, как можно предпо-

ложить по его названию, все элементы строго типизированы (имеют

конкретный тип). На таблицы и поля такого объекта DataSet можно

ссылаться по их дружественным именам, представляющим реальные

имена таблиц и столбцов, с которыми выполняется работа; их значения

доступны в виде значений соответствующих типов, а не объектов. Это

дает приложению целый ряд преимуществ.

Во-первых, код программы становится более понятным и его

удобнее сопровождать. Во-вторых, ошибки из-за несоответствия типов

обнаруживаются в период компиляции, а не в период выполнения – это

экономит время, необходимое для тестирования. Наконец, полные име-

на членов коллекций (например, Tables) допустимо заменять их дру-

жественными именами, при этом в период разработки, имена типизиро-

ванных членов данных отображаются в окнах среды разработки благо-

даря технологии Intellisense.

Ниже приводится пример эквивалентных строк кода, использую-

щих типизированные и не типизированные объекты DataSet. Оба при-

мера возвращают значения поля OrderID первой записи таблицы Or-

ders в результирующем наборе dsOrders. Для не типизированного

объекта DataSet получить это значение можно сделать следующим об-

разом: string myOrder =

(string)dsOrders.Tables["Orders"].Rows[0]["OrderID"];

А типизированных объектов DataSet выполняется так, как пока-

зано ниже: string myOrder = dsOrders.Orders[0].OrderID;

Как видно, второй фрагмент существенно проще, более понятный

и не требует явного преобразования типов. В действительности типизи-

рованный DataSet – это экземпляр совсем другого класса, производно-

го от DataSet. Структура этого класса определяется файлом схемы

Page 198: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

198

XML (XSD-файлом), в котором описаны различные особенности струк-

туры объекта DataSet, в том числе имена таблиц и столбцов. Для соз-

дания нового типизированного объекта DataSet требуется файл схемы,

но его удается создать, только если структура данных, с которыми вам

предстоит работать, известна заранее. Чтобы генерировать типизиро-

ванный объект DataSet, нужно выполнить в Visual Studio команду Da-

ta->Add New Source.... В результате выполнения этой команды

появится диалоговое окно мастера “Data Source Configuration Wizard”, с

помощью которого можно создать типизированный объект DataSet.

По окончанию работы с мастером будет сгенерирован XSD-файл с кор-

ректной схемой, необходимый для создания объекта DataSet.

Чтобы заполнить строго типизированный объект DataSet, ис-

пользуется метод Fill() каждого объекта DataAdapter, чьи данные

требуется добавить в объект DataSet, передав этот DataSet при вызо-

ве как целевой объект.

Page 199: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

199

Список литературы

1. Биллиг В.А. Основы программирования на C#. – М.: Бином. Лабора-

тория знаний, 2006. – 483 с.

2. Нортроп Т., Уилдермьюс Ш., Райан Б. Основы разработки приложе-

ний на платформе Microsoft .Net Framework / Пер. с англ. – М.: Изда-

тельско-торговый дом «Русская Редакция», 2007. – 864 с.

3. Троелсен Э. Язык программирования С# 2005 и платформа .NET 2.0,

3-е издание: Пер. с англ. – М.: ООО “И.Д. Вильямс”, 2007. – 1168 с.

4. Введение в С#. Библиотека программиста: пер с англ. – СПб.; Питер,

2001г. – 304 с.

5. Просиз Дж. Программирование для Microsoft .NET / Пер. с англ. – М.:

Издательско–торговый дом «Русская Редакция», 2003. – 704 стр.: ил.

6. Либерти Д.Программирование на C# / Пер. с англ. – М: Символ–

Плюс, 2003. – 840 c.

7. Разработка Windows-приложений на Microsoft Visual Basic .NET и

Microsoft Visual C# .NET. Учебный курс MCAD/MCSD / Пер. с англ. –

М.: Издательско-торговый дом «Русская Редакция», 2003. – 512 с.

Page 200: C# Programming Textbookwindow.edu.ru/resource/030/76030/files/PosobieVUMIP.pdf · имеющиеся и создавать собственные типы данных. В .Net под

200

Учебное пособие

ТУЗОВСКИЙ Анатолий Фѐдорович

ВЫСОКОУРОВНЕВЫЕ МЕТОДЫ ИНФОРМАТИКИ И ПРОГРАММИРОВАНИЯ

Учебно-методическое пособие

Издано в авторской редакции

Научный редактор доктор технических наук,

профессор А.Ф. Тузовский Дизайн обложки А.И. Сидоренко

Отпечатано в Издательстве ТПУ в полном соответствии

с качеством предоставленного оригинал-макета

Подписано к печати 05.11.2009. Формат 60х84/16. Бумага «Снегурочка». Печать XEROX. Усл.печ.л. 11,63. Уч.-изд.л. 10,53.

Заказ 1421-09 Тираж 40 экз.

Томский политехнический университет

Система менеджмента качества

Томского политехнического университета сертифицирована

NATIONAL QUALITY ASSURANCE по стандарту ISO 9001:2000

.634050,г.Томск,пр.Ленина,30. Тел./факс:8(3822)56-35-35,www.tpu.ru


Recommended