ГЛАВА 16. Пространства имен, препроцессор и сборки
В этой главе речь пойдет о трех средствах С#, позволяю щих улучшить организованность и доступность про граммы. Этими средствами являются пространства имен, препроцессор и сборки. Пространства имен
О пространстве имен уже вкратце упоминалось в главе 2 в связи с тем, что это основополагающее понятие для С#. В действительности пространство имен в той или иной сте пени используется в каждой программе на С#. Потребность в подробном рассмотрении пространств имен не возникала до сих пор потому, что для каждой программы на C# авто матически предоставляется используемое по умолчанию глобальное пространство имен. Следовательно, в приме рах программ, представленных в предыдущих главах, ис пользовалось глобальное пространство имен. Но во многих реальных программах приходится создавать собственные пространства имен или же организовать взаимодействие с другими пространствами имен. Подобные пространства будут представлены далее во всех подробностях.
Пространство имен определяет область объявлений, в которой допускается хранить одно множество имен от дельно от другого. По существу, имена, объявленные в одном пространстве имен, не будут вступать в конфликт с аналогичными именами, объявленными в другой обла сти. Так, в библиотеке классов для среды .NET Framework, которая одновременно является библиотекой классов С#, используется пространство имен System. Именно поэтому строка кода using System;
обычно вводится в самом начале любой программы на С#. Как пояснялось в главе 14, классы ввода-вывода определены в пространстве имен System.IO, подчиненном про странству имен System. Ему подчинены и многие другие пространства имен, относя щиеся к разным частям библиотеки классов С#.
Пространства имен важны потому, что за последние годы в программировании "расплодились" в огромном количестве имена переменных, методов, свойств и клас сов, применяемых в библиотечных программах, стороннем и собственном коде. Поэто му без отдельных пространств все эти имена будут соперничать за место в глобальном пространстве имен, порождая конфликтные ситуации. Так, если в программе опреде лен класс Finder, то этот класс может вступить в конфликт с другим классом Finder, доступным в сторонней библиотеке, используемой в этой программе. К счастью, по добного конфликта можно избежать, используя отдельные пространства имен, огра ничивающие область видимости объявленных в них имен. Объявление пространства имен
Пространство имен объявляется с помощью ключевого слова namespace. Ниже приведена общая форма объявления пространства имен: namespace имя { // члены }
где имя обозначает конкретное имя объявляемого пространства имен. При объявлении пространства имен определяется область его действия. Все, что объявляется непосред ственно в этом пространстве, оказывается в пределах его области действия. В простран стве имен можно объявить классы, структуры, делегаты, перечисления, интерфейсы или другие пространства имен.
Ниже приведен пример объявления 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 cd1 = new Counter.CountDown(10); int i; do { i = cd1.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 8 7 6 5 4 3 2 1 0 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 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 cd1 = new Counter.CountDown(10);
Это правило можно обобщить: всякий раз, когда используется член пространства имен, его имя необходимо дополнительно определить с помощью этого пространства имен. В противном случае член пространства имен не будет обнаружен компилятором.
Во-вторых, как только объект типа Counter будет создан, дополнительно опреде лять его члены с помощью пространства имен уже не придется. Следовательно, метод cd1.Count() может быть вызван непосредственно без дополнительного указания про странства имен, как в приведенной ниже строке кода. i = cd1.Count();
И в-третьих, ради наглядности примера рассматриваемая здесь программа была разделена на два отдельных файла. В одном файле содержится код объявления про странства имен Counter, а в другом — код самой программы NSDemo. Но оба фраг мента кода можно было бы объединить в единый файл. Более того, в одном файле ис ходного кода может содержаться два или более пространства имен со своими собствен ными областями объявлений. Когда оканчивается действие внутреннего пространства имен, возобновляется действие внешнего пространства имен — в примере с Counter это глобальное пространство имен. Ради большей ясности в последующих примерах все пространства имен, требующиеся в программе, будут представлены в одном и том же файле. Следует, однако, иметь в виду, что их допускается распределять по отдель ным файлам, что практикуется чаще в выходном коде. Предотвращение конфликтов имен с помощью пространств имен
Главное преимущество пространств имен заключается в том, что объявленные в них имена не вступают в конфликт с именами, объявленными за их пределами. Например, в приведенной ниже программе определяются два пространства имен. Первым из них является представленное ранее пространство имен Counter, а вторым — Counter2. Оба пространства имен содержат классы с одинаковым именем CountDown, но по скольку это разные пространства, то оба класса CountDown не вступают в конфликт друг с другом. Кроме того, оба пространства имен определены в одном и том же фай ле. Как пояснялось выше, это вполне допустимо. Безусловно, каждое из этих про странств имен можно было бы выделить в отдельный файл, если бы в этом возникла потребность. // Пространства имен предотвращают конфликты имен. using System; // Объявить пространство имен 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 cd1 = new Counter.CountDown(10); // Это класс CountDown из пространства имен Counter2. Counter2.CountDown cd2 = new Counter2.CountDown(); int i; do { i = cd1.Count(); Console.Write(i + " "); } while(i > 0); Console.WriteLine(); cd2.Count(); } }
Вот к какому результату приводит выполнение этой программы. 10 9 8 7 6 5 4 3 2 1 0 Это метод Count() из пространства имен Counter2.
Как следует из приведенного выше результата, класс CountDown из пространства имен Counter существует отдельно от класса того же названия из пространства имен Counter2, и поэтому конфликт имен не возникает. Несмотря на всю простоту данного примера, он наглядно показывает, как удается избежать конфликта имен в собствен ном коде и коде, написанном другими разработчиками, поместив классы с одинаковы ми именами в разные пространства имен. Директива using
Если в программе присутствуют частые ссылки на члены конкретного пространства имен, то указывать это пространство всякий раз, когда требуется ссылка на него, не очень удобно. Преодолеть это затруднение помогает директива using. В подавляю щем большинстве приводившихся ранее примеров программ с помощью этой ди рективы делалось видимым глобальное для C# пространство имен System, поэтому она отчасти вам уже знакома. Как и следовало ожидать, с помощью директивы using можно сделать видимыми вновь создаваемые пространства имен.