Иногда бывает так, что все решение представляет собой не что иное, как библиотек классов, а не выполняемую программу. (Обычно при разработке такой библиотеки соз дается также сопутствующий . ЕХЕ-проект, именуемый драйвером, который предназна чен для тестирования библиотеки в процессе разработки. Однако при выпуске готовой
библиотеки для других программистов вы поставляете им только . DLL, но не |
. ЕХЕ |
а также, надеюсь, документацию к этим классам!) |
|
Как создавать собственные библиотеки классов, будет рассказано немного |
позже |
в этой главе. |
|
Пространства имен существуют для того, чтобы можно было поместить связанны классы в "одну корзину", и для снижения коллизий между именами, используемые в разных местах. Например, вы можете собрать все классы, связанные с математически ми вычислениям, в одно пространство имен MathRoutines.
Можно (но вряд ли будет сделано на практике) разделить на несколько пространств имен один исходный файл. Гораздо более распространена ситуация, когда несколько файлов группируются в одно пространство имен. Например, файл Point.cs может со= держать класс Point, а файл ThreeDSpace.cs— класс ThreeDSpace, описываю щий свойства Евклидова пространства. Вы можете объединить Point.cs, ThreeD Space . cs и другие исходные файлы С# в пространство имен MathRoutines (и, веро ятно, в библиотечную сборку MathRoutines). Каждый файл будет помещать свой код в одно и то же пространство имен. (В действительности пространство имен составляют классы в этих исходных файлах, а не файлы сами по себе.)
Пространства имен служат для следующих целей.
Пространства имен помещают груши к грушам, а не к яблокам. Как при кладной программист, вы можете не без оснований предполагать, что все классы, составляющие пространство имен MathRoutines, имеют отношение к математи ческим вычислениям. Так что поиск некоторой математической функции следует начать с просмотра классов, составляющих пространство имен MathRoutines.
Пространства имен позволяют избежать конфликта имен. Например, библио тека для работы с файлами может содержать класс Convert, который преобразу ет представление файла одного типа к другому. В то же время библиотека перево да может содержать класс с точно таким же именем. Назначая этим двум множе ствам классов пространства имен FilelO и TranslationLibrary, вы устраняете проблему: класс FilelO. Convert, очевидно, отличается от класса
TranslationLibrary.Convert.
Объявление пространств имен
Пространства имен объявляются с использованием ключевого слова namespace, за которым следует имя и блок в фигурных скобках. Классы в этом блоке являются частью пространства имён,
namespace MyStuff
{
422 |
Часть VII. Дополнительные главы |
class MyClass {} class UrClass {}
}
В этом примере классы MyClass и UrClass являются частью пространства имен MyStuff.
Кроме классов, пространства имен могут содержать другие типы, такие как структуры и интерфейсы. Одно пространство имен может также содержать вложенные пространства имен с любой глубиной вложенности. У вас может быть пространство имен Namespace2, вло женное в Namespacel, как показано в следующем фрагменте исходного текста: namespace Namespacel
{
//Классы в Namespacel namespace Namespace2
{
//Классы в Namespace2 public class Class2
public void AMethod() { }
}
}
}
Для вызова метода из Class2 в Namespace2 откуда-то извне пространства имен Namespacel применяется следующая запись: Namespacel.Namespace2.Class2.AMethod();
Пространства имен неявно открыты, так что для них нельзя использовать специфика торы доступа, даже спецификатор public.
Удобно добавлять к пространствам имен в ваших программах название вашей фирмы: MyCompany .MathRoutines. (Конечно, если вы работаете на фирме. Вы можете также использовать свое собственное имя. Я бы мог применять для своих программ что-то наподобие CMSCo.MathRoutines или Sphar.MathRoutines.) Добавление названия фирмы предупреждает коллизии имен в вашем коде при использовании двух библиотек сторонних производителей, у которых ока зывается одно и то же базовое имя пространства имен, например, MathRoutines.
Такие "имена с точками" выглядят как вложенные пространства имен, но на самом деле это одно пространство имен, так что System. Data — это полное имя Пространст ва имен, а не имя пространства имен Data, вложенного в пространство имен System. Такое соглашение позволяет проще создавать несколько связанных пространств имен, таких как System. 10, System. Data и System. Text.
Visual Studio Application Wizard помещает каждый формируемый им класс в пространство имен, имеющее такое же имя, как и создаваемый им каталог. Взгляните на любую программу в этой книге, созданную Application Wizard. Например, программа AlignOutput размещается в папке AlignOutput. Имя исходного файла— Program.cs, соответствующее имени класса по умолчанию. Имя пространства имен в Program.cs то же, что и имя папки: AlignOutput. (Можно изменить любое из этих имен, только делайте это ос торожно и аккуратно. Изменение имени общего пространства имен проекта выполняется в окне Properties проекта.)
Глава 19. Работа с файлами и библиотеками |
423 |
Если вы не помещаете ваши классы в пространство имен, С# сам поместит их в глобаль ное пространство имен. Однако лучше использовать собственные пространства имен.
Важность пространств имен
Самое важное в пространствах имен с практической точки зрения то, что от расширяют описание управления доступом, о котором говорилось в главе 11, "Классы" (где были введены такие ключевые слова, как public, private protected, internal и protected internal). Пространства имен рас ширяют управление доступом с помощью дальнейшего ограничения на доступ к членам класса.
Реально пространства имен влияют не на доступность, а на видимость. По умолчанию. классы и методы в пространстве имен NamespaceA невидимы классам в пространстве имен NamespaceB, независимо от их спецификаторов доступа. Но можно сделать мас сы и методы из пространства имен NamespaceB видимыми для пространства имен NamespaceA. Обращаться вы можете только к тому, что видимо для вас.
Видимы ли вам необходимые классы и методы?
Для того чтобы определить, может ли класс Classl в пространстве имен NamespaceA вызывать NamespaceB. Class2 . AMethod () , рассмотрим следующие два элемента.
1.Видим ли класс Class2 из пространства имен NamespaceB вызывающему классу Classl? Это вопрос видимости пространства имен, который будет вскоре рассмотрен.
2.Если ответ на первый вопрос — "да", то "достаточно ли открыты" Class2 и его метод AMethod () классу Classl для доступа? "Достаточная открытость" опре деляется как наличие спецификаторов доступа нужной степени строгости с точки зрения вызывающего класса Classl. Это вопрос управления доступом, рассмат ривающийся в главе 11, "Классы".
Если Class2 находится в сборке, отличной от сборки Classl, он должен быть открыт (publ ic) для Classl для доступа к его членам. Если же это одна и та же сборка, Class2 должен быть объявлен как минимум как internal. Классы мо гут быть только public, protected, internal или private.
Аналогично, метод класса Class2 должен иметь как минимум определенный уровень доступа в каждой из этих ситуаций. Методы добавляют protected internal в список спецификаторов доступа класса. Детальнее об этом расска зывается в главе 11, "Классы".
Вы должны получить ответ "да" на оба поставленных вопроса, чтобы класс Classl мог вызывать методы Class2.
Остальной приведенный здесь материал посвящен вопросам использования и види мости пространств имен.
Как сделать видимыми классы и методы в другом пространстве имен
С# предоставляет два пути сделать элементы в пространстве имен NamespaceB ви димыми в пространстве имен NamespaceA.
424 |
Насть VII. Дополнительные главы |
Применяя полностью квалифицированные имена из пространства имен Name spaceB при использовании их в пространстве имен NamespaceA. Это приводит к коду наподобие приведенного, начинающемуся с имени пространства имен, к которому добавляется имя класса и имя метода:
System.Console.WriteLine("my string");
Устраняя необходимость в полностью квалифицированных именах в пространстве имен NamespaceA посредством директивы using для пространства имен
NamespaceB:
using NamespaceB;
Программы в этой книге используют последний способ — директиву using. Полно стью квалифицированные имена и директивы using будут рассмотрены в двух следую щих разделах.
Доступ к классам с использованием полностью квалифицированных имен
Пространство имен класса является составной частью его расширенного имени, что приводит к первому способу обеспечения видимости класса из одного пространства имен в другом. Рассмотрим следующий пример, в котором не имеется ни одной директи вы using для упрощения обращения к классам в других пространствах имен: namespace MathRoutines
{
class Sort
{
public void SomeFunction(){}
namespace Paint
{
public class PaintColor
{
public |
PaintColor(int nRed, int nGreen, int nBlue) {} |
public |
void Paint() {} |
public |
staticvoid StaticPaint () {} |
}
namespace MathRoutines
{
public class Test
{
static public void Main(string[] args)
//Создание объекта типа Sort из того же пространства
//имен, в котором мы находимся, и вызов некоторой
//функции
Sort obj = new Sort(); obj.SomeFunction();
//Создание объекта в другом пространстве имен —
//обратите внимание, что пространство имен должно
//быть явно включено в каждую ссылку на класс
Глава 19. Работа с файлами и библиотеками |
425 |
Paint.PaintColor black = new Paint.PaintColor(0, 0, 0); black.Paint();
Paint.PaintColor.StaticPaint();
}
}
В этом случае классы Sort и Test содержатся внутри одного и того же пространст ва имен MathRoutines, хотя и объявляются в разных местах файла. Это пространство имен разбито на две части (в данном случае в одном и том же файле).
В обычной ситуации Sort и Test оказались бы в различных исходных файлах С#, которые вы бы собрали в одну программу.
Функция Test. Main () может обращаться к классу Sort без указания его про странства имен, так как оба эти класса находятся в одном и том же пространстве имен, Однако Main () должна указывать пространство имен Paint при обращении к
PaintColor, как это сделано в вызове Paint, PaintColor. StaticPaint (). Здесь использовано полностью квалифицированное имя.
Обратите внимание, что вам не требуется принимать специальных мер при обраще нии к black. Paint ( ) , поскольку класс и пространство имен объекта black известны
Директива using
Обращение к классу с использованием полностью квалифицированного имени быст ро становится раздражающим. С# позволяет избежать излишнего раздражения с помо щью ключевого слова using. Директива using добавляет указанное пространство имен в список пространств имен по умолчанию, в которых С# выполняет поиск при разреше нии имени класса. Следующий пример компилируется без каких-либо замечаний: namespace Paint
{
public class PaintColor
{
public PaintColor(int nRed, int nGreen, int nBlue) {} public void Paint() {}
public static void StaticPaint() {}
namespace MathRoutines
{
//Добавляем Paint к пространствам имен, в которых
//выполняется автоматический поиск
using Paint; public class Test
{
static public void Main(string[] args)
{
//Создаем объект в другом пространстве имен —
//название пространства имен не требуется включать в
//имя, поскольку само пространство имен было включено
//полностью с использованием директивы "using" PaintColor black = new PaintColor(0, 0, 0 ) ;
426 |
Часть VII. Дополнительные главы |
black.Paint(); PaintColor.StaticPaint();
Директива using говорит компилятору: "Если ты не в состоянии найти определен ный класс в текущем пространстве имен, посмотри еще и в этом пространстве имен, мо жет, ты найдешь его там". Можно указать любое количество пространств имен, но все они должны быть указаны в самом начале программы (либо внутри, либо снаружи блока пространства имен), как описано ранее в разделе "Объявление пространств имен".
Все программы включают директиву using System,-. Эта команда дает программе автоматический доступ к функциям, включенным в системную библиотеку, таким как WriteLine ().
Использование полностью квалифицированных имен
Приведенная далее демонстрационная программа NamespaceUse иллюст рирует влияние пространств имен на видимость и использование директивы using и полностью квалифицированных имен, чтобы обеспечить види мость элемента.
//NamespaceUse - демонстрирует доступ к объектам с одним и
//тем же именем в разных пространствах имен
using System; // Все пространства имен нуждаются в этой
//директиве для доступа к классам типа String
//и Console
namespace MainCode
{
using LibraryCodel; // Эта директива упрощает MainCode public class Classl
{
public void AMethod()
{
Console.WriteLine("MainCode.Classl.AMethod() ") ;
}
// Функция Main():
static void Main(string[] args)
//Создание экземпляра класса, содержащего функцию
//Main, в данном пространстве имен
Classl cl = new C l a s s l О ; |
// MainCode.Classl |
cl.AMethod(); |
// |
Никогда |
не вызывайте |
|
// |
Main() |
самостоятельно! |
//Создание экземпляра LibraryCodel.Classl
//Приведенный далее код создает объект
// |
MainCode.Classl, |
а не тот, |
что вы хотели, так как |
// |
нет ни директивы |
using, ни |
полностью |
//квалифицированного имени Classl с2 = new Classl(),• с2.AMethod();
//Однако полностью квалифицированное имя создает
//объект требуемого класса. Имя следует
Глава 19. Работа с файлами и библиотеками |
427 |
//квалифицировать даже при использовании директивы
//using, поскольку оба пространства имен содержат
//класс Classl
LibraryCodel.Classl сЗ = new LibraryCodel.Classl(); c3 .AMethod () ,-
//В то же время создание LibraryCodel.Class2 не
//требует полностью квалифицированного имени,
//поскольку имеется директива using при отсутствии
//коллизии имен; С# может без труда найти Class2 Class2 с4 = new Class2();
с4.AMethod();
//Создание экземпляра LibraryCode2.Classl требует
//полностью квалифицированного имени, как из-за
//отсутствия директивы using для LibraryCode2, так и
//потому, что оба пространства имен имеют Classl
//Примечание: этот способ работает даже несмотря на
//то, что LibraryCode2.Classl объявлен как internal,
// |
а не public, |
поскольку оба класса |
находятся |
в одной |
// |
компилируемой |
сборке |
|
|
|
LibraryCode2.Classl с5 = new LibraryCode2.Classl(); |
с5.AMethod(); |
|
|
|
|
// |
Ожидаем подтверждения пользователя |
|
Console.WriteLine("Нажмите <Enter> для " + |
|
|
|
"завершения программы..."); |
|
Console.Read(); |
|
|
|
|
} |
|
|
|
|
|
namespace |
LibraryCodel |
|
|
|
|
{ |
|
|
|
|
|
public |
class Classl |
// |
Имя дублирует |
Classl в |
другом |
{ |
|
// |
пространстве |
имен |
|
public void AMethod() // |
Имя дублировано в другом |
{ |
|
// |
пространстве |
имен |
|
Console.WriteLine("LibraryCodel.Classl.AMethod()");
} } |
|
|
public class Class2 |
// |
Имя уникально, его нет в другом |
{ |
// |
пространстве имен |
public void AMethod()
{
Console.WriteLine("LibraryCodel.Class2.AMethod()");
namespace |
LibraryCode2 |
|
|
{ |
|
|
|
class Classl |
// |
Нет ключевых слов, описывающих |
{ |
|
// доступ: по умолчанию доступ — |
public |
void AMethod() |
// |
internal |
{
Console.WriteLine("LibraryCode2.Classl.AMethod()");
}
428 |
Часть VII. Дополнительные главы |
Данная демонстрационная программа включает три пространства имен: MainCode, в которое входит один класс Classl, содержащий функцию Main() и один дополни тельный метод AMethod ( ) . Пространство имен LibraryCodel содержит два класса: Classl дублирует имя Classl из пространства имен MainCode, класс Class2 уни кален. Пространство имен LibraryCode2 имеет один класс, также названный Classl, имя которого создавало бы коллизию с именем Classl в другом пространстве имен, ес ли бы эти имена не были разделены и размещены каждое в своем пространстве имен. Каждый их этих классов имеет метод AMethod ().
Функция Main () из MainCode . Classl пытается создать и использовать MainCode. Classl (класс-владелец MainO), LibraryCodel. Classl, Library Codel .Class2 и LibraryCode2 .Classl. После создания объектов функция вызы вает метод AMethod () каждого из них. Каждый метод идентифицирует свое местопо ложение. Вывод демонстрационной программы на экран выглядит следующим образом:
MainCode.Classl.AMethod()
MainCode.Classl.AMethod()
LibraryCodel.Classl.AMethod()
LibraryCodel.Class2.AMethod()
LibraryCode2.Classl.AMethod()
Нажмите <Enter> для завершения программы...
Без разделения на различные пространства имен компилятор не может позволить дублирования имен классов в MainO . При применении пространств имен исходный текст компилируется, но вы должны использовать либо директиву using, либо полно стью квалифицированные имена для обращения к объектам в разных пространствах имен. Пространство имен MainCode содержит директиву using для пространства имен
LibraryCodel, так что функция Main O из MainCode. Classl может обратиться к LibraryCodel. Class2 без полностью квалифицированного имени благодаря нали чию упомянутой директивы.
Попытка создать LibraryCodel. Classl без использования полностью квалифи цированного имени приводит ко второй строке в выводе на экран в рассмотренном при мере. Как видите, оказался создан объект класса MainCode. Classl, а не класса из пространства имен LibraryCodel. Без применения полностью квалифицированного имени компилятор находит Classl в пространстве имен MainCode. Остальные вызовы работают так, как от них и ожидалось.
Однако использование директивы using для пространства имен LibraryCodel сослужит функции Main () плохую службу при ее желании обратиться к Library Codel . Classl, так как имя этого класса дублировано в двух пространствах имен. Не смотря на наличие директивы using, единственным решением является применение полностью квалифицированного имени для доступа к LibraryCodel. Classl.
Последний вызов в Main () для создания и использования объекта Library2 . Classl был бы неверен, если бы пространство имен Library2 находилось в сборке, отличной от сборки MainCode. Причина заключается в том, что спе цификаторы доступа у класса Library2 .Classl не указаны, так что вместо того чтобы быть public, он является internal. Это поведение по умолча нию для классов без спецификаторов доступа (для методов по умолчанию ис пользуется private). Повторяясь еще р а з — всегда явно указывайте уровень доступа к каждому классу и методу.
Глава 19. Работа с файлами и библиотеками |
429 |
После того как вы используете полностью квалифицированные имена или директивы using, ваш класс сможет обратиться к чему угодно в другом пространстве имен, если конечно, это не запрещено ему правилами доступа — как, например, к закрытым членам.
В главе 21, "Использование интерфейса Visual Studio", вы познакомитесь с тем, как создавать проект с несколькими . СS-файлами. Даже при разбиении проекта на несколь ко файлов (обычно по одному классу в файле) один проект равен одной скомпилированной сборке. Библиотеки классов располагаются в файлах с расширением . DLL и не яв ляются выполнимыми программами сами по себе. Они служат для поддержки других программ, предоставляя им полезные классы и методы.
Простейшее определение проекта библиотеки классов — это классы, не содер жащие функции Main ( ) , что отличает библиотеку классов от выполнимой программы.
Visual Studio 2005 позволяет построить либо . ЕХЕ, либо . DLL (либо решение, со держащее их оба). В следующем разделе поясняются основы создания ваших собствен ных библиотек классов. Не беспокойтесь: рабочая лошадка С# вытянет за вас и этот груз.
Менее дорогие версии Visual Studio 2005, такие как Visual С# Express, не позволяют собирать библиотеки классов. Однако вам будет показан один трюк, позволяющий обой ти данное ограничение.
Создание проекта библиотеки классов
Для формирования нового проекта библиотеки в полной версии Visual Studio 2005 выберите тип проекта Class Library в диалоговом окне New Project. Затем пропустите следующий раздел главы и переходите к созданию классов, которые будут составлять библиотеку.
Если вы используете Visual С# Express, процесс создания нового проекта биб лиотеки будет немного более сложным. Несмотря на снижение функциональ ности, которому Microsoft подвергла Visual С# Express, если выполнить опи санные далее действия, все равно можно сформировать библиотеку классов.
1.При создании нового проекта создавайте Console Application (или Win dows Application).
Если вы не уверены в том, как это делается, вернитесь к главам 1, "Создание вашей первой Windows-программы на С#", и 2, "Создание консольного приложения на C#", Обычно библиотека классов формируется под видом консольного приложения.
2.В Solution Explorer удалите файл Program.cs, закройте и сохраните ре шение.
Поскольку библиотека классов не может содержать функцию Main ( ) , вы просто удаляете файл с ней.
3.Запустите Блокнот Windows или другой обычный текстовый редактор и от кройте в нем файл ProjectName.csproj из вновь созданного вами проекта.
430 |
Часть VII. Дополнительные главы |
Он выглядит страшновато, но это всего лишь масса информации о программе, за писанная с использованием языка XML.
4.Воспользуйтесь командой меню Edit"=>Find для того, чтобы найти строку:
<OutputType>Exe</OutputType>
Если вы создали проект приложения Windows, а не консольного приложения, найдите строку
<OutputType>WinExe</OutputТуре>
Это примерно восьмая строка в файле.
5.Замените в найденной строке Ехе (или WinExe) на Library:
<OutputType>Library</OutputType >
Вот и все!
6.Сохраните файл и заново откройте проект в Visual С# Express.
7.Добавляйте в проект новые классы и работайте. Только убедитесь, что вы не раз местили где-то в проекте функцию Main ().
Когда вы соберете новый проект, то получите . DLL-файл, а не . ЕХЕ-файл.
Создание классов для библиотеки
После того как вы сформировали проект библиотеки классов, вы создаете классы, составляющие эту библиотеку. Приведенный далее пример ClassLibrary демонстрирует простую библиотеку классов, которую вы сможете увидеть в действии (в примере показан как исходный текст библио теки, так и описываемого далее драйвера).
//ClassLibrary - простая библиотека классов и ее
//программа-драйвер
//Файл ClassLibrary.cs
using System;
namespace ClassLibrary
{
public class MyLibrary
{
public void LibraryFunctionl()
{
Console.WriteLine("Это LibraryFunctionl()");
}
public int LibraryFunction2(int input)
{
Console.Write("Это LibraryFunction2(), " + "возвращает { о } , input);
return input; // Возвращает аргумент
}
}
//Драйвер — в отдельном проекте
//Файл Program.cs
using System;
Глава 19. Работа с файлами и библиотеками |
431 |