Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Объектно-ориентированное программирование.-6

.pdf
Скачиваний:
6
Добавлен:
05.02.2023
Размер:
4.5 Mб
Скачать

такие результаты:

Зарплата служащего Анна составляет :: SalariedEmployee.CalculatePay ::

32000

Зарплата служащего Яна составляет :: ContractEmployee.CalculatePay ::

4800

Зарплата служащего Инна составляет :: HourlyEmployee.CalculatePay ::

3600

Полиморфизм имеет, как минимум, два плюса. Во-первых, он позволяет группировать объекты, имеющие общий базовый класс, и последовательно (например, в цикле) их обрабатывать. В рассмотренном случае у нас три раз-

ных типа объектов (SalariedEmployee, ContractorEmployee и HourlyEmployee),

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

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

21

§ 2.2. Технология Microsoft .NET

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

2.2.1. Платформа Microsoft .NET

Основу Microsoft .NET составляют четыре базовых компонента:

.NET Building Block Services – средства программного доступа к таким службам, как хранилище файлов (file storage), календарь (calendar), служба аутентификации «Passport.NET»;

ПО для устройств .NET, которое будет выполняться на новых устройствах Интернета;

Средства .NET для работы с пользователями, включающие естественный интерфейс (natural interface), информационные агенты (information agents) и интеллектуальные теги (smart tags) – технологию, которая автоматизирует переход по гиперссылкам к информации, связанной со словами и фразами в документах пользователей;

Инфраструктура .NET, состоящая из .NET Framework, Microsoft Visual Studio .NET, .NET Enterprise Servers и Microsoft Windows .NET.

Большинство разработчиков воспринимает инфраструктуру .NET собственно как .NET. Поэтому в дальнейшем при любом упоминании .NET (если нет предварительной оговорки) мы будем иметь в виду инфраструктуру

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

22

Технология .NET Framework состоит из Common Language Runtime (CLR) и набора библиотек классов .NET Framework, который иногда называют Base Class Library (BCL). Среда CLR – это, по сути, виртуальная машина, в которой функционируют приложения .NET. Все языки .NET имеют в своем распоряжении библиотеки классов .NET Framework. Если вы знакомы с Microsoft Foundation Classes (MFC), либо Object Windows Library (OWL) и Visual Component Library (VCL) компании Borland, то вам не надо объяснять, что это такое. Библиотеки классов .NET Framework включают поддержку практически всех технологий. Вообще, библиотеки классов .NET Framework столь обширны, что даже поверхностный обзор всех поддерживаемых классов потребует отдельной книги.

Под термином «виртуальная машина» здесь не подразумевается Java Virtual Machine (JVM). Здесь применяется этот термин в его традиционном значении. Несколько десятилетий назад IBM ввела в оборот словосочетание «виртуальная машина» («virtual machine»). Этим термином была обозначена абстракция высокоуровневой ОС, внутри которой могли функционировать в полностью инкапсулированной среде другие ОС. Говоря о CLR как о виртуальной машине, имеется в виду то, что код, выполняемый в инкапсулированной и управляемой среде, отделен от других процессов на этой машине.

2.2.2. Common Language Runtime

Среда Common Language Runtime (CLR) – это сердце технологии Microsoft .NET. Как следует из названия, это среда времени выполнения кода, в которой обеспечивается эффективное взаимодействие приложений, пересекающее границы разных языков программирования (cross-language interoperability). Как достигается это взаимодействие? При помощи Common Language Specification (CLS) – набора правил, которых должен придерживаться компилятор языка при создании .NET-приложений, запускаемых в среде CLR. Любой, кто захочет написать компилятор для .NET, должен следовать этим правилам, и тогда приложения, сгенерированные этим компилятором, будут работать наряду с другими .NET-приложениями и будут иметь такую же возможность взаимодействия.

С CLR связана важная концепция управляемого кода (managed code) – кода, выполняемого только в среде CLR и управляемого ею. Напомню, что во время исполнения в нынешних ОС Microsoft Windows мы имеем дело с раз-

23

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

В среде Windows есть несколько глобальных правил поведения приложений, относящихся к их взаимодействию друг с другом, распределению памяти, а также к привлечению средств самой ОС для работы от их имени. Напротив, в среде управляемого кода есть набор правил, обеспечивающих единообразное в глобальном смысле поведение всех приложений независимо от того, на каком языке они написаны. Единообразное поведение .NETприложений – характерная черта технологии .NET, и его нельзя игнорировать. К счастью, эти глобальные правила распространяются главным образом только на создателей компиляторов.

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

Библиотеки классов .NET Framework играют чрезвычайно важную роль в обеспечении межъязыкового взаимодействия приложений, так как они позволяют разработчикам использовать единый программный интерфейс ко всем функциональным средствам CLR. Библиотеки классов .NET Framework делают фактически революционный прорыв в разработке компиляторов. До

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

В качестве иллюстрации к сказанному сравним версии традиционного приложения «Hello, World!» на языках C++ и C#. Код на языке C++:

#include <iostream>

int main(int argc, char* argv[])

{

cout << "Hello, World!" << endl; return 0;

}

24

В начало приложения включен заголовочный файл с объявлением функции класса ostream (экземпляром которого является cout). Функция main

– входная точка любого приложения на C/C++ – выводит на стандартное устройство вывода с помощью объекта cout строку «Hello, World!». Здесь для нас важно то, что написать такое приложение на языке .NET без библиотек классов .NET Framework нельзя. Это действительно так: в .NET-языках нет присущих обычным компиляторам основных элементов, которые, например, выводят на консоль строку текста. Да, с точки зрения технологии, реализация объекта cout находится в той части C/C++, которая сама является библиотекой. И все-таки основные задачи C++, такие как форматирование строк, файловый ввод-вывод и вывод на экран, хотя бы формально считаются частью исходного языка. Что касается C# (и это характерно для любого .NET-языка), то он не в состоянии выполнить даже самую примитивную задачу без привлечения библиотеки классов .NET Framework. А так выглядит пример «Hello, World!» на языке C#:

using System;

class Program

{

public static int Main(string[] args)

{

Console.WriteLine("Hello, World!"); return 0;

}

}

Подробный разбор этой программы, а также сведения о выборе среды разработки приведены в § 2.3.

Итак, что дает этот стандартный набор библиотек классов и чем он хорош? Это зависит от точки зрения. Благодаря такому набору все языки .NET в идеале располагают одними и теми же функциональными возможностями, поскольку все они могут что-то делать (если речь идет не только об объявлении переменных) только с помощью этих библиотек.

В идеале библиотеки классов .NET Framework открывают пользователям языка все функциональные возможности CLR, однако на самом деле так бывает не всегда. Камнем преткновения между разработчиками библиотек классов .NET Framework и разработчиками компиляторов является то, что первые, хотя и попытались открыть для любых языков все функциональные возможности библиотек классов, последних все-таки ничто не обязывает делать реализацию каждой такой возможности (не пренебрегая, правда, мини-

25

мальными стандартами CLS). Поэтому вряд ли каждый язык .NET будет иметь доступ ко всем функциональным возможностям .NET Framework, поскольку каждая бригада разработчиков компиляторов вправе реализовать только те, что они считают самыми нужными для своих пользователей. Однако, C# – по-видимому, тот язык, в котором имеется доступ практически ко всем функциональным возможностям .NET Framework.

2.2.4. Microsoft Intermediate Language и компиляторы JITter

Для облегчения перевода языков в среду .NET в Microsoft разработан промежуточный язык – Microsoft Intermediate Language (MSIL). Чтобы от-

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

Результатом компиляции приложения, написанного на C# или другом языке, который отвечает правилам CLS, является MSIL-код. Потом, при первом запуске приложения в среде CLR, MSIL-код компилируется в машинные команды, специфичные для данного процессора (рис. 2.1).

Visual Basic .NET

C++ .NET

C#

J#

Другие языки .NET

Microsoft Intermediate Language

(MSIL)

 

Common Language Runtime

(CLR)

 

Исполняемый код процессора

 

 

 

 

 

 

 

Рис. 2.1 – Компиляция приложения в среде .NET

26

Посмотрим по порядку, что же происходит с кодом:

1.Мы пишем исходный код на C#.

2.Затем компилируем его с помощью компилятора языка C# в EXE-

файл.

3.Компилятор создает MSIL-код и помещает в раздел «только-на- чтение» выходного файла стандартный заголовок PE (признак машиннонезависимой выполняемой программы для Win32). Здесь появляется очень важная деталь: при создании выходного файла компилятор импортирует из

CLR функцию _CorExeMain.

4.Когда приложение начинает выполняться, ОС загружает этот РЕ (впрочем, как и обычный РЕ), а также все нужные DLL, в частности, библиотеку, которая экспортирует функцию _CorExeMain (mscoree.dll).

5.Загрузчик ОС выполняет переход в точку входа РЕ, устанавливаемую компилятором. Это ничем не отличается от процедуры загрузки в Windows любого другого РЕ. Однако, так как ОС не в состоянии выполнить MSIL-код, то фактически в точке входа содержится заглушка, в которой установлена команда перехода к функции _CorExeMain из mscoree.dll.

6.Функция _CorExeMain переходит к выполнению MSIL-кода, помещенного в секцию РЕ.

7.Так как MSIL-код не может быть выполнен непосредственно (ведь это не машинный код), CLR компилирует его с помощью оперативного (just- in-time, или JIT) компилятора (его еще называют JITter) в команды процессора. Эта компиляция выполняется только для непосредственно вызываемых методов программы. Откомпилированный выполняемый код сохраняется на машине и перекомпилируется только в случае изменения исходного кода. Для преобразования MSIL в настоящий машинный код можно применить один из следующих JIT-компиляторов:

1) Генератор кода при установке (install-time code generation). Выпол-

няет компиляцию всей сборки в двоичный код, специфичный для данного процессора, подобно тому, как это делает компилятор C#. Сборка (assembly)

это комплект модулей кода, посылаемый компилятору. Эта компиляция выполняется в ходе установки, когда обработка сборки JIT-компилятором меньше всего заметна для конечного пользователя. Достоинство этого типа генерации двоичного кода в том, что компиляция всей сборки выполняется один раз еще до запуска приложения. Поскольку код скомпилирован, то о

27

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

2)Компилятор JIT – стандартный JITter, вызываемый при выполнении приложения каждый раз для впервые активизируемого метода (в порядке, описанном выше). Данный режим работает по умолчанию, если вы не запускаете явно компилятор РrеJIТ.

3)Компилятор EconoJIT – включается во время выполнения приложения и предназначен специально для систем, которые имеют ограниченные ресурсы, например, для портативных устройств с малым размером памяти. Основное отличие этого компилятора от обычного компилятора JITter – в объединении кодовых фрагментов (code pitching). Благодаря разбивке кода на фрагменты EconoJIT может удалить сгенерированный (т.е. откомпилированный) код, если памяти для запуска системы недостаточно. Достоинство этого компилятора в экономии памяти, а недостаток в том, что если фрагментированный код загружается вновь, он должен быть опять перекомпилирован как код, который еще никогда не вызывался.

2.2.5. Унифицированная система типов

Одна из ключевых черт любой среды разработки – ее система типов. Если среда разработки имеет небольшой выбор типов или ограничивает возможность программиста добавлять свои типы, то такая среда проживет недолго. Среда CLR не только предоставляет разработчику единую унифицированную систему типов CTS (Common Type System), доступную для всех CLS-совместимых языков, но и позволяет создателям языков расширять систему типов путем добавления новых типов, по виду и поведению ничем не отличающихся от встроенных типов. Это означает, что разработчик может работать со всеми типами единообразно независимо от того, стандартные это типы или вновь созданные.

28

2.2.5.1. Преимущества использования CTS

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

Возможность взаимодействия языков. Система CTS играет важную роль в обеспечении способности к взаимодействию языков, так как она определяет набор типов, которые должен поддерживать компилятор .NET, чтобы обеспечивать взаимодействие с другими языками. Сама CTS определена в спецификации CLS (Common Language Specification). Система CLS опреде-

ляет единый набор правил для каждого компилятора .NET, гарантируя, что каждый компилятор будет выдавать код, согласованно взаимодействующий с CLR. Согласно требованиям CLS, компилятор должен поддерживать некоторые типы, определенные в CTS. Все языки .NET используют единую систему типов. Это выгодно, так как гарантирует бесшовное взаимодействие объектов и типов, создаваемых на различных языках. Благодаря этой комбинации CTS/CLS взаимодействие программ на разных языках становится реальным.

Иерархия объектов с единым корнем. Итак, важная характеристика

CTS – иерархия объектов с единым корнем. В .NET Framework каждый тип системы происходит от базового класса System.Object. Подход, использующий единый базовый класс – важное отличие от языка C++, в котором нет базового класса для всех классов. Он рекомендован теоретиками ООП и реализован в большинстве объектно-ориентированных языков, формирующих главное направление этой технологии.

Иерархия объектов с единым корнем является ключевой для унифицированной системы типов, поскольку гарантирует наличие общего интерфейса у каждого объекта в иерархии, и поэтому всякая сущность в составе этой иерархии, в конечном счете, принадлежит к одному базовому типу. Один из самых серьезных минусов C++ – отсутствие поддержки подобной иерархии. Рассмотрим простой пример.

Допустим, создав иерархию объектов на C++ на основе собственного базового класса CFoo, вы хотите интегрировать ее с другой иерархией, все объекты которой происходят от базового класса СВаr. В этом примере интерфейсы иерархий объектов несовместимы, поэтому их интеграция заставит попотеть. Чтобы справиться с этой задачей, вам придется использовать класс-оболочку или множественное наследование. В случае иерархии с од-

29

ним корнем совместимость не проблема, так как интерфейс объектов един (унаследованный от System.Object). В результате вы знаете, что у любого и каждого объекта вашей иерархии – и, что важнее всего, в иерархиях кода

.NET сторонних разработчиков – всегда будет минимальный набор функциональности.

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

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

Поскольку CTS отслеживает каждый тип в системе, систему нельзя обмануть, выдав один тип за другой. Очевидно, это особенно важно для распределенных приложений, где приоритетом является защита.

Каждый тип отвечает за определение доступности своих членов, задавая модификатор доступа. Это делается для каждого члена в отдельности. Может быть задан любой вид доступа (если член объявлен как public), доступ может быть ограничен кругом унаследованных классов (если член объявлен как protected) или вовсе запрещен (при объявлении члена как private). Можно также разрешить доступ к члену только другим типам в составе текущего компилируемого модуля, если объявить его как internal.

2.2.5.2. Метаданные и отражение

Как уже говорилось в п. 2.2.4, CLS-совместимые компиляторы создают из нашего исходного кода код MSIL, подлежащий компиляции (с помощью JIT-компиляторов) перед своим выполнением. Помимо перевода исходного кода в MSIL-последовательность, CLS-совместимые компиляторы выполняют и другую столь же важную задачу: внедрение метаданных в выходной

EXE-файл.

Метаданные (metadata) – это данные, описывающие другие данные. В нашем контексте – это набор программных элементов EXE-файла, таких как типы и реализации методов. Эти метаданные похожи на библиотеки типов

30