Текст книги "C# 4.0: полное руководство"
Автор книги: Герберт Шилдт
Жанр:
Программирование
сообщить о нарушении
Текущая страница: 40 (всего у книги 83 страниц)
}
}
могут быть указаны следующим образом.
namespace OuterNS.InnerNS {
// ...
}
Глобальное пространство имен
Если в программе не объявлено пространство имен, то по умолчанию используется глобальное пространство имен. Именно поэтому в примерах программ, представленных в предыдущих главах книги, не нужно было обращаться для этой цели к ключевому слову namespace
. Глобальное пространство удобно для коротких программ, как в примерах из этой книги, но в большинстве случаев реальный код содержится в объявляемом пространстве имен. Главная причина инкапсуляции кода в объявляемом пространстве имен – предотвращение конфликтов имен. Пространства имен служат дополнительным средством, помогающим улучшить организацию программ и приспособить их к работе в сложной среде с современной сетевой структурой.
Применение описателя псевдонима пространства имен ::
Пространства имен помогают предотвратить конфликты имен, но не устранить их полностью. Такой конфликт может, в частности, произойти, когда одно и то же имя объявляется в двух разных пространствах имен и затем предпринимается попытка сделать видимыми оба пространства. Допустим, что два пространства имен содержат класс MyClass
. Если попытаться сделать видимыми оба пространства имен с помощью директив using, то имя MyClass
из первого пространства вступит в конфликт с именем MyClass
из второго пространства, обусловив появление ошибки неоднозначности. В таком случае для указания предполагаемого пространства имен явным образом можно воспользоваться описателем псевдонима пространства имен ::
.
Ниже приведена общая форма оператора ::
.
псевдоним_пространства_имен :: идентификатор
Здесь псевдоним_пространства_имен обозначает конкретное имя псевдонима пространства имен, а идентификатор – имя члена этого пространства.
Для того чтобы стало понятнее назначение описателя псевдонима пространства имен, рассмотрим следующий пример программы, в которой создаются два пространства имен, Counter и AnotherCounter
, и в обоих пространствах объявляется класс CountDown
. Затем оба пространства имен становятся видимыми с помощью директив using
. И наконец, в методе Main()
предпринимается попытка получить экземпляр объекта типа CountDown
.
// Продемонстрировать необходимость описателя ::.
using System;
// Использовать оба пространства имен Counter и AnotherCounter.
using Counter;
using AnotherCounter;
// Объявить пространство имен для счетчиков,
namespace Counter {
// Простой вычитающий счетчик,
class CountDown {
int val;
public CountDown(int n) {
val = n;
}
// ...
}
}
// Объявить еще одно пространство имен для счетчиков,
namespace AnotherCounter {
// Объявить еще один класс CountDown, принадлежащий
// пространству имен AnotherCounter.
class CountDown {
int val;
public CountDown(int n) {
val = n;
}
}
}
class WhyAliasQualifier {
static void Main() {
int i;
// Следующая строка, по существу, неоднозначна!
// Неясно, делается ли в ней ссылка на класс CountDown
// из пространства имен Counter или AnotherCounter?
CountDown cd1 = new CountDown(10); // Ошибка!!!
// ...
}
}
Если попытаться скомпилировать эту программу, то будет получено сообщение об ошибке, уведомляющее о неоднозначности в следующей строке кода из метода Main()
.
CountDown cd1 = new CountDown(10); // Ошибка!!!
Причина подобной неоднозначности заключается в том, что в обоих прострайствах имен, Counter
и AnotherCounter
, объявлен класс CountDown
и оба пространства сделаны видимыми. Поэтому неясно, к какому именно варианту класса CountDown
следует отнести приведенное выше объявление. Для устранения подобного рода недоразумений и предназначен описатель ::
.
Для того чтобы воспользоваться описателем ::
, необходимо сначала определить псевдоним для пространства имен, которое требуется описать, а затем дополнить описание неоднозначного элемента этим псевдонимом. Ниже приведен вариант предыдущего примера программы, в котором устраняется упомянутая выше неоднозначность.
// Продемонстрировать применение описателя ::.
using System;
using Counter;
using AnotherCounter;
// Присвоить классу Counter псевдоним Ctr.
using Ctr = Counter;
// Объявить пространство имен для счетчиков,
namespace Counter {
// Простой вычитающий счетчик,
class CountDown {
int val;
public CountDown(int n) {
val = n;
}
}
}
// Объявить еще одно пространство имен для счетчиков,
namespace AnotherCounter {
// Объявить еще один класс CountDown, принадлежащий
// пространству имен AnotherCounter.
class CountDown {
int val;
public CountDown(int n) {
val = n;
}
// ...
}
}
class AliasQualifierDemo {
static void Main() {
// Здесь оператор :: разрешает конфликт, предписывая компилятору
// использовать класс CountDown из пространства имен Counter.
Ctr::CountDown cd1 = new Ctr::CountDown(10);
// ...
}
}
В этом варианте программы для класса Counter
сначала указывается псевдоним Ctr
в следующей строке кода.
using Ctr = Counter;
А затем этот псевдоним используется в методе Main()
для дополнительного описания класса CountDown
, как показано ниже.
Ctr::CountDown cd1 = new Ctr::CountDown(10);
Описатель ::
устраняет неоднозначность, поскольку он явно указывает на то, что следует обратиться к классу CountDown
из пространства Ctr
, а фактически – Counter
. Именно это и делает теперь программу пригодной для компиляции.
Описатель ::
можно также использовать вместе с предопределенным идентификатором global
для ссылки на глобальное пространство имен. Например, в приведенной ниже программе класс CountDown
объявляется как в пространстве имен Counter
, так и в глобальном пространстве имен. А для доступа к варианту класса CountDown
в глобальном пространстве имен служит предопределенный псевдоним global
.
// Использовать псевдоним глобального пространства имен,
using System;
// Присвоить классу Counter псевдоним Ctr.
using Ctr = Counter;
namespace Counter {
// Простой вычитающий счетчик,
class CountDown { int val;
public CountDown(int n) {
val = n;
}
//...
}
}
// Объявить еще один класс CountDown, принадлежащий
// глобальному пространству имен,
class CountDown {
int val;
public CountDown(int n) {
val = n;
}
// ...
}
class GlobalAliasQualifierDemo {
static void Main() {
// Здесь описатель :: предписывает компилятору использовать
// класс CountDown из пространства имен Counter.
Ctr::CountDown cd1 = new Ctr::CountDown(10);
// Далее создать объект класса CountDown из
// глобального пространства имен.
global::CountDown cd2 = new global::CountDown(10) ;
// ...
}
}
Обратите внимание на то, что идентификатор global
служит для доступа к классу CountDown
из используемого по умолчанию пространства имен.
global::CountDown cd2 = new global::CountDown(10) ;
Этот подход можно распространить на любую ситуацию, в которой требуется указывать используемое по умолчанию пространство имен.
И последнее: описатель псевдонима пространства имен можно применять вместе с псевдонимами типа extern
, как будет показано в главе 20.
В C# определен ряд директив препроцессора, оказывающих влияние на интерпретацию исходного кода программы компилятором. Эти директивы определяют порядок интерпретации текста программы перед ее трансляцией в объектный код в том исходном файле, где они появляются. Термин директива препроцессора появился в связи с тем, что подобные инструкции по традиции обрабатывались на отдельной стадии компиляции, называемой препроцессором. Обрабатывать директивы на отдельной стадии препроцессора в современных компиляторах уже не нужно, но само ее название закрепилось.
Ниже приведены директивы препроцессора, определенные в С#.
# define
#elif
#else
#endif
#endregion
#error
#if
#line
#pragma
#region
#undef
#warning
Все директивы препроцессора начинаются со знака #. Кроме того, каждая директива препроцессора должна быть выделена в отдельную строку кода.
Принимая во внимание современную объектно-ориентированную архитектуру языка С#, потребность в директивах препроцессора в нем не столь велика, как в языках программирования предыдущих поколений. Тем не менее они могут быть иногда полезными, особенно для условной компиляции. В этом разделе все директивы препроцессора рассматриваются по очереди.
Директива #define
Директива #define
определяет последовательность символов, называемую идентификатором. Присутствие или отсутствие идентификатора может быть определено с помощью директивы #if
или #elif
и поэтому используется для управления процессом компиляции. Ниже приведена общая форма директивы #define
.
#define идентификатор
Обратите внимание на отсутствие точки с запятой в конце этого оператора. Между директивой #define
и идентификатором может быть любое количество пробелов, но после самого идентификатора должен следовать только символ новой строки. Так, для определения идентификатора EXPERIMENTAL служит следующая директива.
#define EXPERIMENTAL
–
ПРИМЕЧАНИЕ
В C/C++ директива #define может использоваться для подстановки исходного текста, например для определения имени значения, а также для создания макрокоманд, похожих на функции. А в C# такое применение директивы #define не поддерживается. В этом языке директива #define служит только для определения идентификатора.
–
Директивы #if и #endif
Обе директивы, #if
и #endif
, допускают условную компиляцию последовательности кода в зависимости от истинного результата вычисления выражения, включающего в себя один или несколько идентификаторов. Идентификатор считается истинным, если он определен, а иначе – ложным. Так, если идентификатор определен директивой #define
, то он будет оценен как истинный. Ниже приведена общая форма директивы #if
.
#if идентификаторное_выражение
последовательность операторов
#endif
Если идентификаторное_выражение, следующее после директивы #if
, истинно, то компилируется код (последовательность операторов), указываемый между ним и директивой #endif
. В противном случае этот промежуточный код пропускается. Директива #endif
обозначает конец блока директивы #if
.
Идентификаторное выражение может быть простым, как наименование идентификатора. В то же время в нем разрешается применение следующих операторов: !, ==, ! =, && и ||
, а также круглых скобок.
Ниже приведен пример применения упомянутых выше директив.
// Продемонстрировать применение директив // #if, #endif и #define.
#define EXPERIMENTAL
using System;
class Test {
static void Main() {
#if EXPERIMENTAL
Console.WriteLine(«Компилируется для экспериментальной версии.»);
#endif
Console.WriteLine(«Присутствует во всех версиях.»);
}
}
Этот код выдает следующий результат.
Компилируется для экспериментальной версии.
Присутствует во всех версиях.
В приведенном выше коде определяется идентификатор EXPERIMENTAL. Поэтому когда в этом коде встречается директива #if
, идентификаторное выражение вычисляется как истинное и затем компилируется первый оператор, содержащий вызов метода WriteLine()
. Если же удалить определение идентификатора EXPERIMENTAL и перекомпилировать данный код, то первый оператор, содержащий вызов метода WriteLine()
, не будет скомпилирован, поскольку идентификаторное выражение директивы #if
вычисляется как ложное. Но второй оператор, содержащий вызов метода WriteLine()
, компилируется в любом случае, потому что он не входит в блок директивы #if
.
Как пояснялось выше, в директиве #if
допускается указывать идентификаторное выражение. В качестве примера рассмотрим следующую программу.
// Использовать идентификаторное выражение.
#define EXPERIMENTAL
#define TRIAL
using System;
class Test {
static void Main() {
#if EXPERIMENTAL
Console.WriteLine(«Компилируется для экспериментальной версии.»);
#endif
#if EXPERIMENTAL && TRIAL
Console.Error.WriteLine("Проверка пробной экспериментальной версии. ") ;
#endif
Console.WriteLine(«Присутствует во всех версиях.»);
}
}
Эта программа дает следующий результат.
Компилируется для экспериментальной версии.
Проверка пробной экспериментальной версии.
Присутствует во всех версиях.
В данном примере определены два идентификатора: EXPERIMENTAL и TRIAL. Второй оператор, содержащий вызов метода WriteLine()
, компилируется лишь в том случае, если определены оба идентификатора.
Для компилирования кода в том случае, если идентификатор не определен, можно воспользоваться оператором !, как в приведенном ниже примере.
#if !EXPERIMENTAL
Console.WriteLine(«Этот код не экспериментальный!»);
#endif
Вызов метода будет скомпилирован только в том случае, если идентификатор EXPERIMENTAL не определен.
Директивы #else и #elif
Директива #else
действует аналогично условному оператору else
языка С#, определяя альтернативный ход выполнения программы, если этого не может сделать директива #if
. С учетом директивы #else
предыдущий пример программы может быть расширен следующим образом.
// Продемонстрировать применение директивы #else.
#define EXPERIMENTAL
using System;
class Test {
static void Main() {
#if EXPERIMENTAL
Console.WriteLine(«Компилируется для экспериментальной версии.»);
#else
Console.WriteLine(«Компилируется для окончательной версии.»);
#endif
#if EXPERIMENTAL && TRIAL
Console.Error.WriteLine(«Проверка пробной экспериментальной версии.»);
#else
Console.Error.WriteLine(«Это не пробная экспериментальная версия.»);
#endif
Console.WriteLine(«Присутствует во всех версиях.»);
}
}
Вот к какому результату приводит выполнение этой программы.
Компилируется для экспериментальной версии.
Это не пробная экспериментальная версия.
Присутствует во всех версиях.
В данном примере идентификатор TRIAL не определен, и поэтому часть #else
второй условной последовательности кода не компилируется.
Обратите внимание на то, что директива #else
обозначает конец блока директивы #if и в то же время – начало блока самой директивы #else
. Это необходимо потому, что с любой директивой #if
может быть связана только одна директива #endif
. Более того, с любой директивой #if
может быть связана только одна директива #else
.
Обозначение #elif
означает «иначе если», а сама директива #elif
определяет последовательность условных операций if-else-if
для многовариантной компиляции. После директивы #elif
указывается идентификаторное выражение. Если это выражение истинно, то компилируется следующий далее кодовый блок, а остальные выражения директивы #elif
не проверяются. В противном случае проверяется следующий по порядку блок. Если же ни одну из директив #elif
не удается выполнить, то при наличии директивы #else
выполняется последовательность кода, связанная с этой директивой, а иначе не компилируется ни один из кодовых блоков директивы #if
. Ниже приведена общая форма директивы #elif
.
#if идентификаторное_выражение
последовательность операторов
#elif идентификаторное_выражение
последовательность операторов
#elif идентификаторное_выражение
последовательность операторов // . . .
#endif
В приведенном ниже примере демонстрируется применение директивы #elif
.
// Продемонстрировать применение директивы #elif.
#define RELEASE
using System;
class Test {
static void Main() {
#if EXPERIMENTAL
Console.WriteLine(«Компилируется для экспериментальной версии.»);
#elif RELEASE
Console.WriteLine(«Компилируется для окончательной версии.»);
#else
Console.WriteLine(«Компилируется для внутреннего тестирования.»);
#endif
#if TRIAL && !RELEASE
Console.WriteLine("Пробная версия. ") ;
#endif
Console.WriteLine(«Присутствует во всех версиях.»);
}
}
Этот код выдает следующий результат.
Компилируется для окончательной версии.
Присутствует во всех версиях.
Директива #undef
С помощью директивы #undef
удаляется определенный ранее идентификатор. Это, по существу, означает, что он становится «неопределенным». Ниже приведена общая форма директивы #undef
.
#undef идентификатор
Рассмотрим следующий пример кода.
#define SMALL
#if SMALL
// . . .
#undef SMALL
// теперь идентификатор SMALL не определен.
После директивы #undef
идентификатор SMALL уже оказывается неопределенным.
Директива #undef
применяется главным образом для локализации идентификаторов только в тех фрагментах кода, в которых они действительно требуются.
Директива #error
Директива #error
вынуждает компилятор прервать компиляцию. Она служит в основном для отладки. Ниже приведена общая форма директивы #error
.
#еrror сообщение_об_ошибке
Когда в коде встречается директива terror, выводится сообщение об ошибке. Например, когда компилятору встречается строка кода
#еrror Это тестовая ошибка!
компиляция прерывается и выводится сообщение "Это тестовая ошибка ! "
.
Директива #warning
Директива #warning
действует аналогично директиве #error
, за исключением того, что она выводит предупреждение, а не ошибку. Следовательно, компиляция не прерывается. Ниже приведена общая форма директивы #warning
.
#warning предупреждающее_сообщение
Директива #line
Директива #line
задает номер строки и имя файла, содержащего эту директиву. Номер строки и имя файла используются при выводе ошибок или предупреждений во время компиляции. Ниже приведена общая форма директивы #line
.
#line номер ”имя_файла"
Имеются еще два варианта директивы #line
. В первом из них она указывается с ключевым словом default
, обозначающим возврат нумерации строк в исходное состояние, как в приведенном ниже примере.
#line default
А во втором варианте директива #line указывается с ключевым словом hidden
. При пошаговой отладке программы строки кода, находящиеся между директивой
#line hidden
и следующей директивой #line
без ключевого слова hidden
, пропускаются отладчиком.
Директивы #region и #endregion
С помощью директив #region
и #endregion
определяется область, которая разворачивается или сворачивается при структурировании исходного кода в интегрированной среде разработки Visual Studio. Ниже приведена общая форма этих директив:
#region текст
// последовательность кода
#endregion текст
где текст обозначает необязательную символьную строку.
Директива #pragma
С помощью директивы #pragma
инструкции задаются компилятору в виде опций. Ниже приведена общая форма этой директивы:
#pragma опция
где опция обозначает инструкцию, передаваемую компилятору.
В текущей версии C# предусмотрены две опции для директивы #pragma
. Первая из них, warning
, служит для разрешения или запрета отдельных предупреждений со стороны компилятора. Она принимает две формы:
#pragma warning disable предупреждения
#pragma warning restore предупреждения
где предупреждения обозначает разделяемый запятыми список номеров предупреждений. Для отмены предупреждения используется опция disable
, а для его разрешения – опция restore
.
Например, в приведенной ниже директиве #pragma
запрещается выдача предупреждения №168, уведомляющего о том, что переменная объявлена, но не используется.
#pragma warning disable 168
Второй для директивы #pragma
является опция checksum
. Она служит для формирования контрольной суммы в проектах ASP.NET. Ниже приведена ее общая форма:
#pragma checksum «имя_файла» «{GUID}» «контрольная_сумма»
где имя_файла обозначает конкретное имя файла; GUID – глобально уникальный идентификатор, с которым связано имя_файла; контрольная_сумма – шестнадцатеричное число, представляющее контрольную сумму. У этой контрольной суммы должно быть четное число цифр.
Сборка является неотъемлемой частью программирования на С#. Она представляет собой один или несколько файлов, содержащих все необходимые сведения о развертывании программы и ее версии. Сборки составляют основу среды .NET. Они предоставляют механизмы для надежного взаимодействия компонентов, межъязыковой возможности взаимодействия и управления версиями. Кроме того, сборки определяют область действия программного кода.
Сборка состоит из четырех разделов. Первый раздел представляет собой декларацию сборки. Декларация содержит сведения о самой сборке. К этой информации относится, в частности, имя сборки, номер ее версии, сведения о соответствии типов и параметры культурной среды (язык и региональные стандарты). Второй раздел сборки содержит метаданные типов, т.е. сведения о типах данных, используемых в программе. Среди прочих преимуществ метаданные типов способствуют межъязыковой возможности взаимодействия. Третий раздел сборки содержит программный код в формате MSIL (Microsoft Intermediate Language – промежуточный язык корпорации Microsoft). И четвертый раздел сборки содержит ресурсы, используемые программой.
Правда, при программировании на C# сборки получаются автоматически, требуя от программирующего лишь минимальных усилий. Дело в том, что исполняемый файл, создаваемый во время компиляции программы на С#, на самом деле представляет собой сборку, содержащую исполняемый код этой программы, а также другие виды информации. Таким образом, когда компилируется программа на С#, сборка получается автоматически.
У сборок имеется много других особенностей, и с ними связано немало актуальных вопросов программирования, но, к сожалению, их обсуждение выходит за рамки этой книги. Ведь сборки являются неотъемлемой частью процесса разработки программного обеспечения в среде .NET, но формально они не относятся к средствам языка С#. Тем не менее в C# имеется одно средство, непосредственно связанное со сборкой. Это модификатор доступа internal
, рассматриваемый в следующем разделе.