|
1 Особенности среды .NET Программист пишет программу, компьютер ее выполняет. Программа создается на языке, понятном человек}', а компьютер умеет исполнять только программы, написанные на его языке — в машинных кодах. Совокупность средств, с помощью которых программы пишут, корректируют, преобразуют в машинные коды, отлаживают и запускают, называют средой разработки, или оболочкой. Среда разработки обычно содержит: текстовый редактор, предназначенный для ввода и корректировки текста программы; компилятор, с помощью которого программа переводится с языка, на котором она написана, в машинные коды; средства отладки и запуска программ; общие библиотеки, содержащие многократно используемые элементы программ; справочную систему и другие элементы. Под платформой понимается нечто большее, чем среда разработки для одного языка. Платформа .NET (произносится «дотнет») включает не только среду разработки для нескольких языков программирования, называемую Visual Studio.NET, но и множество других средств, например, механизмы поддержки баз данных, электронной почты и коммерции. В эпоху стремительного развития Интернета — глобальной информационной сети, объединяющей компьютеры разных архитектур, важнейшими задачами при создании программ становятся: □ переносимость — возможность выполнения на различных типах компьютеров; Q безопасность — невозможность несанкционированных действий; □ надежность — способность выполнять необходимые функции в предопреде ленных условиях; средний интервал между отказами; использование готовых компонентов — для ускорения разработки; □ межъязыковое взаимодействие — возможность применять одновременно несколько языков программирования. Платформа .NET позволяет успешно решать все эти задачи. Для обеспечения переносимости компиляторы, входящие в состав платформы, переводят программу не в машинные коды, а в промежуточный язык (Microsoft Intermediate Language, MSIL, или просто IL), который не содержит команд, зависящих от языка, операционной системы и типа компьютера. Программа на этом языке выполняется не самостоятельно, а под управлением системы, которая называется общеязыковой средой выполнения (Common Language Runtime, CLR). Среда CLR может быть реализована для любой операционной системы. При выполнении программы CLR вызывает так называемый JIT-компилятор, переводящий код с языка IL в машинные команды конкретного процессора, которые немедленно выполняются. JIT означает «just in time», что можно перевести как «вовремя», то есть компилируются только те части программы, которые требуется выполнить в данный момент. Каждая часть программы компилируется один раз и сохраняется в кэше1 для дальнейшего использования. | 2 Принципы ООП Принципы ООП проще всего понять на примере программ моделирования. В реальном мире каждый предмет или процесс обладает набором статических и динамических характеристик, иными словами, свойствами и поведением. Поведение объекта зависит от его состояния и внешних воздействий. Например, объект «автомобиль» никуда не поедет, если в баке нет бензина, а если повернуть руль, изменится положение колес. Понятие объекта в программе совпадает с обыденным смыслом этого слова: объект представляется как совокупность данных, характеризующих его состояние, и функций их обработки, моделирующих его поведение. Вызов функции на выполнение часто называют посылкой сообщения объекту'. При создании объектно-ориентированной программы предметная область представляется в виде совокупности объектов. Выполнение программы состоит в том, что объекты обмениваются сообщениями. Это позволяет использовать при программировании понятия, более адекватно отражающие предметную область. При представлении реального объекта с помощью программного необходимо выделить в первом его существенные особенности. Их список зависит от цели моделирования. Например, объект «крыса» с точки зрения биолога, изучающего миграции, ветеринара или, скажем, повара будет иметь совершенно разные характеристики. Выделение существенных с той или иной точки зрения свойств называется абстрагированием. Таким образом, программный объект — это абстракция. Важным свойством объекта является его обособленность. Детали реализации объекта, то есть внутренние структуры данных и алгоритмы их обработки, скрыты от пользователя объекта и недоступны для непреднамеренных изменений. Объект используется через его интерфейс — совокупность правил доступа. Скрытие деталей реализации называется инкапсуляцией (от слова «капсула»). Ничего сложного в этом понятии нет: ведь и в обычной жизни мы пользуемся объектами через их интерфейсы. Сколько информации пришлось бы держать в голове, если бы для просмотра новостей надо было знать устройство телевизора! Таким образом, объект является «черным ящиком*, замкнутым по отношению к внешнему миру. Это позволяет представить программу в укрупненном виде -на уровне объектов и их взаимосвязей, а следовательно, управлять большим объемом информации и успешно отлаживать сложные программы. Сказанное можно сформулировать более кратко и строго: объект — это инкапсулированная абстракция с четко определенным интерфейсом. Инкапсуляция позволяет изменить реализацию объекта без модификации основной части программы, если его интерфейс.остался прежним. Простота модификации является очень важным критерием качества программы, ведь любой программный продукт в течение своего жизненного цикла претерпевает множество изменений и дополнений. Кроме того, инкапсуляция позволяет использовать объект в другом окружении и быть уверенным, что он не испортит не принадлежащие ему области памяти, а также создавать библиотеки объектов для применения во многих программах. Каждый год в мире пишется огромное количество новых программ, и важнейшее значение приобретает возможность многократного использования кода. Преимущество объектно-ориентированного программирования состоит в том, что для объекта можно определить наследников, корректирующих или дополняющих его поведение. При этом нет необходимости не только повторять исходный код родительского объекта, но даже иметь к нему доступ. | 4 Понятие класса Для представления объектов в языках С#, Java, C++, Delphi и т. п. используется понятие класс, аналогичное обыденному смыслу этого слова в контексте «класс членистоногих», «класс млекопитающих», «класс задач» и т. п. Класс является обобщенным понятием, определяющим характеристики и поведение некоторого множества конкретных объектов этого класса, называемых экземплярами класса. «Классический» класс содержит данные, задающие свойства объектов класса, и функции, определяющие их поведение. В последнее время в класс часто добавляется третья составляющая — события, на которые может реагировать объект класса1. Все классы библиотеки .NET, а также все классы, которые создает программист в среде .NET, имеют одного общего предка — класс object и организованы в единую иерархическую структуру. Внутри нее классы логически сгруппированы в так называемые пространства имен, которые служат для упорядочивания имен классов и предотвращения конфликтов имен: в разных пространствах имена могут совпадать. Пространства имен могут быть вложенными, их идея аналогична знакомой вам иерархической структуре каталогов на компьютере. Любая программа, создаваемая в .NET, использует пространство имен System. В нем определены классы, которые обеспечивают базовую функциональность, например, поддерживают выполнение математических операций, управление памятью и ввод-вывод. Обычно в одно пространство имен объединяют взаимосвязанные классы. Например, пространство System.Net содержит классы, относящиеся к передаче данных по сети, System.Windows.Forms — элементы графического интерфейса пользователя, такие как формы, кнопки и т. д. Имя каждого пространства имен представляет собой неделимую сущность, однозначно его определяющую. Последнее, о чем необходимо поговорить, прежде чем начать последовательное изучение языка С#, — среда разработки Visual Studio.NET. | 5 Конструкторы и деструкторы Конструктор предназначен для инициализации объекта. Он вызывается автоматически при создании объекта класса с помощью операции new. Имя конструктора совпадает с именем класса. Ниже перечислены свойства конструкторов: □ Конструктор не возвращает значение, даже типа void. * Класс может иметь несколько конструкторов с разными параметрами для раз ных видов инициализации. * Если программист не указал ни одного конструктора или какие-то поля не были инициализированы, полям значимых типов присваивается нуль, полям ссылочных типов — значение null. * Конструктор, вызываемый без параметров, называется конструктором по умолчанию. До сих пор мы задавали начальные значения полей класса при описании класса (см., например, листинг 5.1). Это удобно в том случае, когда для всех экземпляров класса начальные значения некоторого поля одинаковы. Если же при создании объектов требуется присваивать полю разные значения, это следует делать в конструкторе. В листинге 5.6 в класс Demo добавлен конструктор, а поля сделаны закрытыми (ненужные в данный момент элементы опущены). В программе создаются два объекта с различными значениями полей. Деструкторы В С# существует специальный вид метода, называемый деструктором. Он вызывается сборщиком мусора непосредственно перед удалением объекта из памяти. В деструкторе описываются действия, гарантирующие корректность последующего удаления объекта, например, проверяется, все ли ресурсы, используемые объектом, освобождены (файлы закрыты, удаленное соединение разорвано и т. п.). Синтаксис деструктора: [ атрибуты ] [ extern ] -иияклассаО тело Как видно из определения, деструктор не имеет параметров, не возвращает значения и не требует указания спецификаторов доступа. Его имя совпадает с именем класса и предваряется тильдой (~), символизирующей обратные по отношению к конструктору действия. Тело деструктора представляет собой блок или просто точку с запятой, если деструктор определен как внешний (extern). Сборщик мусора удаляет объекты, на которые нет ссылок. Он работает в соответствии со своей внутренней стратегией в неизвестные для программиста моменты времени. Поскольку деструктор вызывается сборщиком мусора, невозможно гарантировать, что деструктор будет обязательно вызван в процессе работы программы. Следовательно, его лучше использовать только для гарантии освобождения ресурсов, а «штатное» освобождение выполнять в другом месте программы. Применение деструкторов замедляет процесс сборки мусора. | 6 NEW.THIS Ключевое слово this Каждый объект содержит свой экземпляр полей класса. Методы находятся в памяти в единственном экземпляре и используются всеми объектами совместно, поэтому необходимо обеспечить работу методов нестатических экземпляров с полями именно того объекта, для которого они были вызваны. Для этого в любой нестатический метод автоматически передается скрытый параметр this, в котором хранится ссылка на вызвавший функцию экземпляр. В явном виде параметр this применяется для того, чтобы возвратить из метода ссылку на вызвавший объект, а также для идентификации поля в случае, если его имя совпадает с именем параметра метода, например: class Demo { double у; public Demo TO // метод возвращает ссылку на экземпляр { return this; j public void Sety< double у ) f this.у - у: // полю у присваивается значение параметра у } ) | 7 наследование Класс в С# может иметь произвольное количество потомков и только одного предка. При описании класса имя его предка записывается в заголовке класса после двоеточия. Если имя предка не указано, предком считается базовый класс всей иерархии System.Object: [ атрибуты ] [ спецификаторы ] class инякласса [ : предки ] тело класса Примечание: Обратите внимание на то, что слово «предки» присутствует в описании класса во множественном числе, хотя класс может иметь только одного предка. Причина в том, что класс наряду с единственным предком может наследовать от интерфейсов — специального вида классов, не имеющих реализации. Интерфейсы рассматриваются в следующей главе. Конструкторы не наследуются, поэтому производный класс должен иметь собственные конструкторы. Порядок вызова конструкторов определяется приведенными далее правилами: * Если в конструкторе производного класса явный вызов конструктора базового класса отсутствует, автоматически вызывается конструктор базового класса без параметров. Это правило использовано в первом из конструкторов класса Daemon. *Для иерархии, состоящей из нескольких уровней, конструкторы базовых классов вызываются, начиная с самого верхнего уровня. После этого выполняются конструкторы тех элементов класса, которые являются объектами, в порядке их объявления в классе, а затем исполняется конструктор класса. Таким образом, каждый конструктор инициализирует свою часть объекта. *Если конструктор базового класса требует указания параметров, он должен быть явным образом вызван в конструкторе производного класса в списке инициализации (это продемонстрировано в конструкторах, вызываемых в операторах 1 и 2). Вызов выполняется с помощью ключевого слова base. Вызывается та версия конструктора, список параметров которой соответствует списку аргументов, указанных после слова base. Поля, методы и свойства класса наследуются, поэтому при желании заменить элемент базового класса новым элементом следует явным образом указать компилятору свое намерение с помощью ключевого слова new1. | 8 Виртуальные методы При раннем связывании программа, готовая для выполнения, представляет собой структуру, логика выполнения которой жестко определена. Если же требуется, чтобы решение о том, какой из одноименных методов разных объектов иерархии использовать, принималось в зависимости от конкретного объекта, для которого выполняется вызов, то заранее жестко связывать эти методы с остальной частью кода нельзя. Следовательно, надо каким-то образом дать знать компилятору, что эти методы будут обрабатываться по-другому. Для этого в С# существует ключевое слово virtual. Оно записывается в заголовке метода базового класса, например: virtual public void PassportC) ... Слово virtual в переводе с английского значит «фактический». Объявление метода виртуальным означает, что все ссылки на этот метод будут разрешаться по факту его вызова, то есть не на стадии компиляции, а во время выполнения программы. Этот механизм называется поздним связыванием. Для его реализации необходимо, чтобы адреса виртуальных методов хранились там, где ими можно будет в любой момент воспользоваться, поэтому компилятор формирует для этих методов таблицу виртуальных методов (Virtual Method Table, VMT). В нее записываются адреса виртуальных методов (в том числе унаследованных) в порядке описания в классе. Для каждого класса создается одна таблица. Каждый объект во время выполнения должен иметь доступ к VMT. Обеспечение этой связи нельзя поручить компилятору, так как она должна устанавливаться во время выполнения программы при создании объекта. Поэтому связь экземпляра объекта с VMT устанавливается с помощью специального кода, автоматически помещаемого компилятором в конструктор объекта. Если в производном классе требуется переопределить виртуальный метод, используется ключевое слово override, например: override public void PassportC) ... Переопределенный виртуальный метод должен обладать таким же набором параметров, как и одноименный метод базового класса. Это требование вполне естественно, если учесть, что одноименные методы, относящиеся к разным классам, могут вызываться из одной и той же точки программы. | 9 Абстрактные классы При создании иерархии объектов для исключения повторяющегося кода часто быва-ет логично выделить их общие свойства в один родительский класс. При этом может оказаться, что создавать экземпляры такого класса не имеет смысла, потому что никакие реальные объекты им не соответствуют. Такие классы назьтают абстрактными. Абстрактный класс служит только Зля порождения потомков. Как правило, в нем задается набор методов, которые каждый из потомков будет реализовывать по-своему. Абстрактные классы предназначены для представления общих понятий, которые предполагается конкретизировать в производных классах. Абстрактный класс задает интерфейс для всей иерархии, при этом методам класса может не соответствовать никаких конкретных действий. В этом случае методы имеют пустое тело и объявляются со спецификатором abstract. ПРИМЕЧАНИЕ Абстрактный класс может содержать и полностью определенные методы, в отличие от сходного с ним по предназначению специального вида класса, называемого интерфейсом. Интерфейсы рассматриваются в следующей главе. Если в классе есть хотя бы один абстрактный метод, весь класс также должен быть описан как абстрактный Абстрактные классы используются при работе со структурами данных, предназначенными для хранения объектов одной иерархии, и в качестве параметров методов. Если класс, производный от абстрактного, не переопределяет все абстрактные методы, он также должен описываться как абстрактный. Можно создать метод, параметром которого является абстрактный класс. На место этого параметра при выполнении программы может передаваться объект любого производного класса. Это позволяет создавать полиморфные методы, работающие с объектом любого типа в пределах одной иерархии. Полиморфизм в различных формах является мощным и широко применяемым инструментом ООП. | 10-11 типы данных Целые типы, а также символьный, вещественные и финансовый типы можно объединить иод названием арифметических типов. Внутреннее представление величины целого типа — целое число в двоичном коде. В знаковых типах старший бит числа интерпретируется как знаковый (0 — положительное число, 1 — отрицательное). Отрицательные числа чаще всего представляются в так называемом дополнительном коде. Для преобразования числа в дополнительный код все разряды числа, за исключением знакового, инвертируются, затем к числу прибавляется единица, и знаковому биту тоже присваивается единица. Беззнаковые типы позволяют представлять только положительные числа, поскольку старший разряд рассматривается как часть кода числа. Название Логический тип | Ключево) слово bool | ■ Тип .NET Boolean | Диапазон значений true, false | Описание | Размер, битов | Целые типы | sbyte | SByte | От -128 до 127 | Со знаком | 8 | | byte | Byte | От 0 до 255 | Без знака | 8 | | short | Int 16 | От -32 768 до 32 767 | Со знаком | 16 | | ushort | UInt16 | От 0 до 65 535 | Без знака | 16 | | int | Int32 | От -2 ■ 109 до 2 109 | Со знаком | 32 | | uint | UInt32 | От 0 до 4 ■ 109 | Без знака | 32 | | long | Int64 | От-9х Ю,8до 9 · 10й | Со знаком | 64 | | ulong | UInt64 | ОтОдо 18- 1018 | Без знака | 64 | Символьный | char | Char | От U+0000 | Unicode- | 16 | тип | | | дои+ffff | символ | | Вещественные1 | float | Single | От 1.5 - 10 45 до 3.4 • 1038 | 7 цифр | 32 | | double | Double | От 5.0 ■ Ю-324 до 1.7 • 10308 | 15-16 цифр | 64 | Финансовый тип | decimal | Decimal | От 1.0 ■ 10'28 до 7.9 ■ 1028 | 28-29 цифр | 128 | Строковый тип | string | String | Длина ограничена объемом доступной памяти | Строка из Unicode-символов | | Тип object | object | Object | Можно хранить все что угодно | Всеобщий предок | | | | | | | | | | | | | | | 12 Массивы До настоящего момента мы использовали в программах простые переменные. При этом каждой области памяти, выделенной для хранения одной величины, соответствует свое имя. Если переменных много, программа, предназначенная для их обработки, получается длинной и однообразной. Поэтому в любом процедурном языке есть понятие массива — ограниченной совокупности однотипных величин. Элементы массива имеют одно и то же имя, а различаются порядковым номером (индексом). Это позволяет компактно записывать множество операций с помощью циклов. Массив относится к ссылочным типам данных, то есть располагается в динамической области памяти, поэтому создание массива начинается с выделения памяти под его элементы. Элементами массива могут быть величины как значимых, так и ссылочных типов (в том числе массивы). Массив значимых типов хранит значения, массив ссылочных типов — ссылки на элементы. Всем элементам при создании массива присваиваются значения по умолчанию: нули для значимых типов и null — для ссылочных. На рис. 6.1 представлен массив, состоящий из пяти элементов любого значимого типа, например int или double, а рис. 6.2 иллюстрирует организацию массива из элементов ссылочного типа. Вот, например, как выглядят операторы создания массива из 10 целых чисел и массива из 100 строк: Int[ ] w = new int[10]; Stnng[ ] z - new string[100]; Элементы массива нумеруются с нуля, поэтому максимальный номер элемента всегда на единицу меньше размерности (например, в описанном выше массиве w элементы имеют индексы от 0 до 9). Для обращения к элементу массива после имени массива указывается номер элемента в квадратных скобках. | 13 Преобразование типов При вычислении выражений может возникнуть необходимость в преобразовании типов. Если операнды, входящие в выражение, одного типа и операция для этого типа определена, то результат выражения будет иметь тот же тип. Если операнды разного типа и/или операция для этого типа не определена, перед вычислениями автоматически выполняется преобразование типа по правилам, обеспечивающим приведение более коротких типов к более длинным для сохранения значимости и точности. Автоматическое (неявное) преобразование возможно не всегда, а только если при этом не может случиться потеря значимости. Если неявного преобразования из одного типа в другой не существует, программист может задать явное преобразование типа с помощью операции (тип)х. Его результат остается на совести программиста. Явное преобразование рассматривается в этой главе немного позже. ВНИМАНИЕ Арифметические операции не определены для более коротких, чем int, типов. Это означает, что если в выражении участвуют только величины типов sbyte, byte, short и ushort, перед выполнением операции они будут преобразованы в int. Таким образом, результат любой арифметической операции имеет тип не менее int. Правила неявного преобразования. Если один из операндов имеет тип, изображенный на более низком уровне, чем другой, то он приводится к типу второго операнда при наличии пути между ними. Если пути нет, возникает ошибка компиляции. Если путей несколько, выбирается наиболее короткий, не содержащий пунктирных линий. Преобразование выполняется не последовательно, а непосредственно из исходного типа в результирующий. | 18 Операторы цикла Операторы цикла используются для вычислений, повторяющихся многократно. В С# имеется четыре вида циклов: цикл с предусловием while, цикл с постусловием repeat, цикл с параметром for и цикл перебора foreach. Каждый из них состоит из определенной последовательности операторов. Блок, ради выполнения которого и организуется цикл, называется телом цикла. Остальные операторы служат для управления процессом повторения вычислений: это начальные установки, проверка условия продолжения цикла и модификация параметра цикла (рис. 4.4). Один проход цикла называется итерацией. Начальные установки служат для того, чтобы до входа в цикл задать значения переменных, которые в нем используются. Проверка условия продолжения цикла выполняется на каждой итерации либо до тела цикла (тогда говорят о цикле с предусловием, схема которого показана на рис. 4.4, а), либо после тела цикла (цикл с постусловием, рис. 4.4. б). Разница между ними состоит в том, что тело цикла с постусловием всегда выполняется хотя бы один раз, после чего проверяется, надо ли его выполнять еще раз. Проверка необходимости выполнения цикла с предусловием делается до тела цикла, поэтому возможно, что он не выполнится ни разу. Параметром цикла называется переменная, которая используется при проверке условия продолжения цикла и принудительно изменяется на каждой итерации, причем, как правило, на одну и ту же величину. Если параметр цикла целочисленный, он называется счетчиком цикла. Количество повторений такого цикла можно определить заранее. Параметр есть не у всякого цикла. В так называемом итеративном цикле условие продолжения содержит переменные, значения которых изменяются в цикле по рекуррентным формулам1. Цикл завершается, если условие его продолжения не выполняется. Возможно принудительное завершение как текущей итерации, так и цикла в целом. Для этого служат операторы break, continue, return и goto (см. раздел «Операторы передачи управления», с. 83). Передавать управление извне внутрь цикла запрещается (при этом возникает ошибка компиляции). | 19 Операторы перехода Оператор goto Оператор безусловного перехода goto используется в одной из трех форм: goto нетка; goto case константноевыражение; goto default; В теле той же функции должна присутствовать ровно одна конструкция вида метка: оператор; Оператор goto метка передает управление на помеченный оператор. Метка — это обычный идентификатор, областью видимости которого является функция, в теле которой он задан. Метка должна находиться в той же области видимости, что и оператор перехода. Использование этой формы оператора безусловного перехода оправдано в двух случаях: □ принудительный выход вниз по тексту программы из нескольких вложенных циклов или переключателей; Q переход из нескольких точек функции вниз по тексту в одну точку (например, если перед выходом из функции необходимо всегда выполнять какие-либо действия). В остальных случаях для записи любого алгоритма существуют более подходящие средства, а использование оператора goto приводит только к усложнению структуры программы и затруднению отладки. Применение этого оператора нарушает принципы структурного и модульного программирования, по которым все блоки, образующие программу, должны иметь только один вход и один выход. Вторая и третья формы оператора goto используются в теле оператора выбора switch. Оператор goto case константное_выражение передает управление на соответствующую константному выражению ветвь, а оператор goto default — на ветвь default. Надо отметить, что реализация оператора выбора в С# на редкость неудачна, и наличие в нем оператора безусловного перехода затрудняет понимание программы, поэтому лучше обходиться без него. | 21 Свойства Свойства служат для организации доступа к полям класса. Как правило, свойство связано с закрытым полем класса и определяет методы его получения и установки. Синтаксис свойства: [ атрибуты ] [ спецификаторы ] тип инясвойства { [ get коддоступа ] [ set код_доступа ] } Значения спецификаторов для свойств и методов аналогичны. Чаще всего свойства объявляются как открытые (со спецификатором publ i с), поскольку они входят в интерфейс объекта. Код доступа представляет собой блоки операторов, которые выполняются при получении (get) или установке (set) свойства. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, свойство доступно только для чтения (read-only), если отсутствует часть get, свойство доступно только для записи (write-only). В версии С# 2.0 введена удобная возможность задавать разные уровни доступа для частей get и set. Например, во многих классах возникает потребность обеспечить неограниченный доступ для чтения и ограниченный — для записи. Спецификаторы доступа для отдельной части должны задавать либо такой же, либо более ограниченный доступ, чем спецификатор доступа для свойства в целом. Например, если свойство описано как publ ic, его части могут иметь любой спецификатор доступа, а если свойство имеет доступ protected internal, его части могут объявляться как internal, protected или private. Синтаксис свойства в версии 2.0 имеет вид [ атрибуты ] [ спецификаторы ] тип инясвойства { [ [ атрибуты ] [ спецификаторы ] get коддоступа ] [ [ атрибуты ] [ спецификаторы ] set коддоступа ] } | 22 Класс Object Корневой класс System.Object всей иерархии объектов .NET, называемый в С# object, обеспечивает всех наследников несколькими важными методами. Производные классы могут использовать эти методы непосредственно или переопределять их. Класс object часто используется и непосредственно при описании типа параметров методов для придания им общности, а также для хранения ссылок на объекты различного типа — таким образом реализуется полиморфизм. Открытые методы класса System.Object перечислены ниже. * Метод Equals с одним параметром возвращает значение true, если параметр и вызывающий объект ссылаются на одну и ту же область памяти. Син таксис: public virtual bool EqualsC object obj ); * Метод Equals с двумя параметрами возвращает значение true, если оба пара метра ссылаются на одну и ту же область памяти. Синтаксис: public static bool EqualsC object obi. object ob2 ); * Метод GetHashCode формирует хеш-код объекта и возвращает число, однознач но идентифицирующее объект. Это число используется в различных структу рах и алгоритмах библиотеки. Если переопределяется метод Equals, необходи мо перегрузить и метод GetHashCode. Подробнее о хеш-кодах рассказывается в разделе «Абстрактные структуры данных» (см. с. 291). Синтаксис: public virtual int GetHashCodeO; * Метод Get Type возвращает текущий полиморфный тип объекта, то есть не тип ссылки, а тип объекта, на который она в данный момент указывает. Возвра щаемое значение имеет тип Туре. Это абстрактный базовый класс иерархии, использующийся для получения информации о типах во время выполнения1. Синтаксис: public Type GetTypeC): * Метод ReferenceEquals возвращает значение true, если оба параметра ссыла ются на одну и ту же область памяти. Синтаксис: public static bool( object obi, object ob2 ); * Метод ToString по умолчанию возвращает для ссылочных типов полное имя класса в виде строки, а для значимых — значение величины, преобразованное в строку. Этот метод переопределяют для того, чтобы можно было выводить информацию о состоянии объекта. Синтаксис: public virtual string ToStringC) | 24 Структуры Структура — тип данных, аналогичный классу, но имеющий ряд важных отличий от него: Q структура является значимым, а не ссылочным типом данных, то есть экземпляр структуры хранит значения своих элементов, а не ссылки на них, и располагается в стеке, а не в хипе; Q структура не может участвовать в иерархиях наследования, она может только реализовывать интерфейсы; в структуре запрещено определять конструктор по умолчанию, поскольку он определен неявно и присваивает всем ее элементам значения по умолчанию (нули соответствующего типа); в структуре запрещено определять деструкторы, поскольку это бессмысленно. ПРИМЕЧАНИЕ Строго говоря, любой значимый тип С# является структурным. Отличия от классов обусловливают область применения структур: типы данных, имеющие небольшое количество полей, с которыми удобнее работать как со значениями, а не как со ссылками. Накладные расходы на динамическое выделение памяти для небольших объектов могут весьма значительно снизить быстродействие программы, поэтому их эффективнее описывать как структуры, а не как классы. ПРИМЕЧАНИЕ С другой стороны, передача структуры в метод по значению требует и дополнительного времени, и дополнительной памяти. Синтаксис структуры: [ атрибуты ] [ спецификаторы ] struct иняструктуры [ : интерфейсы ] тело структуры [ ; ] Спецификаторы структуры имеют такой же смысл, как и для класса, причем из спецификаторов доступа допускаются только public, internal и private (последний — только для вложенных структур). Интерфейсы, реализуемые структурой, перечисляются через запятую. Тело структуры может состоять из констант, полей, методов, свойств, событий, индексаторов, операций, конструкторов и вложенных типов. Правила их описания и использования аналогичны соответствующим элементам классов, за исключением некоторых отличий, вытекающих из упомянутых ранее: * поскольку структуры не могут участвовать в иерархиях, для их элементов не могут использоваться спецификаторы protected и protected internal; * структуры не могут быть абстрактными (abstract), к тому же по умолчанию они бесплодны (sealed); методы структур не могут быть абстрактными и виртуальными; переопределяться (то есть описываться со спецификатором override) могут только методы, унаследованные от базового класса object; параметр this интерпретируется как значение, поэтому его можно использовать для ссылок, но не для присваивания; при описании структуры нельзя задавать значения полей по умолчанию1 — это будет сделано в конструкторе по умолчанию, создаваемом автоматически (конструктор присваивает значимым полям структуры нули, а ссылочным — значение null). В листинге 9.8 приведен пример описания структуры, представляющей комплексное число. Для экономии места из всех операций приведено только описание сложения. Обратите внимание на перегруженный метод ToString; он позволяет выводить экземпляры структуры на консоль, поскольку неявно вызывается в методе Console.WriteLine. Использованные в методе спецификаторы формата описаны в приложении. | 25 Перегрузка операций С# позволяет переопределить действие большинства операций так, чтобы при использовании с объектами конкретного класса они выполняли заданные функции. Это дает возможность применять экземпляры собственных типов данных в составе выражений таким же образом, как стандартных, например: MyObject a. b. с; с = а + Ь: // используется операция сложения для класса MyObject Определение собственных операций класса часто называют перегружай операций. Перегрузка обычно применяется для классов, описывающих математические или физические понятия, то есть таких классов, для которых семантика операций делает программу более понятной. Если назначение операции интуитивно не понятно с первого взгляда, перегружать такую операцию не рекомендуется. Операции класса описываются с помощью методов специального вида {функций-операций). Перегрузка операций похожа на перегрузку обычных методов. Синтаксис операции: [ атрибуты ] спецификаторы о&ьявительоперации тело Атрибуты рассматриваются в главе 12, в качестве спецификаторов одновременно используются ключевые слова public и static. Кроме того, операцию можно объявить как внешнюю (extern). Объявитель операции содержит ключевое слово operator, по которому и опознается описание операции в классе. Тело операции определяет действия, которые выполняются при использовании операции в выражении. Тело представляет собой блок, аналогичный телу других методов. | 26 Индексаторы Индексатор представляет собой разновидность свойства. Если у класса есть скрытое поле, представляющее собой массив, то с помощью индексатора можно обратиться к элементу этого массива, используя имя объекта и номер элемента массива в квадратных скобках. Иными словами, индексатор — это такой «умный» индекс для объектов. Синтаксис индексатора аналогичен синтаксису свойства: атрибуты спецификаторы тип this [ список параметров ] { get коддоступа set коддоступа } ВНИМАНИЕ В данном случае квадратные скобки являются элементом синтаксиса, а не указанием на необязательность конструкции. Атрибуты мы рассмотрим позже, в главе 12, а спецификаторы аналогичны спецификаторам свойств и методов. Индексаторы чаще всего объявляются со спецификатором public, поскольку они входят в интерфейс объекта1. Атрибуты и спецификаторы могут отсутствовать. Код доступа представляет собой блоки операторов, которые выполняются при получении (get) или установке значения (set) элемента массива. Может отсутствовать либо часть get, либо set, но не обе одновременно. Если отсутствует часть set, индексатор доступен только для чтения (read-only), если отсутствует часть get, индексатор доступен только для записи (write-only). Список параметров содержит одно или несколько описаний индексов, по которым выполняется доступ к элементу. Чаще всего используется один индекс целого типа. Индексаторы в основном применяются для создания специализированных массивов, на работу с которыми накладываются какие-либо ограничения. В листинге 7.3 создан класс-массив, элементы которого должны находиться в диапазоне [О, 100]. Кроме того, при доступе к элементу проверяется, не вышел ли индекс за допустимые границы. | | | | 27 Интерфейсы Интерфейс является «крайним случаем» абстрактного класса. В нем задается набор абстрактных методов, свойств и индексаторов, которые должны быть реализованы в производных классах1. Иными словами, интерфейс определяет поведение, которое поддерживается реализующими этот интерфейс классами. Основная идея использования интерфейса состоит в том, чтобы к объектам таких классов можно было обращаться одинаковым образом. Каждый класс может определять элементы интерфейса по-своему. Так достигав ется полиморфизм: объекты разных классов по-разному реагируют на вызовы одного и того же метода. Синтаксис интерфейса аналогичен синтаксису класса: [ атрибуты ] [ спецификаторы ] interface иня интерфейса [ ; предки ] телоинтерфейса [ ; ] Для интерфейса могут быть указаны спецификаторы new, publ ic, protected, internal и private. Спецификатор new применяется для вложенных интерфейсов и имеет такой же смысл, как и соответствующий модификатор метода класса. Остальные спецификаторы управляют видимостью интерфейса. В разных контекстах определения интерфейса допускаются разные спецификаторы. По умолчанию интерфейс доступен только из сборки, в которой он описан (internal). Интерфейс может наследовать свойства нескольких интерфейсов, в этом случае предки перечисляются через запятую. Тело интерфейса составляют абстрактные методы, шаблоны свойств и индексаторов, а также события. ПРИМЕЧАНИЕ Методом исключения можно догадаться, что интерфейс не может содержать константы, поля, операции, конструкторы, деструкторы, типы и любые статические элементы. | 28 Обработка исключительных ситуаций В языке С# есть операторы, позволяющие обнаруживать и обрабатывать ошибки (исключительные ситуации), возникающие в процессе выполнения программы. Об этом уже упоминалось в разделе «Введение в исключения» (см. с. 46), а сейчас мы рассмотрим механизм обработки исключений более подробно. Исключительная ситуация, или исключение, — это возникновение аварийного события, которое может порождаться некорректным использованием аппаратуры или неправильной работой программы, например делением на ноль или переполнением. Обычно эти события приводят к завершению программы с системным сообщением об ошибке. С# дает программисту возможность восстановить работоспособность программы и продолжить ее выполнение. Исключения С# не поддерживают обработку асинхронных событий, таких как ошибки оборудования или прерывания, например нажатие клавиш Ctrl+C. Механизм исключений предназначен только для событий, которые могут произойти в результате работы самой программы и указываются явным образом. Исключения возникают тогда, когда некоторая часть программы не смогла сделать то, что от нее требовалось. При этом другая часть программы может попытаться сделать что-нибудь иное. Исключения позволяют логически разделить вычислительный процесс на две части — обнаружение аварийной ситуации и ее обработка. Это важно не только для лучшей структуризации программы. Главное то, что функция, обнаружившая ошибку, может не знать, что предпринимать для ее исправления, а использующий эту функцию код может знать, что делать, но не уметь определить место возникновения. Это особенно актуально при использовании библиотечных функций и программ, состоящих из многих модулей. Другое достоинство исключений состоит в том, что для передачи информации об ошибке в вызывающую функцию не требуется применять возвращаемое значение или параметры, поэтому заголовки функций не разрастаются. ПРИМЕЧАНИЕ В принципе, ничто не мешает рассматривать в качестве исключений не только ошибки, но и нормальные ситуации, возникающие при обработке данных, но это не имеет преимуществ перед другими решениями, не улучшает структуру программы и не делает ее понятнее. Исключения генерирует либо среда выполнения, либо программист с помощью оператора throw. В табл. 4.1 приведены наиболее часто используемые стандартные исключения, генерируемые средой. Они определены в пространстве имен System. Все они являются потомками класса Exception, а точнее, потомками его потомка SystemException. Исключения обнаруживаются и обрабатываются в операторе try. | 29 Ключевые слова Операции преобразования типа обеспечивают возможность явного и неявного преобразования между пользовательскими типами данных. Синтаксис объявителя операции преобразования типа: implicit operator тип ( параметр ) // неявное преобразование explicit operator тип ( параметр ) // явное преобразование Эти операции выполняют преобразование из типа параметра в тип, указанный в заголовке операции. Одним из этих типов должен быть класс, для которого определяется операция. Таким образом, операции выполняют преобразование либо типа класса к другому типу, либо наоборот. Преобразуемые типы не должны быть связаны отношениями наследования1. Примеры операций преобразования типа для класса Monster, описанного в главе 5: public static implicit operator intC Monster m ) { return m.health: } public static explicit operator Monster( int h ) { return new Monster( h. 100, "Fromlnt" ); } Ниже приведены примеры использования этих преобразований в программе. Не надо искать в них смысл, они просто иллюстрируют синтаксис: Monster Masha - new Monster( 200. 200. "Masha" ); int i = Masha; // неявное преобразование Masha = (Monster) 500; // явное преобразование Неявное преобразование выполняется автоматически: при присваивании объекта переменной целевого типа, как в примере; при использовании объекта в выражении, содержащем переменные целевого типа; при передаче объекта в метод на место параметра целевого типа; при явном приведении типа. Явное преобразование выполняется при использовании операции приведения типа. Все операции класса должны иметь разные сигнатуры. В отличие от других видов методов, для операций преобразования тип возвращаемого значения включается в сигнатуру, иначе нельзя было бы определять варианты преобразования данного типа в несколько других. Ключевые слова implicit и explicit в сигнатуру не включаются, следовательно, для одного и того же преобразования нельзя определить одновременно явную и неявную версии. Неявное преобразование следует определять так, чтобы при его выполнении не возникала потеря точности и не генерировались исключения. Если эти ситуации возможны, преобразование следует описать как явное. | 30 Делегаты Делегат — это вид класса, предназначенный для хранения ссылок на методы. Делегат, как и любой другой класс, можно передать в качестве параметра, а затем вызвать инкапсулированный в нем метод. Делегаты используются для поддержки событий, а также как самостоятельная конструкция языка. Рассмотрим сначала второй случай. Описание делегатов Описание делегата задает сигнатуру методов, которые могут быть вызваны с его помощью: » [ атрибуты ] [ спецификаторы ] delegate тип имя делегата С [ параметры ] ) Спецификаторы делегата имеют тот же смысл, что и для класса, причем допускаются только спецификаторы new, public, protected, internal и private. Тип описывает возвращаемое значение методов, вызываемых с помощью делегата, а необязательными параметрами делегата являются параметры этих методов. Делегат может хранить ссылки на несколько методов и вызывать их поочередно; естественно, что сигнатуры всех методов должны совпадать. Пример описания делегата: public delegate void 0 ( int i ): Здесь описан тип делегата, который может хранить ссылки на методы, возвращающие void и принимающие один параметр целого типа. ПРИМЕЧАНИЕ Делегат, как и всякий класс, представляет собой тип данных. Его базовым классом является класс System. Delegate, снабжающий своего «отпрыска» некоторыми полезными элементами, которые мы рассмотрим позже. Наследовать от делегата нельзя, да и нет смысла. Объявление делегата можно размещать непосредственно в пространстве имен или внутри класса. Использование делегатов Для того чтобы воспользоваться делегатом, необходимо создать его экземпляр и задать имена методов, на которые он будет ссылаться. При вызове экземпляра делегата вызываются все заданные в нем методы. Делегаты применяются в основном для следующих целей: получения возможности определять вызываемый метод не при компиляции, а динамически во время выполнения программы; обеспечения связи между объектами по типу «источник — наблюдатель»; □ создания универсальных методов, в которые можно передавать другие методы; J поддержки механизма обратных вызовов. Все эти варианты подробно обсуждаются далее. Рассмотрим сначала пример реализации первой из этих целей. В листинге 10.1 объявляется делегат, с помощью которого один и тот же оператор используется для вызова двух разных методов (С001 и Hack). | 31 События Событие — это элемент класса, позволяющий ему посылать другим объектам уведомления об изменении своего состояния. При этом для объектов, являющихся наблюдателями события, активизируются методы-обработчики этого события. Обработчики должны быть зарегистрированы в объекте-источнике события. Таким образом, механизм событий формализует на языковом уровне паттерн «наблюдатель», который рассматривался в предыдущем разделе. Механизм событий можно также описать с помощью модели «публикация — подписка»: один класс, являющийся отправителем (sender) сообщения, публикует события, которые он может инициировать, а другие классы, являющиеся получателями (receivers) сообщения, подписываются на получение этих событий. События построены на основе делегатов: с помощью делегатов вызываются методы-обработчики событий. Поэтому создание события в классе состоит из следующих частей: описание делегата, задающего сигнатуру обработчиков событий; описание события; описание метода (методов), инициирующих событие. Синтаксис события похож на синтаксис делегата1: [ атрибуты ] [ спецификаторы ] event тип имя события Для событий применяются спецификаторы new, public, protected, internal, private, static, virtual, sealed, override, abstract и extern, которые изучались при рассмотрении методов классов. Например, так же как и методы, событие может быть статическим (static), тогда оно связано с классом в целом, или обычным — в этом случае оно связано с экземпляром класса. Тип события — это тип делегата, на котором основано событие. Пример описания делегата и соответствующего ему события: public delegate void Del( object о ); // объявление делегата class A ( public event Del Oops; // объявление события } Обработка событий выполняется в классах-получателях сообщения. Для этого в них описываются методы-обработчики событий, сигнатура которых соответствует типу делегата. Каждый объект (не класс!), желающий получать сообщение, должен зарегистрировать в объекте-отправителе этот метод. Как видите, это в точности тот же самый механизм, который рассматривался в предыдущем разделе. Единственное отличие состоит в том, что при использовании событий не требуется описывать метод, регистрирующий обработчики, поскольку события поддерживают операции += и -=, добавляющие обработчик в список и удаляющие его из списка. | 32 Директивы препроцессора Препроцессором в языке C++ называется предварительный этап компиляции, формирующий окончательный вариант текста программы. В языке С#, потомке C++, препроцессор практически отсутствует, но некоторые директивы сохранились. Назначение директив — исключать из процесса компиляции фрагменты кода при выполнении определенных условий, выводить сообщения об ошибках и предупреждения, а также структурировать код программы. Каждая директива располагается на отдельной строке и не закапчивается точкой с запятой, в отличие от операторов языка. В одной строке с директивой может располагаться только комментарий вида //. Перечень и краткое описание директив приведены в табл. 12.4. Рассмотрим более подробно применение директив условной компиляции. Они используются для того, чтобы исключить компиляцию отдельных частей программы. Это бывает полезно при отладке или, например, при поддержке нескольких версий программы для различных платформ. Формат директив: #if константноевыражение [ #elif константное_выражение [ #elif константное выражение …] #else #endif Количество директив #elif произвольно. Исключаемые блоки кода могут содержать как описания, так и исполняемые операторы. Константное выражение может содержать одну или несколько символьных констант, объединенных знаками операций ==, !=, !, && и 11. Также допускаются круглые скобки. Константа считается равной true, если она была ранее определена с помощью директивы #def 1 пе. Атрибуты Атрибуты ;гго догюлшггельные сведения об атементах iiporpasiMbi (классах, методах, параметрах и г. х). С помощью атрибутов можно добавлять информацию в метаданные сборки и затем извлекать се во время выполнения программы. Атрибут является специальным видом класса и происходит от базового класса System Attribute. Атрибуты делятся на стандартные и пользовательские. В библиотеке .NET предусмотрено множество стандартных атрибутов, которые можно использовать в программах. Если всего разнообразия стандартных атрибутов не хватит, чтобы удовлетворить прихотливые требования программиста, он может описать собственные классы атрибутов, после чего применять их точно так же. как стандартные. При испом>зовании {спецификащш) атрибутов они затаются в секции атрибутов, располагаемой непосредственно перед элементом, для описания которого они предназначены. Секция заключается ь квадратные скобки и может содержать несколько атрибутов, перечисляемых через запятую. Порядок следования атрибутов произвольный. Для каждого атрибута задаются имя, а также необязательные параметры и тип элемента сборки, к которому относится атрибут. Простейший пример атрибута: [Serializable} class Honster {… [NonSerialized] string папе: int health, агою: } Атрибут [Serial izable], означающий, что объекты этого класса можно сохранять во внешней памяти, относится ко всему классу Honster. При этом поле паше помечено атрибутом [NonSerialized], что говорит о том. что это поле сохраняться не должно. Сохранение объектов рассматривалось в главе 10. Обычно из контекста понятно, к какому элементу сборки относится атрибут. однако в некоторых случаях могут возникнуть неоднозначности. Для их устранения перед именем атрибута записывается тип элемента сборки - уточняющее ключевое слово, отделяемое от атрибута двоеточием. | 33 Небезопасный код Одним из основных достоинств языка С# является его схема работы с памятью: автоматическое выделение памяти под объекты и автоматическая уборка мусора. При этом невозможно обратиться по несуществующему адресу памяти или выйти за границы массива, что делает программы более надежными и безопасными и исключает возможность появления целого класса ошибок, доставляющих массу неудобств при написании программ на других языках. Однако в некоторых случаях возникает необходимость работать с адресами памяти непосредственно, например, при взаимодействии с операционной системой, написании драйверов или программ, время выполнения которых критично. Такую возможность предоставляет так называемый небезопасный (unsafe) код. Небезопасным называется код, выполнение которого среда CLR не контролирует. Он работает напрямую с адресами областей памяти посредством указателей и должен быть явным образом помечен с помощью ключевого слова unsafe, которое определяет так называемый небезопасный контекст выполнения. Ключевое слово unsafe может использоваться либо как спецификатор, либо как оператор. В первом случае его указывают наряду с другими спецификаторами при описании класса, делегата, структуры, метода, поля и т. д. — везде, где допустимы другие спецификаторы. Это определяет небезопасный контекст для описываемого элемента, например: public unsafe struct Node { public int Value; public Node* Left; public Node* Right; } Вся структура Node помечается как небезопасная, что делает возможным использование в ней указателей Left и Right. Можно применить и другой вариант описания, в котором небезопасными объявляются только соответствующие поля структуры: public struct Node { public int Value; public unsafe Node* Left; public unsafe Node* Right; } Оператор unsafe имеет следующий синтаксис: unsafe блок Все операторы, входящие в блок, выполняются в небезопасном контексте. | 34 Строки Строки типа string Тип string, предназначенный для работы со строками символов в кодировке Unicode, является встроенным типом С#. Ему соответствует базовый класс System.String библиотеки .NET. Создать строку можно несколькими способами: string s; // инициализация отложена string t = "qqq"; // инициализация строковым литералом string u = new stringC '. 20); // конструктор создает строку из 20 пробелов char[] а = { "0'. '0'. 'О' }; // массив для инициализации строки string v = new stringC a ): // создание из массива символов Для строк определены следующие операции: присваивание (=); проверка на равенство (=); проверка на неравенство (!=); обращение по индексу ([]); сцепление (конкатенация) строк (+). Несмотря на то что строки являются ссылочным типом данных, на равенство и неравенство проверяются не ссылки, а значения строк. Строки равны, если имеют одинаковое количество символов и совпадают посимвольно. Обращаться к отдельному элементу строки по индексу можно только для получения значения, но не для его изменения. Это связано с тем, что строки типа string относятся к так называемым неизменяемым типам данных1. Методы, изменяющие содержимое строки, на самом деле создают новую копию строки. Неиспользуемые «старые» копии автоматически удаляются сборщиком мусора. В классе System.String предусмотрено множество методов, полей и свойств, позволяющих выполнять со строками практически любые действия. | 35 Регулярные выражения Регулярные выражения предназначены для обработки текстовой информации и обеспечивают: эффективный поиск в тексте по заданному шаблону; редактирование, замену и удаление подстрок; Q формирование итоговых отчетов по результатам работы с текстом. С помощью регулярных выражений удобно обрабатывать, например, файлы в формате HTML, файлы журналов или длинные текстовые файлы. Для поддержки регулярных выражений в библиотеку .NET включены классы, объединенные в пространство имен System.Text.RegularExpressions. Метасимволы Регулярное выражение — это шаблон (образец), по которому выполняется поиск соответствующего ему фрагмента текста. Язык описания регулярных выражений состоит из символов двух видов: обычных и метасимволов. Обычный символ представляет в выражении сам себя, а метасимвол — некоторый класс символов, например любую цифру или букву. Например, регулярное выражение для поиска в тексте фрагмента «Вася» записывается с помощью четырех обычных символов Вася, а выражение для поиска двух цифр, идущих подряд, состоит из двух метасимволов \d\d. С помощью комбинаций метасимволов можно описывать сложные шаблоны для поиска. Например, можно описать шаблон для IP-адреса, адреса электронной почты, различных форматов даты, заголовков определенного вида и т. д. ПРИМЕЧАНИЕ Синтаксис регулярных выражений .NET в основном позаимствован из языка Perl 5. Неподготовленного человека вид сложного регулярного выражения может привести в замешательство, но при вдумчивом изучении он обязательно почувствует его красоту и очарование. Пожалуй, регулярные выражения более всего напоминают заклинания, по которым волшебным образом преобразуется текст. Ошибка всего в одном символе делает заклинание бессильным, зато, верно составленное, оно творит чудеса! В табл. 15.2 описаны наиболее употребительные метасимволы, представляющие собой классы символов. Метасимволы, перечисленные в табл. 15.3, уточняют позицию в строке, в которой следует искать совпадение с регулярным выражением, например, только в начале или в конце строки. Эти метасимволы являются мнимыми, то есть в тексте им не соответствует никакой реальный символ. | 36 Коллекции и словари В пространстве имен System.Col lections определены наборы стандартных коллекций и интерфейсов, которые реализованы в этих коллекциях. Таблица 13.2. Интерфейсы пространства имен System.Collections Интерфейс Назначение ICol lection Определяет общие характеристики (например, размер) для набора элементов IComparer - Позволяет сравнивать два объекта IDictionary Позволяет представлять содержимое объекта в виде пар «имя—значение» IDictionaryEnumerator Используется для нумерации содержимого объекта, поддерживающего интерфейс IDictionary I Enumerable Возвращает интерфейс I Enumerator для указанного объекта I Enumerator Обычно используется для поддержки оператора foreach В отношении объектов IHashCodeProvider Возвращает хеш-код для реализации типа с применением выбранного пользователем алгоритма хеширования ILi st Поддерживает методы добавления, удаления и индексирования элементов в списке объектов I Enumerable Возвращает интерфейс I Enumerator для указанного объекта I Enumerator Обычно используется для поддержки оператора foreach В отношении объектов IHashCodeProvider Возвращает хеш-код для реализации типа с применением выбранного пользователем алгоритма хеширования ILi st Поддерживает методы добавления, удаления и индексирования элементов в списке объектов В табл. 13.3 перечислены основные коллекции, определенные в пространстве System.Collections1. Таблица 13.3. Коллекции пространства имен System. Col lections Класс Назначение Важнейшие из реализованных интерфейсов ArrayList Массив, динамически IList, ICollection, [Enumerable, ICloneable изменяющий свой размер BitArray Компактный массив для ICollection, IEnumerable, ICloneable хранения битовых значений Hashtable Хеш-таблица2 IDictionary, ICollection, IEnumerable, ICloneable Queue Очередь ICollection, ICloneable, IEnumerable SortedList Коллекция, отсортированная IDictionary, ICollection, IEnumerable, по ключам. Доступ ICloneable к элементам — по ключу или по индексу Stack Стек ICollection, IEnumerable Пространство имен System.Collections.Specialized включает специализированные коллекции, например коллекцию строк StringCollection и хеш-таблицу со строковыми ключами StringDictionary. В качестве примера стандартной коллекции рассмотрим класс ArrayLi st. Хеш-таблица, ассоциативный массив, или словарь — это массив, доступ к элементам которого осуществляется не по номеру, а по некоторому ключу. Можно сказать, что это таблица, состоящая из пар «ключ—значение* (табл. 13.1). Хеш-таблица эффективно реализует операцию поиска значения по ключу. При этом ключ преобразуется в число (хеш-код), которое используется для быстрого нахождения нужного значения в хеш-таблице. Преобразование выполняется с помощью хеш-функции, или функции расстановки. Эта функция обычно производит какие-либо преобразования внутреннего представления ключа, например, вычисляег среднее арифметическое кодов составляющих его символов (английское слово «hash» означает перемалывать, перемешивать). Если хеш-функция распределяет совокупность возможных ключей равномерно по множеству индексов массива, то доступ к элементу по ключу выполняется почти так же быстро, как в массиве. Если хеш-функция генерирует для разных ключей одинаковые хеш-коды, время поиска возрастает и становится сравнимым со временем поиска в списке. Смысл хеш-функции состоит в том, чтоб отобразить более широкое множество ключей в более узкое множество индексов. При этом неизбежно возникают так называемые коллизии, когда хеш-функция формирует для двух разных элементов один и тот же хеш-код. В разных реализациях хеш-таблиц используются различные стратегии борьбы с коллизиями. | 38 Потоки Поток (thread1) представляет собой часть исполняемого кода программы. В каждом процессе есть первичный поток, исполняющий роль точки входа в приложение. Для консольных приложений это метод Main. Многопоточные приложения создают как для многопроцессорных, так и для однопроцессорных систем. Основной целью при этом являются повышение общей производительности и сокращение времени реакции приложения. Управление потоками осуществляет операционная система. Каждый поток получает некоторое количество квантов времени, по истечении которого управление передается другому потоку. Это создает у пользователя однопроцессорной машины впечатление одновременной работы нескольких потоков и позволяет, к примеру, выполнять ввод текста одновременно с длительной операцией по передаче данных. Недостатки многопоточности: □ большое количество потоков ведет к увеличению накладных расходов, связанных с их переключением, что снижает общую производительность системы; Q в многопоточных приложениях возникают проблемы синхронизации данных, связанные с потенциальной возможностью доступа к одним и тем же данным со стороны нескольких потоков (например, если один поток начинает изменение общих данных, а отведенное ему время истекает, доступ к этим же данным может получить другой поток, который, изменяя данные, необратимо их повреждает). Класс Thread Поддержка многопоточности осуществляется в .NET в основном с помощью пространства имен System.Threading. Первичный поток создается автоматически. Для запуска вторичных потоков используется класс Thread. При создании объекта-потока ему передается делегат, определяющий метод, выполнение которого выделяется в отдельный поток: Thread t = new Thread ( new ThreadStart( имя_метода ) ): После создания потока заданный метод начинает в нем свою работу, а первичный поток продолжает выполняться. В листинге 10.10 приведен пример одновременной работы двух потоков. | | | | |
|
|