Из этого правила можно (и нужно) сделать вывод о том, что все остальные правила в CLS не распространяются на логику, применяемую для построения внутренних рабочих деталей типа .NET. Единственными аспектами типа, которые должны соответствовать CLS, являются сами определения членов (т.е. соглашения об именовании, параметры и возвращаемые типы). В рамках логики реализации члена может применяться любое количество и не согласованных с CLS приемов, поскольку для внешнего мира это не будет играть никакой роли.
Помимо среды CLR и спецификаций CTS и CLS, в составе платформы .NET поставляется библиотека базовых классов, которая является доступной для всех языков программирования .NET. В этой библиотеке не только содержатся определения различных примитивов, таких как потоки, файловый ввод-вывод, системы графической визуализации и механизмы для взаимодействия с различными внешними устройствами, но также предоставляется поддержка для целого ряда служб, требуемых в большинстве реальных приложений.
Например, в библиотеке базовых классов содержатся определения типов, которые способны упрощать процесс получения доступа к базам данных, манипулирования XML-документами, обеспечения программной безопасности и создания веб-, а также обычных настольных и консольных интерфейсов. На высоком уровне взаимосвязь между CLR, CTS, CLS и библиотекой базовых классов выглядит так, как показано на рис. 2.
Рис. 2. Отношение между CLR, CTS, CLS и библиотеками базовых классов
Наиболее важным моментом, о котором следует знать, программируя на С#, является то, что с помощью этого языка можно создавать только такой код, который будет выполняться в исполняющей среде .NET. Официально код, ориентируемый на выполнение в исполняющей среде .NET, называется управляемым кодом (managed code), двоичная единица, в которой содержится такой управляемый код — сборкой (assembly), а код, который не может обслуживаться непосредственно в исполняющей среде .NET — неуправляемым кодом (unmanaged code). Сборка получается при создании файла *.dll или *.exe с помощью .NET-компилятора.
Важно понимать, что двоичные .NЕТ-единицы содержат не специфические, а наоборот, не зависящие от платформы инструкции на промежуточном языке (Intermediate Language — IL), а также метаданные типов. На рис. 3 показано, как все это выглядит схематически.
Рис. 3. Генерация IL-инструкций и метаданных
В сборке содержится CIL-код, который не компилируется в ориентированные на конкретную платформу инструкции до тех пор, пока это не становится абсолютно необходимым. Обычно этот момент "абсолютной необходимости" наступает тогда, когда к какому-то блоку CIL-инструкций (например, к реализации метода) выполняется обращение для его использования в исполняющей среде .NET.
Помимо CIL-инструкций, в сборках также содержатся метаданные, которые детально описывают особенности каждого имеющегося внутри данной двоичной .NET-единицы "типа". Например, при наличии класса по имени MyClass они будут описывать детали наподобие того, как выглядит базовый класс этого класса MyClass, какие интерфейсы реализует MyClass (если вообще реализует), а также, подробно, какие члены он поддерживает. Метаданные .NET всегда предоставляются внутри сборки и автоматически генерируются компилятором соответствующего распознающего .NET языка.
Помимо CIL и метаданных типов, сами сборки тоже описываются с помощью метаданных, которые официально называются манифестом (manifest). В каждом таком манифесте содержится информация о текущей версии сборки, сведения о культуре (применяемые для локализации строковых и графических ресурсов) и перечень ссылок на все внешние сборки, которые требуются для правильного функционирования.
Одним из самых важных преимуществ такого подхода является интеграция языков: все компиляторы .NET генерируют примерно одинаковые CIL-инструкции. Кроме того, поскольку CIL не зависит от платформы, .NET Framework тоже получается не зависящей от платформы.
Из-за того, что в сборках содержатся CIL-инструкции, а не инструкции, ориентированные на конкретную платформу, CIL-код перед использованием должен обязательно компилироваться на лету. Объект, который отвечает за компиляцию CIL-кода в понятные ЦП инструкции, называется оперативным (just-in-time — JIT) компилятором.
Пространство имен определяет область объявлений, в которой допускается хранить одно множество имен отдельно от другого. По существу, имена, объявленные в одном пространстве имен, не будут вступать в конфликт с аналогичными именами, объявленными в другой области. Так, в библиотеке классов для среды .NET Framework, которая одновременно является библиотекой классов С#, используется пространство имен System. Именно поэтому строка кода
using System;
обычно вводится в самом начале любой программы на С#.
Пространства имен важны потому, что в последнее время в программировании всё больше возрастает число имён переменных, методов, свойств и классов, применяемых в библиотечных программах, стороннем и собственном коде. Поэтому без отдельных пространств все эти имена будут соперничать за место в глобальном пространстве имен, порождая конфликтные ситуации. Так, если в программе определен класс Abc, то этот класс может вступить в конфликт с другим классом Abc, доступным в сторонней библиотеке, используемой в этой программе. К счастью, подобного конфликта можно избежать, используя отдельные пространства имен, ограничивающие область видимости объявленных в них имен.
Пространство имен объявляется с помощью ключевого слова namespace. Ниже приведена общая форма объявления пространства имен:
namespace имя {
// члены
}
где имя обозначает конкретное имя объявляемого пространства имен. При объявлении пространства имен определяется область его действия. Все, что объявляется непосредственно в этом пространстве, оказывается в пределах его области действия. В пространстве имен можно объявить классы, структуры, делегаты, перечисления, интерфейсы или другие пространства имен. Обращение к членам конкретного пространства имён происходит через оператор «точка».
Пространства имён допускается распределять по отдельным файлам.
Если в программе присутствуют частые ссылки на члены конкретного пространства имен, то указывать это пространство всякий раз, когда требуется ссылка на него, не очень удобно. Преодолеть это затруднение помогает директива using.
С помощью неё можно сделать видимыми вновь создаваемые пространства имен. Существуют две формы директивы using. Ниже приведена первая из них:
using имя;
где имя обозначает имя того пространства имен, к которому требуется получить доступ.
Все члены, определенные в указанном пространстве имен, становятся видимыми, и поэтому могут быть использованы без дополнительного определения их имен. Директиву using необходимо вводить в самом начале каждого файла исходного кода перед любыми другими объявлениями или же в начале тела пространства имен.
Вторая форма директивы using позволяет определить еще одно имя (так называемый псевдоним) типа данных или пространства имен. Эта форма приведена ниже:
using псевдоним = имя;
где псевдоним становится еще одним именем типа (например, типа класса) или пространства имен, обозначаемого как имя. После того как псевдоним будет создан, он может быть использован вместо первоначального имени.
Пол, одним именем можно объявить несколько пространств имен. Это дает возможность распределить пространство имен по нескольким файлам или даже разделить его в пределах одного и того же файла исходного кода. Также одно пространство имен может быть вложено в другое. В качестве примера рассмотрим следующую программу.
Если в программе не объявлено пространство имен, то по умолчанию используется глобальное пространство имен. Глобальное пространство удобно для коротких программ, но в большинстве случаев реальный код содержится в объявляемом пространстве имен. Главная причина инкапсуляции кода в объявляемом пространстве имен — предотвращение конфликтов имен. Пространства имен служат дополнительным средством, помогающим улучшить организацию программ и приспособить их к работе в сложной среде с современной сетевой структурой.
Пространства имен помогают предотвратить конфликты имен, но не устранить их полностью. Такой конфликт может, в частности, произойти, когда одно и то же имя объявляется в двух разных пространствах имен и затем предпринимается попытка сделать видимыми оба пространства. Допустим, что два пространства имен содержат класс MyClass. Если попытаться сделать видимыми оба пространства имен с помощью директивой using, то имя MyClass из первого пространства вступит в конфликт с именем MyClass из второго пространства, обусловив появление ошибки неоднозначности. В таком случае для указания предполагаемого пространства имен явным образом можно воспользоваться описателем псевдонима пространства имен ::.
Ниже приведена общая форма оператора ::.
псевдоним_пространства_имен::идентификатор
Здесь псевдоним_пространства_имен обозначает конкретное имя псевдонима пространства имен, а идентификатор — имя члена этого пространства.
Сборка представляет собой один или несколько файлов, содержащих все необходимые сведения о развертывании программы и ее версии. Сборки составляют основу среды .NET. Они предоставляют механизмы для надежного взаимодействия компонентов, межъязыковой возможности взаимодействия и управления версиями. Кроме того, сборки определяют область действия программного кода.
Сборка состоит из четырех разделов. Первый раздел представляет собой декларацию сборки. Декларация содержит сведения о самой сборке. К этой информации относится, в частности, имя сборки, номер ее версии, сведения о соответствии типов и параметры культурной среды (язык и региональные стандарты). Второй раздел сборки содержит метаданные типов, т.е. сведения о типах данных, используемых в программе. Среди прочих преимуществ метаданные типов способствуют межъязыковой возможности взаимодействия. Третий раздел сборки содержит программный код в формате MSIL (Microsoft Intermediate Language — промежуточный язык корпорации Microsoft). И четвертый раздел сборки содержит ресурсы, используемые программой.
Исполняемый файл, создаваемый во время компиляции программы на С#, представляет собой сборку, содержащую исполняемый код этой программы, а также другие виды информации. Таким образом, когда компилируется программа на С#, сборка получается автоматически.
В одной сборке может содержаться любое количество пространств имен, каждое из которых, в свою очередь, может иметь любое число типов.
Помимо указания пространства имен с помощью поддерживаемого в С# ключевого слова using, компилятору С# необходимо сообщить имя сборки, в которой содержится само CIL-oпpeделение упоминаемого типа. Многие из ключевых пространств имен .NET находятся внутри сборки mscorlib.dll.
Подавляющее большинство сборок в .NET Framework размещено в специально предназначенном для этого каталоге, который называется глобальным кэшем сборок (Global Assembly Cache — GAC). На машине Windows по умолчанию GAC может располагаться внутри каталога %windir%\assembly
В зависимости от того, какое средство применяется для разработки приложений .NET, на выбор может оказываться доступными несколько различных способов для уведомления компилятора о том, какие сборки требуется включить в цикл компиляции.
Программистам на С# никогда не приходится непосредственно удалять управляемый объект из памяти (в языке С# нет даже ключевого слова вроде delete). Вместо этого объекты .NET размещаются в области памяти, которая называется управляемой кучей (managed heap), откуда они автоматически удаляются сборщиком мусора, когда наступает "определенный момент в будущем".
Для начала вспомним, что класс представляет собой схему, которая описывает то, каким образом экземпляр данного типа должен выглядеть и вести себя в памяти. Определяются классы в файлах кода (которым по соглашению назначается расширение *.cs). Как только класс определен, с использованием ключевого слова new, поддерживаемого в С#, можно размещать в памяти любое количество его объектов. Однако при этом следует помнить, что ключевое слово new возвращает ссылку на объект в куче, а не фактический объект. Если ссылочная переменная объявляется как локальная переменная в контексте метода, она сохраняется в стеке для дальнейшего использования в приложении. Для вызова членов объекта к сохраненной ссылке должна применяться операция точки С#.
На рис. 4 схематично показаны отношения между классами, объектами и ссылками на них.
Ссылки на объекты в управляемой куче
Рис. 4. Отношения между классами объектами и ссылками
При создании приложений на С# можно смело полагать, что исполняющая среда .NET будет сама заботиться об управляемой куче без непосредственного вмешательства со стороны программиста. На самом деле "золотое правило" по управлению памятью в .NET звучит просто: «Размещайте объект в управляемой куче с использованием ключевого слова new и забывайте об этом.»