Андрей Колесов

Официальный старт платформы Microsoft .NET состоялся 13 февраля 2002 г. В этот день на грандиозной презентации в Сан-Франциско были представлены рабочие версии двух ключевых элементов платформы - операционной среды .NET Framework и инструментального набора Visual Studio .NET. Событие весьма значительное для всего мирового ИТ-сообщества: игнорировать существование этой платформы не могут даже те, кто не делает ставку на технологии Microsoft.

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

Именно поэтому я решил высказать некоторые соображения об архитектуре .NET Framework (основы всей платформы) в дополнение к статьям, опубликованным в "BYTE/Россия", начиная с № 12'2001. Каждый автор освещает проблему исходя из собственного опыта, а рассмотрение любого объекта под разными углами зрения всегда делает его представление более рельефным…

Каркас, да не тот

Мне кажется, что дословный перевод слова Framework (каркас) не очень точно отражает суть примененного Microsoft термина. Больше подходит "платформа" (набор базовых строительных блоков), на основе которой строятся приложения. Заметим, что понятие "платформа", в том числе и .NET Framework, мы используем в двух разных смыслах. С одной стороны, это "концепция" (идеи, спецификации и т. д.), с другой - набор вполне конкретных объектов (файлов, документации и т. п.). Предложенная Владимиром Биллигом аналогия между библиотеками MFC и Class Library .NET Framework вполне понятна, но все же при этом хотелось бы сделать акцент на некоторых принципиальных различиях между этими объектами.

Во-первых, базовые классы .NET - общие для всех систем программирования, точнее, для всех приложений, работающих в среде .NET. А это означает, что изменилось соотношение язык - базовые функции: если MFC делались под VC++, то теперь язык программирования адаптируется к платформе. Изменения в C# (по сравнению с C/C++) и в VB.NET (по сравнению с VB 6.0) были предопределены именно этой сменой позиций. При этом сама предлагаемая идея и ее реализация не очень новы. Еще четверть века назад наличие общих библиотек подпрограмм для разных языков программирования было совершенно естественной вещью (правда, это были в основном математические функции, но такова тогда была основная направленность применения вычислительной техники).

Во-вторых, MFC - это набор статических объектных модулей (не будем путать библиотеки объектных модулей и библиотеки объектов). Они подключаются к приложению на этапе компоновки исполняемого модуля программы и становятся при этом его неотъемлемой частью. В то же время .NET Class Library - это динамические библиотеки классов, которые являются составной частью операционной среды (специальным видом исполняемых модулей) и используются приложением только в момент его выполнения. Разницу между использованием объектных библиотек и библиотек классов (объектов) мы еще обсудим, но в данном случае важно, что степень зависимости прикладной программы от платформы теперь стала существенно выше.

Вспомним о компиляции и компоновке

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

csc /target:exe /reference:Hello.dll ClientAndServer.cs"

Но ведь очевидно, что хотя разработчики и называют программу CSC компилятором (C-Sharp Compiler), на самом деле она задействует две операции - компиляции и компоновки. И используемые в примере ключи относятся именно к компоновке.

Мне не хотелось бы вдаваться сейчас в обсуждение достоинств и недостатков двух вариантов подключения повторно используемого программного кода. (Почему-то часто в публикациях встречаются высказывания о том, что история применения повторно используемых компонентов началась с DLL. На самом деле все началось задолго до появления MS-DOS и тем более Windows.) Но должен констатировать очевидную для меня истину - существует довольно много ситуаций, когда вариант со "статической" компоновкой имеет явные преимущества.

Во времена MS-DOS у разработчика была возможность выбора. Например, QuickBasic позволял делать исполняемые модули в вариантах Stand-Alone и Requiring Run-Time, вспомогательные функции можно было применять как в виде объектных, так и в исполняемых форматах. В свое время я испытал большое разочарование, когда не обнаружил таких возможностей в VB. Теперь отсутствие выбора оптимального с точки зрения разработчика варианта зафиксировано на уровне операционной среды.

Структура .NET-приложения

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

Хотя при создании нового проекта в среде Visual Studio .NET предлагается довольно большой список типов проектов (рис. 1), но на самом деле есть всего три основные разновидности приложений - Windows Application, Console Application и Class Library (рис. 2). Все остальное - это их различные вариации за счет использования тех или иных шаблонов или мастеров (именно поэтому правое окно на рис. 1 и называется Templates), обеспечивающих автоматическое выполнение каких-то начальных действий, которые при желании можно выполнить и "руками" (в том числе изменив и базовый тип приложения). Пользователь может подключить и свои собственные варианты шаблонов.

Fig.1
Рис. 1. Перечень базовых шаблонов можно расширить самостоятельно.

Fig.2
Рис. 2. Предусмотрены три основных типа приложений - Windows Application, Console Application и Class Library.

Однако мне кажется, что не следует воспринимать перечень ссылок, автоматически формируемых при создании нового проекта, как каркас, основу данного приложения. И вообще, нужно четко видеть различия между описанием ссылок на используемые ресурсы - References в окне Solution Explorer и описанием применяемых в программе пространств имен - набор оператор Using в окне кода (рис. 3).

Fig.3
Рис. 3. Идентификаторы библиотек и пространств имен могут не совпадать.

Как известно, библиотеки классов .NET реализованы в виде набора DLL-файлов (сейчас их 20), имена которых начинаются с идентификатора System (рис. 4). Но имена библиотек и используемых в них идентификаторов пространств имен - это разные вещи. Они могут совпадать, а могут и не совпадать (хотя бы потому, что одна библиотека может включать несколько пространств имен). Такое совпадение/несовпадение хорошо видно на рис. 3.

Fig.4
Рис. 4. Состав библиотек базовых классов.

Оператор Using для C# (в VB.NET он почему-то называется Imports) не подключает никаких классов, а лишь дает возможность использовать не полные, а сокращенные имена компонентов (кстати, эту возможность нужно применять очень осторожно, чтобы избежать возможных конфликтов сокращенных имен из разных библиотек). Отметим также, что начальный набор таких описаний в C# формируется автоматически, а в VB его нужно создавать вручную.

Собственно же подключение внешних классов выполняется с помощью команды References. Однако тут самое интересное заключается в том, что список ссылок References тоже не определяет "каркас" приложения - по той простой причине, что все системные классы .NET автоматически доступны, независимо от того, прописаны они в References или нет (рис. 5). Зачем VB.NET прописывает какие-то ссылки на библиотеки System, пока не очень понятно (все работает и без этих ссылок). Правда, они позволяют реализовать режим создания локальной копии DLL в сборке приложения, но для системных библиотек это требуется довольно редко.

Fig.5
Рис. 5. Системные пространства имен доступны даже в отсутствие ссылок на соответствующие библиотеки.

Реально же описание ссылок References обязательно только для несистемных компонентов. Так, в командной строке компиляции-компоновки в параметре /reference не указываются системные библиотеки, хотя в самом коде используется директива Using, позволяющая использовать сокращенные имена классов.

Это к вопросу о пользе изучения параметров командной строки компиляции-компоновки. И тут опять хотелось бы взгрустнуть о старых временах - в QB ссылка на описание этих опций размещалась на первой странице справочной системы, а сейчас для того, чтобы найти их, нужно изрядно покопаться в документации .NET Framework SDK.

С# или Java? Или VB? Или?..

Стало уже традицией в рассказе о C# проводить аналогии с Java. Сразу отмечу, что упреки в том, что Microsoft скопировала Java (подобные обвинения часто встречаются в интернетовских дискуссиях разработчиков) не очень состоятельны. Конечно, разработчики C# имели возможность учесть опыт Java, но сходство двух языков очень просто объяснить наличием общего родителя в лице C/C++ и необходимостью решения весьма схожих в целом задач.

Однако сейчас мне хотелось бы подчеркнуть, что сравнение C# и Java, несмотря на всю свою увлекательность, представляет в основном академический интерес. Ведь на самом деле имеет смысл сравнивать не отдельные языки, а платформы .NET и J2EE в целом. При этом наличие, например, перечислений в том или ином языке - это явно второстепенный фактор.

Если вы выбрали .NET в качестве платформы, то актуальным будет совсем иное сравнение: какой язык из представленного в этой системе набора выбрать для решения своей задачи? В результате сравнительный анализ C# с тем же VB.NET представляется более интересным с практической точки зрения. (Не стану сейчас заниматься этим, так как имею о C# весьма поверхностное представление, хотя было бы интересно увидеть такой анализ специалиста), но выскажу некоторые соображения на несколько иную тему. Интересно было бы и сравнить C# с C/C++. Правда, эти языки также находятся в разных весовых категориях: C/C++ позиционируется как достаточно автономный инструмент создания системных средств, в первую очередь вне среды .NET Framework, а C# - исключительно для разработки .NET-приложений.)

Еще самая первая информации о появлении C# и новшествах VB вызвала у меня вопрос: а зачем создавать еще один язык программирования? Ведь было бы гораздо проще, учитывая радикальные изменения в VB.NET, довести их до логического конца, придав VB недостающую новую функциональность.

Одна из причин появления C# представляется очевидной - использование синтаксиса C/C++ обеспечит простой путь миграции системных программистов в среду .NET Framework. Не столь очевиден другой момент: в силу достаточно понятных причин Microsoft хочет иметь два похожих, но в то же время разных инструмента (есть еще J# и JScript, но они не входят в число основных игроков на поле .NET). Действительно, при наличии единой платформы - в виде библиотек базовых классов унификация средств разработки выполняется достаточно просто, и она выглядела бы вполне естественной, логичной.

Строго говоря, единственный "родной" для .NET Framework язык - это Microsoft Intermediate Language (MSIL). Это также С-подобный язык, на котором можно писать исходные тексты с целью последующей компиляции и т. п., только он не включен в среду Visual Studio .NET. Почему не включен? Ведь использование единого промежуточного языка и одного набора базовых классов обеспечивает простоту объединения модулей, написанных на C# и VB.NET, в рамках одного проекта. Но эта потенциальная возможность в .NET Framework не реализована.

Действительно, почему бы, подвергая VB коренной реконструкции и создавая потенциальные проблемы с переносимостью кода предыдущих версий, не включить в инструментарий средства работы с указателями или хотя бы с беззнаковым целочисленным типом данных? И почему для одной и той же операции описания пространств имен в двух языках создаются новые операторы с разными названиями (Using и Imports)? Не говорю о таких "мелочах", как ссылки, которые в одной системе прописываются автоматически, а в другой требуют "ручной" обработки (таких примеров можно привести немало). Я привожу здесь примеры слабых сторон VB, но уверен, что этот инструмент имеет и свои преимущества по сравнению с C#.

В советские времена нас учили, что двухпартийная американская система - это яркий пример козней капиталистов, которые таким образом создают у трудящихся масс иллюзию демократии и свободы выбора. А Великобританию всегда обвиняли в проведении политики "разделяй и властвуй". Почему-то именно эти ассоциации возникают у меня при обсуждении семейства языков .NET Framework. Если различия сохраняются, значит, это кому-нибудь нужно?