Книга: C# 4.0: полное руководство

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

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

О пространстве имен уже вкратце упоминалось в главе 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 б 5 4 3 2 1 0 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 можно сделать видимыми вновь создаваемые пространства имен.

Существуют две формы директивы 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 cd1 = new CountDown(10);
    int i;
    do {
      i = cd1.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 cd1 = new CountDown(10);

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

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

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

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

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

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

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

// Продемонстрировать применение псевдонима,
using System;
// Cоздать псевдоним для составного имени 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 cd1 = new MyCounter(10);
    int i;
    do {
      i = cd1.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 cd1 = 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 9 8 7 6 5 4 3 2 1 0
0 1 2 3 4 5 6 7 8

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

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() {
    NS1.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. Если попытаться сделать видимыми оба пространства имен с помощью директив 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.

Оглавление книги


Генерация: 0.046. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз