Главная страница

программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт


Скачать 3.32 Mb.
НазваниеРуководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Анкорпрограммирование
Дата25.01.2022
Размер3.32 Mb.
Формат файлаrtf
Имя файлаc-40-polnoe-rukovodstvo-2011.rtf
ТипРуководство
#341448
страница42 из 97
1   ...   38   39   40   41   42   43   44   45   ...   97
ГЛАВА 16 Пространства имен, препроцессор и сборки

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

Пространства имен

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

Пространство имен определяет область объявлений, в которой допускается хранить одно множество имен отдельно от другого. По существу, имена, объявленные в одном пространстве имен, не будут вступать в конфликт с аналогичными именами, объявленными в другой области. Так, в библиотеке классов для среды .NET Framework, которая одновременно является библиотекой классов С#, используется пространство имен System. Именно поэтому строка кода

using System;

обычно вводится в самом начале любой программы на С#. Как пояснялось в главе 14, классы ввода‑вывода определены в пространстве имен System. 10, подчиненном пространству имен System. Ему подчинены и многие другие пространства имен, относящиеся к разным частям библиотеки классов С#.

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

Объявление пространства имен

Пространство имен объявляется с помощью ключевого слова namespace. Ниже приведена общая форма объявления пространства имен:

namespace имя {

11 члены

}

где имя обозначает конкретное имя объявляемого пространства имен. При объявлении пространства имен определяется область его действия. Все, что объявляется непосредственно в этом пространстве, оказывается в пределах его области действия. В пространстве имен можно объявить классы, структуры, делегаты, перечисления, интерфейсы или другие пространства имен.

Ниже приведен пример объявления namespace для создания пространства имен Counter. В этом пространстве локализуется имя, используемое для реализации простого класса вычитающего счетчика CountDown.

// Объявить пространство имен для счетчиков.

namespace Counter {

// Простой вычитающий счетчик, class CountDown { int val;

public CountDown(int n) { val = n;

}

public void Reset(int n) { val = n;

}

public int Count()    {

if(val > 0) return val–; else return 0;

}

} // Это конец пространства имен Counter.

Обратите внимание на то, что класс CountDown объявляется в пределах области действия пространства имен Counter. Для того чтобы проработать этот пример на практике, поместите приведенный выше код в файл Counter, cs.

Ниже приведен пример программы, демонстрирующий применение пространства имен Counter.

// Продемонстрировать применение пространства имен Counter.

using System;

class NSDemo {

static void Main() {

// Обратите внимание на то, как класс CountDown // определяется с помощью пространства имен Counter.

Counter.CountDown cdl = new Counter.CountDown(10); int i;

do {

i = cdl.Count();

Console.Write(i + " ");

} while (i > 0);

Console.WriteLine ();

// Еще раз обратите внимание на то, как класс CountDown // определяется с помощью пространства имен Counter.

Counter.CountDown cd2 = new Counter.CountDown(20);

do {

i = cd2.Count();

Console.Write(i + " ");

} while (i > 0);

Console.WriteLine ();

cd2.Reset (4) ; do {

i = cd2.Count ();

Console.Write(i + " ");

} while (i > 0);

Console.WriteLine () ;

}

}

При выполнении этой программы получается следующий результат.

10 9 876543210

20 19 18 17 16 15 14 13 12 11 10 9 8 7 б 5 4 3 2 1 0 4 3 2 1 0

Для того чтобы скомпилировать эту программу, вы должны включить приведенный выше код в отдельный файл и указать его вместе с упоминавшимся выше файлом, содержащим код объявления пространства имен Counter. Если этот код

находится в файле NSDemo. cs, а код объявления пространства имен Counter – в файле Counter. cs, то для компиляции программы используется следующая командная строка.

csc NSDemo.cs counter.cs

Некоторые важные аспекты данной программы заслуживают более пристального внимания. Во‑первых, при создании объекта класса CountDown необходимо дополнительно определить его имя с помощью пространства имен Counter, как показано ниже. Ведь класс CountDown объявлен в пространстве имен Counter.

Counter.CountDown cdl = new Counter.CountDown(10);

Это правило можно обобщить: всякий раз, когда используется член пространства имен, его имя необходимо дополнительно определить с помощью этого пространства имен. В противном случае член пространства имен не будет обнаружен компилятором.

Во‑вторых, как только объект типа Counter будет создан, дополнительно определять его члены с помощью пространства имен уже не придется. Следовательно, метод cdl. Count () может быть вызван непосредственно без дополнительного указания пространства имен, как в приведенной ниже строке кода.

i = cdl.Count();

И в‑третьих, ради наглядности примера рассматриваемая здесь программа была разделена на два отдельных файла. В одном файле содержится код объявления пространства имен Counter, а в другом – код самой программы NSDemo. Но оба фрагмента кода можно было бы объединить в единый файл. Более того, в одном файле исходного кода может содержаться два или более пространства имен со своими собственными областями объявлений. Когда оканчивается действие внутреннего пространства имен, возобновляется действие внешнего пространства имен – в примере с Counter это глобальное пространство имен. Ради большей ясности в последующих примерах все пространства имен, требующиеся в программе, будут представлены в одном и том же файле. Следует, однако, иметь в виду, что их допускается распределять по отдельным файлам, что практикуется чаще в выходном коде.

Предотвращение конфликтов имен с помощью пространств имен

Главное преимущество пространств имен заключается в том, что объявленные в них имена не вступают в конфликт с именами, объявленными за их пределами. Например, в приведенной ниже программе определяются два пространства имен. Первым из них является представленное ранее пространство имен Counter, а вторым – Counter2. Оба пространства имен содержат классы с одинаковым именем CountDown, но поскольку это разные пространства, то оба класса CountDown не вступают в конфликт друг с другом. Кроме того, оба пространства имен определены в одном и том же файле. Как пояснялось выше, это вполне допустимо. Безусловно, каждое из этих пространств имен можно было бы выделить в отдельный файл, если бы в этом возникла потребность.

// Пространства имен предотвращают конфликты имен.

// Объявить пространство имен Counter, namespace Counter {

// Простой вычитающий счетчик, class CountDown { int val; ‑

public CountDown(int n) { val = n;

}

public void Reset(int n) { val = n;

}

public int Count ()    {

if(val > 0) return val–; else return 0;

}

}

}

// Объявить пространство имен Counter2. namespace Counter2 {

/* Этот класс CountDown относится к пространству имен Counter2 и поэтому не вступает в конфликт с аналогичным классом из пространства имен Counter.

*/

class CountDown {

public void Count()    {

Console.WriteLine("Это метод Count() из " +

"пространства имен Counter2.");

}

}

}

class NSDemo2 {

static void Main() {

// Это класс CountDown из пространства имен Counter. Counter.CountDown cdl = new Counter.CountDown(10);

// Это класс CountDown из пространства имен Counter2. Counter2.CountDown cd2 = new Counter2.CountDown(); int i;

do {

i = cdl.Count();

Console.Write(i + " ");

} while(i > 0);

Console.WriteLine();

Вот к какому результату приводит выполнение этой программы.

10 987654 3.210

Это метод Count () из пространства имен Counter2.

Как следует из приведенного выше результата, класс CountDown из пространства имен Counter существует отдельно от класса того же названия из пространства имен Counter2, и поэтому конфликт имен не возникает. Несмотря на всю простоту данного примера, он наглядно показывает, как удается избежать конфликта имен в собственном коде и коде, написанном другими разработчиками, поместив классы с одинаковыми именами в разные пространства имен.

Директива using

Если в программе присутствуют частые ссылки на члены конкретного пространства имен, то указывать это пространство всякий раз, когда требуется ссылка на него, не очень удобно. Преодолеть это затруднение помогает директива using. В подавляющем большинстве приводившихся ранее примеров программ с помощью этой директивы делалось видимым глобальное для C# пространство имен System, поэтому она отчасти вам уже знакома. Как и следовало ожидать, с помощью директивы using можно сделать видимыми вновь создаваемые пространства имен.

Существуют две формы директивы using. Ниже приведена первая из них:

using имя;

где имя обозначает имя того пространства имен, к которому требуется получить доступ. Все члены, определенные в указанном пространстве имен, становятся видимыми, и поэтому могут быть использованы без дополнительного определения их имен. Директиву using необходимо вводить в самом начале каждого файла исходного кода перед любыми другими объявлениями или же в начале тела пространства имен.

Приведенная ниже программа является вариантом предыдущего примера, переработанным с целью продемонстрировать применение директивы using, делающей видимым создаваемое пространство имён.

// Продемонстрировать применение директивы using, using System;

// Сделать видимым пространство имен Counter, using Counter;    ,

// Объявить пространство имен для счетчиков, namespace Counter {

// Простой вычитающий счетчик, class CountDown { int val;

public CountDown(int n) { val = n;

}

public void Reset(int n) { val = n;

}

public int Count ()    {

if(val > 0) return val–; else return 0;

}

}

}

class NSDemo3 {

static void Main() {

// Теперь класс CountDown может быть использован непосредственно. CountDown cdl = new CountDown(10); int i;

do {

i = cdl.Count ();

Console.Write (i + " ") ;

} while (i > 0);

Console.WriteLine ();

CountDown cd2 = new CountDown (20);

do {

i = cd2.Count ();

Console.Write (i + " ");

} while (i > 0);

Console.WriteLine ();

cd2.Reset(4) ; do {

i = cd2.Count ();

Console.Write(i + " ");

} while (i > 0);

Console.WriteLine ();

}

}

В эту версию программы внесены два существенных изменения. Первое из них состоит в применении директивы using в самом начале программы, как показано ниже.

using Counter;

Благодаря этому становится видимым пространство имен Counter. Второе изменение состоит в том, что класс CountDown больше не нужно дополнительно определять с помощью пространства имен Counter, как демонстрирует приведенная ниже строка кода из метода Main ().

CountDown cdl = new CountDown(10);

Теперь пространство имен Counter становится видимым, и поэтому класс CountDown может быть использован непосредственно.    ‑

Рассматриваемая здесь программа иллюстрирует еще одно важное обстоятельство: применение одного пространства имен не отменяет действие другого. Когда пространство имен делается видимым, это просто дает возможность использовать его содержимое без дополнительного определения имен. Следовательно, в данном примере оба пространства имен, System и Counter, становятся видимыми.

Вторая форма директивы using

Вторая форма директивы using позволяет определить еще одно имя (так называемый псевдоним) типа данных или пространства имен. Эта форма приведена ниже:

using псевдоним = имя;

где псевдоним становится еще одним именем типа (например, типа класса) или пространства имен, обозначаемого как имя. После того как псевдоним будет создан, он может быть использован вместо первоначального имени.

Ниже приведен вариант программы из предыдущего примера, измененный с целью показать создание и применение псевдонима MyCounter вместо составного имени Counter.CountDown.

// Продемонстрировать применение псевдонима, using System;

// Создать псевдоним для составного имени Counter.CountDown. using MyCounter = Counter.CountDown;

// Объявить пространство имен для счетчиков, namespace Counter {

// Простой вычитающий счетчик, class CountDown { int val;

public CountDown(int n) { val = n;

}

public void Reset(int n) { val = n;

} '

public int Count()    {

if(val > 0) return val – ; else return 0;

}

}

}

class NSDemo4 {

static void Main() {

// Здесь и далее псевдоним MyCounter используется // вместо составного имени Counter.CountDown.

MyCounter cdl = new MyCounter(10); int i;

do {

i = cdl... Count() ;

Console.Write(i + " ");

} while (i > 0);

Console.WriteLine () ;

MyCounter cd2 = new MyCounter (20);

do {

i = cd2.Count ();

Console.Write (i + " ");

} while (i > 0);

Console.WriteLine ();

cd2.Reset (4); do {

i = cd2.Count ();

Console.Write (i + " ");

} while (i > 0);

Console.WriteLine ();

}

}

Псевдоним MyCounter создается с помощью следующего оператора.

using MyCounter = Counter.CountDown;

После того как псевдоним будет определен в качестве другого имени класса Counter. CountDown, его можно использовать для объявления объектов без дополнительного определения имени данного класса. Например, в следующей строке кода из рассматриваемой здесь программы создается объект класса CountDown.

MyCounter cdl = new MyCounter (10);

Аддитивный характер пространств имен

П 04 одним именем можно объявить несколько пространств имен. Это дает возможность распределить пространство имен по нескольким файлам или даже разделить его в пределах одного и того же файла исходного кода. Например, в приведенной ниже программе два пространства имен определяются под одним и тем же именем Counter. Одно из них содержит класс CountDown, а другое – класс CountUp. Во время компиляции содержимое обоих пространств имен Counter складывается.

// Аддитивный характер пространств имен, using System;

// Сделать видимым пространство имен Counter, using Counter;

// Это одно пространство имен Counter.

namespace Counter {

// Простой вычитающий счетчик, class CountDown { int val;

public CountDown(int n) { val = n;

}

public void Reset(int n) { val = n;

}

public int Count()    {

if(val > 0) return val–; else return 0;

}

}

}

//А это другое пространство имен Counter, namespace Counter {

// Простой суммирующий счетчик, class CountUp { int val; int target;

public int Target { get{

return target;

}

}

public CountUp(int n) { target = n; val = 0;

}

public void Reset(int n) { target = n; val = 0;

}

public int Count()    {

if(val < target) return val++;

else return target;

}

}

}

class NSDemo5 {

static void Main() {

CountDown cd = new CountDown(10); CountUp cu = new CountUp(8);

int i; do {

i = cd.Count ();

Console^.Write (i + "    ;

} ‑while (i > 0) ;

Console.WriteLine ();

do {

i = cu.Count ();

Console.Write(i + " ");

} while(i < cu.Target);

}

}

Вот к какому результату приводит выполнение этой программы.

10 9876543210 012345678

Обратите также внимание на то, что директива

using Counter;

делает видимым все содержимое пространства имен Counter. Это дает возможность обращаться к классам CountDown и CountUp непосредственно, т.е. без дополнительного указания пространства имен. При этом разделение пространства имен Counter на две части не имеет никакого значения.

Вложенные пространства имен

Одно пространство имен может быть вложено в другое. В качестве примера рассмотрим следующую программу.

// Вложенные пространства имен.

using System;

namespace NS1 { class ClassA {

public ClassA()    {

Console.WriteLine("Конструирование класса ClassA");

}

}

namespace NS2 { // вложенное пространство имен class ClassB {

public ClassB ()    {

Console.WriteLine("Конструирование класса ClassB");

}

}

}

}

class NestedNSDemo { static void Main() {    .

NSl.ClassA a = new NS1.ClassA();

// NS2.ClassB b = new NS2.ClassB (); // Неверно!!! Пространство NS2 невидимо NS1.NS2.ClassB b = new NS1.NS2.ClassB(); // Верно!

}

}

Выполнение этой программы дает следующий результат.

Конструирование класса ClassA Конструирование класса ClassB

В этой программе пространство имен NS2 вложено в пространство имен NS1. Поэтому для обращения к классу ClassB необходимо дополнительно указать пространства имен NS1 и NS2. Указания одного лишь пространства имен NS2 для этого недостаточно. Как следует из приведенного выше примера, пространства имен дополнительно указываются через точку. Следовательно, для обращения к классу ClassB в методе Main () необходимо указать его полное имя – NSl.NS2.ClassB.

Пространства имен могут быть вложенными больше, чем на два уровня. В этом случае член вложенного пространства имен должен быть дополнительно определен с помощью всех охватывающих пространств имен.

Вложенные пространства имен можно указать в одном операторе namespace, разделив их точкой. Например, вложенные пространства имен

namespace OuterNS { namespace InnerNS {

// ...

}

}

могут быть указаны следующим образом.

namespace OuterNS.InnerNS {

П ...

}

Глобальное пространство имен

Если в программе не объявлено пространство имен, то по умолчанию используется глобальное пространство имен. Именно поэтому в примерах программ, представленных в предыдущих главах книги, не нужно было обращаться для этой цели к ключевому слову namespace. Глобальное пространство удобно для коротких программ, как в примерах из этой книги, но в большинстве случаев реальный код содержится в объявляемом пространстве имен. Главная причина инкапсуляции кода в объявляемом пространстве имен – предотвращение конфликтов имен. Пространства имен служат дополнительным средством, помогающим улучшить организацию программ и приспособить их к работе в сложной среде с современной сетевой структурой.

Применение описателя псевдонима пространства имен ::

Пространства имен помогают предотвратить конфликты имен, но не устранить их полностью. Такой конфликт может, в частности, произойти, когда одно и то же имя

объявляется в двух разных пространствах имен и затем предпринимается попытка сделать видимыми оба пространства. Допустим, что два пространства имен содержат класс MyClass. Если попытаться сделать видимыми оба пространства имен с помощью директивой sing, то имя 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 cdl = new CountDown(10); // Ошибка! !    !

// ...

}

}

Если попытаться скомпилировать эту программу, то будет получено сообщение об ошибке, уведомляющее о неоднозначности в следующей строке кода из метода Main ().

CountDown cdl = 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 cdl = new Ctr::CountDown(10);

// ...

}

}

В этом варианте программы для класса Counter сначала указывается псевдоним Ctr в следующей строке кода.

using Ctr = Counter;

А затем этот псевдоним используется в методе Main () для дополнительного описания класса CountDown, как показано ниже.

Ctr::CountDown cdl = 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 cdl = 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. Поэтому когда в этом коде встречается директива # i f , идентификаторное выражение вычисляется как истинное и затем компилируется первый оператор, содержащий вызов метода WriteLine () . Если же удалить определение идентификатора EXPERIMENTAL и перекомпилировать данный код, то первый оператор, содержащий вызов метода WriteLine () , не будет скомпилирован, поскольку идентификаторное выражение директивы # i f вычисляется как ложное. Но второй оператор, содержащий вызов метода WriteLine (), компилируется в любом случае, потому что он не входит в блок директивы #if.

Как пояснялось выше, в директиве # i f допускается указывать идентификаторное выражение. В качестве примера рассмотрим следующую программу.

// Использовать идентификаторное выражение.

#define EXPERIMENTAL #define TRIAL

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 вынуждает компилятор прервать компиляцию. Она служит в основном для отладки. Ниже приведена общая форма директивы #еггог.

#еггог сообщение_об_ошибке

Когда в коде встречается директива terror, выводится сообщение об ошибке. Например, когда компилятору встречается строка кода

#еггог Это тестовая ошибка!

компиляция прерывается и выводится сообщение "Это тестовая ошибка ! ".

Директива #warning

Директива #warning действует аналогично директиве terror, за исключением того, что она выводит предупреждение, а не ошибку. Следовательно, компиляция не прерывается. Ниже приведена общая форма директивы #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, рассматриваемый в следующем разделе.

Модификатор доступа internal

Помимо модификаторов доступа public, private и protected, использовавшихся в представленных ранее примерах программ, в C# предусмотрен также модификатор доступа internal. Этот модификатор определяет доступность члена во всех файлах сборки и его недоступность за пределами сборки. Проще говоря, о члене, обозначенном как internal, известно только в самой программе, но не за ее пределами. Модификатор доступа internal особенно полезен для создания программных компонентов.

Модификатор доступа internal можно применять к классам и их членам, а также к структурам и членам структур. Кроме того, модификатор internal разрешается использовать в объявлениях интерфейсов и перечислений.

Из модификаторов protected и internal можно составить спаренный модификатор доступа protected internal. Уровень доступа protected internal может быть задан только для членов класса. Член, объявленный как protected internal, доступен лишь в пределах собственной сборки или для производных типов.

Ниже приведен пример применения модификатора доступа internal.

// Использовать модификатор доступа internal.

using System;

class InternalTest { internal int x;

}

class InternalDemo { static void Main() {

InternalTest ob = new InternalTest();

ob.x = 10; // доступно, потому что находится в том же файле Console.WriteLine("Значение ob.x: " + ob.x);

}

}

В классе InternalTest поле х объявляется как internal. Это означает, что поле х доступно в самой программе, но, как показывает код класса InternalDemo, оно недоступно за пределами программы.

1   ...   38   39   40   41   42   43   44   45   ...   97


написать администратору сайта