
Программирование / 2 Философия .NET
.pdf2. Философия .NET
Прежде чем приступать к рассмотрению основных методов организации вычислений, необходимо сказать об основных принципах технологии .NET. Но сначала, немного истории по платформам и языкам программирования приложений для Windows с точки зрения Эндрю Троелсена.
2.1.Существующие проблемы разработки приложений
2.1.1.Язык C и API-интерфейс Windows
Традиционно разработка программного обеспечения для операционных систем семейства Windows подразумевала использование языка программирования C в сочетании с API-
интерфейсом Windows (Application Programming Interface — интерфейс прикладного программирования). И хотя то, что за счет применения этого проверенного временем подхода было успешно создано очень много приложений, мало кто станет спорить по поводу того, что процесс создания приложений с помощью одного только API-интерфейса является очень сложным занятием.
Первая очевидная проблема состоит в том, что C представляет собой очень лаконичный язык. Разработчики программ на языке C вынуждены мириться с необходимостью “вручную” управлять памятью, «безобразной» арифметикой указателей и ужасными синтаксическими конструкциями. Более того, поскольку C является структурным языком программирования, ему не хватает преимуществ, обеспечиваемых объектно-ориентированным подходом (здесь можно вспомнить о спагетти-подобном коде). Из-за сочетания тысяч глобальных функций и типов данных, определенных в API-интерфейса Windows, с языком, который и без того выглядит устрашающе, совсем не удивительно, что сегодня в обиходе присутствует столь много дефектных приложений.
2.1.2. Подход с применением языка C++ и платформы MFC
Огромным шагом вперед по сравнению с подходом, предполагающим применение языка C прямо с API-интерфейсом, стал переход на использование языка программирования C++. Язык C++ во многих отношениях может считаться объектно-ориентированной надстройкой поверх C. Из-за этого, хотя в случае его применения программисты уже могут начинать пользоваться преимуществами известных “главных столпов ООП” (таких как инкапсуляция, наследование и полиморфизм), они все равно вынуждены иметь дело с утомительными деталями языка C (вроде необходимости осуществлять управление памятью “вручную”, безобразной арифметики указателей и ужасных синтаксических конструкций).
Невзирая на сложность, сегодня существует множество платформ для программирования на
C++. Например, MFC (Microsoft Foundation Classes — библиотека базовых классов Microsoft)
предоставляет в распоряжение разработчику набор классов C++, которые упрощают процесс создания приложений Windows. Основное предназначение MFC заключается в представлении “разумного подмножества” исходного API-интерфейса Windows в виде набора классов, “магических” макросов и многочисленных средств для автоматической генерации программного кода (обычно называемых мастерами). Несмотря на очевидную пользу данной платформы приложений (и многих других основанных на C++ наборов средств), процесс программирования на C++ остается трудным и чреватым допущением ошибок занятием из-за его исторической связи с языком С.
2.1.3. Подход с применением Visual Basic 6.0
Благодаря искреннему желанию иметь возможность наслаждаться более простой жизнью, многие программисты перешли из “мира платформ” на базе C (C++) в мир менее сложных и более дружественных языков наподобие Visual Basic 6.0 (VB6). Язык VB6 стал популярным благодаря предоставляемой им возможности создавать сложные пользовательские интерфейсы, библиотеки программного кода (вроде COM-серверов) и логику доступа к базам данных с приложением минимального количества усилий. Во многом, как и в MFC, в VB6 сложности API-интерфейса
Windows скрываются из вида за счет предоставления ряда интегрированных мастеров, внутренних типов данных, классов и специфических функций VB.
Главный недостаток языка VB6 (который с появлением платформы .NET был устранен) состоит в том, что он является не полностью объектно-ориентированным, а скорее — просто “объектным”. Например, VB6 не позволяет программисту устанавливать между классами отношения “подчиненности” (т.е. прибегать к классическому наследованию) и не обладает никакой внутренней поддержкой для создания параметризованных классов. Более того, VB6 не предоставляет возможности для построения многопоточных приложений, если только программист не готов опускаться до уровня вызовов API-интерфейса Windows (что в лучшем случае является сложным, а в худшем — опасным подходом).
2.1.4. Подход с применением Java
Язык Java представляет собой объектно-ориентированный язык программирования, который своими синтаксическими корнями уходит в C++. Как многим известно, достоинства Java не ограничиваются одной лишь только поддержкой независимости от платформ. Java как язык не имеет многих из тех неприятных синтаксических аспектов, которые присутствуют в C++, а как платформа — предоставляет в распоряжение программистам большее количество готовых пакетов с различными определениями типов внутри. За счет применения этих типов программисты на Java могут создавать “на 100% чистые Java-приложения” с возможностью подключения к базе данных, поддержкой обмена сообщениями, веб-интерфейсами и богатым настольными интерфейсами для пользователей (а также многими другими службами).
Хотя Java и представляет собой очень элегантный язык, одной из потенциальных проблем является то, что применение Java обычно означает необходимость использования Java в цикле разработки и для взаимодействия клиента с сервером. Надежды на появление возможности интегрировать Java с другими языками мало, поскольку это противоречит главной цели Java — быть единственным языком программирования для удовлетворения любой потребности. В действительности, однако, в мире существуют миллионы строк программного кода, которым бы идеально подошло смешивание с более новым программным кодом на Java. К сожалению, Java делает выполнение этой задачи проблематичной. Пока в Java предлагаются лишь ограниченные возможности для получения доступа к отличным от Java API-интерфейсам.
2.2. Решение .NET
Платформа .NET Framework являет собой достаточно радикальную попытку сделать жизнь программистов легче. .NET Framework представляет собой программную платформу для создания приложений на базе семейства операционных систем Windows, а также многочисленных операционных систем производства не Microsoft, таких как Mac OS X и различные дистрибутивы Unix и Linux (хотя для не windows возможности намного более ограниченны, нежели у Java). Для начала не помешает привести краткий перечень некоторых базовых функциональных возможностей, которыми обладает .NET.
-Возможность обеспечения взаимодействия с существующим программным кодом.
-Поддержка для многочисленных языков программирования. Приложения .NET можно создавать с помощью любого множества языков программирования (С#, Visual Basic, F#, S# и
т.д.).
-Общий исполняющий механизм, используемый всеми поддерживающими .NET языками. Одним из аспектов этого механизма является наличие хорошо определенного набора типов, которые способен понимать каждый поддерживающий .NET язык.
-Полная и тотальная интеграция языков. В .NET поддерживается межъязыковое наследование, межъязыковая обработка исключений и межъязыковая отладка кода.
-Обширная библиотека базовых классов. Эта библиотека позволяет избегать сложностей, связанных с выполнением прямых вызовов к API-интерфейсу, и предлагает согласованную объектную модель, которую могут использовать все поддерживающие .NET языки.
2.3. Главные компоненты платформы .NET (CLR, CTS и CLS)
Теперь, когда о некоторых из предоставляемых .NET преимуществах уже известно, давайте вкратце ознакомимся с тремя ключевыми (и связанными между собой) сущностями, которые делают предоставление этих преимуществ возможным: CLR, CTS и CLS. С точки зрения программиста .NET представляет собой исполняющую среду и обширную библиотеку базовых классов. Уровень исполняющей среды называется общеязыковой исполняющей средой (Common Language Runtime) или, сокращенно, средой CLR. Главной задачей CLR является автоматическое обнаружение, загрузка и управление типами .NET (вместо программиста). Кроме того, среда CLR заботится о ряде низкоуровневых деталей, таких как управление памятью, обслуживание приложения, обработка потоков и выполнение различных проверок, связанных с безопасностью.
Другим составляющим компонентом платформы .NET является общая система типов (Common Type System) или, сокращенно, система CTS. В спецификации CTS представлено полное описание всех возможных типов данных и программных конструкций, поддерживаемых исполняющей средой, того, как эти сущности могут взаимодействовать друг с другом, и того, как они могут представляться в формате метаданных .NET.
Важно понимать, что любая из определенных в CTS функциональных возможностей может не поддерживаться в отдельно взятом языке, совместимом с .NET. Поэтому существует еще общеязыковая спецификация (Common Language Specification) или, сокращенно, спецификация CLS, в которой описано лишь то подмножество общих типов и программных конструкций, каковое способны воспринимать абсолютно все поддерживающие .NET языки программирования. Следовательно, в случае построения типов .NETT только с функциональными возможностями, которые предусмотрены в CLS, можно оставаться полностью уверенным в том, что все совместимые с .NET языки смогут их использовать. И, наоборот, в случае применения такого типа данных или конструкции программирования, которой нет в CLS, рассчитывать на то, что каждый язык программирования .NET сможет взаимодействовать с подобной библиотекой кода .NET, нельзя.
Помимо среды CLR и спецификаций CTS и CLS, в составе платформы .NET поставляется библиотека базовых классов, которая является доступной для всех языков программирования
.NET. В этой библиотеке не только содержатся определения различных примитивов, таких как потоки, файловый ввод-вывод, системы графической визуализации и механизмы для взаимодействия с различными внешними устройствами, но также предоставляется поддержка для целого ряда служб, требуемых в большинстве реальных приложений.
Например, в библиотеке базовых классов содержатся определения типов, которые способны упрощать процесс получения доступа к базам данных, манипулирования XML-документами, обеспечения программной безопасности и создания веб-, а также обычных настольных и консольных интерфейсов.
2.4. Что привносит язык C#
Из-за того, что платформа .NET столь радикально отличается от предыдущих технологий, в Microsoft разработали специально под нее новый язык программирования С# и среду разработки Visual Studio 2010 (VS). Синтаксис этого языка программирования очень похож на синтаксис языка Java. Однако сказать, что C# просто переписан с Java, будет неточно. И язык C#, и язык Java просто оба являются членами семейства языков программирования C (в которое также входят языки С, Objective С, C++ и т.д.) и потому имеют схожий синтаксис.
Более того, в C# поддерживается целый ряд функциональных возможностей, которые традиционно встречаются в различных функциональных языках программирования (к числу которых относятся лямбда-выражения и анонимные типы). Кроме того, с появлением технологии LINQ в C# стали поддерживаться еще и конструкции, которые делают его довольно уникальным в мире программирования. Несмотря на все это, наибольшее влияние на него все-таки оказали именно языки на базе С.
Благодаря тому факту, что C# представляет собой собранный из нескольких языков гибрид, он является таким же “чистым” с синтаксической точки зрения, как и язык Java, почти столь же
простым, как язык VB6, и практически таким же мощным и гибким как C++ (только без ассоциируемых с ним громоздких элементов). Все плюсы данного языка здесь перечислять бессмысленно, поскольку больше половины из них начинающему программисту будут попросту не понятны.
Возможно, наиболее важным моментом, о котором следует знать, программируя на С#, является то, что с помощью этого языка можно создавать только такой код, который будет выполняться в исполняющей среде .NET. Официально код, ориентируемый на выполнение в исполняющей среде .NET, называется управляемым кодом (managed code), двоичная единица, в которой содержится такой управляемый код — сборкой (assembly; о сборках будет более подробно рассказываться позже), а код, который не может обслуживаться непосредственно в исполняющей среде .NET — неуправляемым кодом (unmanaged code).
2.5.Жизнь в многоязычном окружении
Вначале процесса осмысления разработчиком нейтральной к языкам природы платформы
.NET, у него возникает множество вопросов и, прежде всего, следующий: если все языки .NET при компиляции преобразуются в управляемый код, то почему существует не один, а множество компиляторов? Ответить на этот вопрос можно по-разному. Программисты бывают очень привередливы, когда дело касается выбора языка программирования. Некоторые предпочитают языки с многочисленными точками с запятой и фигурными скобками, но с минимальным набором ключевых слов. Другим нравятся языки, предлагающие более “человеческие” синтаксические лексемы (вроде языка Visual Basic). Кто-то не желает отказываться от своего опыта работы на мэйнфреймах и предпочитает переносить его и на платформу .NET (использовать COBOL .NET).
А теперь ответьте честно: если бы в Microsoft предложили единственный “официальный” язык .NET, например, на базе семейства BASIC, то все ли программисты были бы рады такому выбору? Или если бы “официальный” язык .NET основывался на синтаксисе Fortran, то сколько людей в мире просто бы проигнорировало платформу .NET? Поскольку среда выполнения .NET демонстрирует меньшую зависимость от языка, используемого для построения управляемого программного кода, программисты .NET могут, не меняя своих синтаксических предпочтений, обмениваться скомпилированными сборками со своими коллегами, другими отделами и внешними организациями (не обращая внимания на то, какой язык .NET в них применяется).
Еще одно полезное преимущество интеграции различных языков .NET в одном унифицированном программном решении вытекает из того простого факта, что каждый язык программирования имеет свои сильные (а также слабые) стороны. Например, некоторые языки программирования обладают превосходной встроенной поддержкой сложных математических вычислений. В других лучше реализованы финансовые или логические вычисления, взаимодействие с мэйнфреймами и т.п. А когда преимущества конкретного языка программирования объединяются с преимуществами платформы .NET, выигрывают все.
Конечно, в реальности велика вероятность того, что будет возможность тратить большую часть времени на построение программного обеспечения с помощью предпочитаемого языка
.NET. Однако, после освоения синтаксиса одного из языков .NET, изучение синтаксиса какого-то другого языка существенно упрощается. Вдобавок это довольно выгодно, особенно тем, кто занимается консультированием по разработке ПО. Например, тому, у кого предпочитаемым языком является С#, в случае попадания в клиентскую среду, где все построено на Visual Basic, это все равно позволит эксплуатировать функциональные возможности .NET Framework и разбираться в общей структуре кодовой базы с минимальным объемом усилий и беспокойства.
2.6.Что собой представляют сборки в .NET
Какой бы язык .NET не выбирался для программирования, важно понимать, что хотя двоичные .NET-единицы имеют такое же файловое расширение, как и двоичные единицы COMсерверов и неуправляемых программ Win32 (*.dll или *.ехе), внутренне они устроены абсолютно по-другому. Пожалуй, самым важным является то, что они содержат не специфические, а наоборот, не зависящие от платформы инструкции на промежуточном языке (Intermediate

Language — IL), а также метаданные типов. На рис. 2.6.1 показано, как все это выглядит схематически.
Исходный код |
|
|
|
|
|
Компилятор C# |
|
|
|
на C# |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Исходный код |
|
|
|
|
|
Компилятор Perl |
|
|
|
на Perl |
|
|
|
|
|
|
|
Инструкции IL и |
|
|
|
|
|
|
|
|
|
|
метаданные |
|
|
|
|
|
Исходный код |
|
|
|
(*.dll или *.exe) |
|
|
|
||
|
Компилятор Lisp |
|
||
|
|
|
||
‘на Lisp |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Исходный код |
|
|
|
|
|
Компилятор C++/CLI |
|
|
|
на C++/CLI |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рисунок 2.6.1. Все .NET-компиляторы генерируют IL-инструкции и метаданные.
Относительно сокращения «IL» уместно сказать несколько дополнительных слов. В ходе разработки .NET официальным названием для IL было Microsoft Intermediate Language (MSIL).
Однако в вышедшей последней версии .NET это название было изменено на CIL (Common Intermediate Language — общий промежуточный язык). Поэтому при прочтении литературы по
.NET следует помнить о том, что IL, MSIL и CIL обозначают одно и то же. Для отражения современной терминологии будем применять аббревиатуру CIL.
При создании файла *.dll или *.ехе с помощью .NET-компилятора получаемый большой двоичный объект называется сборкой (assembly). Следует рассказать хотя бы об основных свойствах этого нового формата файлов.
2.6.1. CIL
Как уже упоминалось, в сборке содержится CIL-код, который концептуально похож на байткод Java тем, что не компилируется в ориентированные на конкретную платформу инструкции до тех пор, пока это не становится абсолютно необходимым. Обычно этот момент “абсолютной необходимости” наступает тогда, когда к какому-то блоку CIL-инструкций (например, к реализации метода) выполняется обращение для его использования в исполняющей среде .NET.
На этом этапе может возникнуть вопрос о том, какую выгоду приносит компиляция исходного кода в CIL, а не напрямую в набор ориентированных на конкретную платформу инструкций. Одним из самых важных преимуществ такого подхода является интеграция языков. Все компиляторы .NET генерируют примерно одинаковые CIL-инструкции. Благодаря этому все языки могут взаимодействовать в рамках четко обозначенной двоичной “арены”.
Более того, поскольку CIL не зависит от платформы, .NET Framework тоже получается не зависящей от платформы, предоставляя те же самые преимущества, к которым привыкли Javaразработчики (например, единую кодовую базу, способную работать во многих операционных системах). Уже существует международный стандарт языка С#, а также подмножество платформы
.NET и реализации для различных операционных систем, отличных от Windows). В отличие от Java, однако, .NET позволяет создавать приложения на предпочитаемом языке.
Из-за того, что в сборках содержатся CIL-инструкции, а не инструкции, ориентированные на конкретную платформу, CIL-код перед использованием должен обязательно компилироваться на лету. Объект, который отвечает за компиляцию CIL-кода в понятные ЦП инструкции, называется оперативным (just-in-time – JIT) компилятором. Иногда его “по-дружески” называют Jitter. Исполняющая среда .NET использует JIT-компилятор в соответствии с целевым процессором и оптимизирует его согласно лежащей в основе платформе.
Например, в случае создания .NET-приложения, предназначенного для развертывания на карманном устройстве (например, на мобильном устройстве, функционирующем под управлением Windows), соответствующий JIT-компилятор будет оптимизирован под функционирование в среде
сограниченным объемом памяти, а в случае развертывания сборки на серверной системе (где объем памяти редко представляет проблему), наоборот — под функционирование в среде с большим объемом памяти. Это дает разработчикам возможность писать единственный блок кода, который будет автоматически эффективно компилироваться JIT-компилятором и выполняться на машинах с разной архитектурой.
Более того, при компиляции CIL-инструкций в соответствующий машинный код JITкомпилятор будет помещать результаты в кэш в соответствии с тем, как того требует целевая операционная система. То есть при вызове, некоторого метода в первый раз, соответствующие CIL-инструкции будут компилироваться в ориентированные на конкретную платформу инструкции и сохраняться в памяти для последующего использования, благодаря чему при вызове этого метода в следующий раз компилировать их снова не понадобится.
Можно также выполнять “предварительную JIT-компиляцию” при инсталляции приложения
спомощью утилиты командной строки ngen.exe, которая поставляется в составе набора .NET Framework 4.0 SDK. Применение такого подхода позволяет улучшить показатели по времени запуска для приложений, насыщенных графикой.
2.6.2. Метаданные
Помимо СIL-инструкций, в сборке .NET содержатся исчерпывающие и точные метаданные, которые описывают каждый определенный в двоичном файле тип (например, класс, структуру или перечисление), а также всех его членов (например, свойства, методы или события). К счастью, за генерацию новейших и наилучших метаданных по типам всегда отвечает компилятор, а не программист. Из-за того, что метаданные .NET являются настолько детальными, сборки представляют собой полностью самоописываемые (self-describing) сущности.
Метаданные используются во многих операциях самой исполняющей среды .NET, а также в различных средствах разработки. Например, функция IntelliSense, предлагаемая в среде Visual Studio 2010, работает за счет считывания метаданных сборки во время проектирования. Кроме того, метаданные используются в различных утилитах для просмотра объектов, инструментах отладки и в самом компиляторе языка С#. Можно с полной уверенностью утверждать, что метаданные играют ключевую роль во многих .NET-технологиях, в том числе в Windows Communication Foundation (WCF), рефлексии, динамическом связывании и сериализации объектов.
2.6.3. Манифест
И, наконец, помимо CIL и метаданных типов, сами сборки тоже описываются с помощью метаданных, которые официально называются манифестом (manifest). В каждом таком манифесте содержится информация о текущей версии сборки, сведения о культуре (применяемые для локализации строковых и графических ресурсов) и перечень ссылок на все внешние сборки, которые требуются для правильного функционирования. Существует много разнообразных инструменты, которые можно использовать для изучения типов, метаданных и манифестов сборок. Сейчас мы их рассматривать не будем.
Как и за генерацию метаданных типов, за генерацию манифеста сборки тоже всегда отвечает компилятор.