355 500 произведений, 25 200 авторов.

Электронная библиотека книг » Герберт Шилдт » C# 4.0: полное руководство » Текст книги (страница 40)
C# 4.0: полное руководство
  • Текст добавлен: 6 апреля 2017, 04:00

Текст книги "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 – глобально уникальный идентификатор, с которым связано имя_файла; контрольная_сумма – шестнадцатеричное число, представляющее контрольную сумму. У этой контрольной суммы должно быть четное число цифр.


Сборки и модификатор доступа internal

Сборка является неотъемлемой частью программирования на С#. Она представляет собой один или несколько файлов, содержащих все необходимые сведения о развертывании программы и ее версии. Сборки составляют основу среды .NET. Они предоставляют механизмы для надежного взаимодействия компонентов, межъязыковой возможности взаимодействия и управления версиями. Кроме того, сборки определяют область действия программного кода.

Сборка состоит из четырех разделов. Первый раздел представляет собой декларацию сборки. Декларация содержит сведения о самой сборке. К этой информации относится, в частности, имя сборки, номер ее версии, сведения о соответствии типов и параметры культурной среды (язык и региональные стандарты). Второй раздел сборки содержит метаданные типов, т.е. сведения о типах данных, используемых в программе. Среди прочих преимуществ метаданные типов способствуют межъязыковой возможности взаимодействия. Третий раздел сборки содержит программный код в формате MSIL (Microsoft Intermediate Language – промежуточный язык корпорации Microsoft). И четвертый раздел сборки содержит ресурсы, используемые программой.

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

У сборок имеется много других особенностей, и с ними связано немало актуальных вопросов программирования, но, к сожалению, их обсуждение выходит за рамки этой книги. Ведь сборки являются неотъемлемой частью процесса разработки программного обеспечения в среде .NET, но формально они не относятся к средствам языка С#. Тем не менее в C# имеется одно средство, непосредственно связанное со сборкой. Это модификатор доступа internal, рассматриваемый в следующем разделе.


    Ваша оценка произведения:

Популярные книги за неделю