Перейти к основному содержимому

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

Разработчику Архитектору

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

Директива using для импорта
using static — доступ к статическим членам
using как оператор (для IDisposable)

Пространства имён (Namespaces) – способ организации кода. Это некоторое множество, под которым подразумевается модель, абстрактное хранилище или окружение, созданное для логической группировки уникальных идентификаторов (имён). Они помогают избежать конфликтов имён и делают структуру проекта более понятной. Чтобы определить пространство имён, нужно указать ключевое слово namespace и закрыть кусок кода в фигурные скобки {…}.

Синтаксис:

namespace MyApplication.Utilities {
class Program {
static void Main() {
Console.WriteLine("Hello from namespace!");
}
}
}

Это нужно для того, чтобы упорядочить код по логическим группам, использовать одинаковые имена классов в разных частях программы и упростить работу с большими проектами.

Представим, что у нас есть два класса с одинаковым именем, например Logger. Один - для работы с файлами, другой - для отправки логов в облако. Без пространств имён компилятор не сможет понять, какой Logger ты имеешь в виду. Пространства имён решают эту проблему, «упаковывая» типы в логические контейнеры.

Мы можем создать namespace FileLogger и namespace CloudLogger, и тогда у каждого Logger будет свой индивидуальный путь:

FileLogger.Logger;
CloudLogger.Logger.

Как мы уже ранее обозначили, пространство имён объявляется с помощью ключевого слова namespace, за которым следует имя и блок кода в фигурных скобках. Начиная с C# 10, можно объявлять пространство имён на уровне файла, без фигурных скобок:


namespace MyApplication.Utilities;

public class StringHelper
{
public static string Reverse(string input) => new(input.Reverse().ToArray());
}

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

namespace MyApplication.Services.Authentication

…будет эквивалентна трём вложенным пространствам:

MyApplication
MyApplication.Services
MyApplication.Services.Authentication

Такой подход помогает организовать код по функциональным модулям.

Чтобы не писать полный путь к типу каждый раз, используется директива using. В нашем случае - добавить в начало кода using MyApplication, и тогда всё, что находится в этом пространстве имён, не будет требовать указания.

Если же будет несколько подключенных пространств, у которых имеются одинаковые имена, то придётся конкретизировать, указав полный путь, чтобы компилятор понял, о каком именно элементе идёт речь. Это называется конфликтом имён.

Пример:

using FileLogger;
using CloudLogger;

Тогда прямо говорим - FileLogger.Logger.

Смысл таков: чтобы, к примеру, обратиться к какому-то методу, нужно указать его по шаблону «адрес.метод()». И если метод находится в классе A, то нужно указать A.метод() для вызова. И чтобы не писать полное имя типа каждый раз, можно подключить пространство имён, используя ключевое слово using:

using System;

namespace MyApp {
class Program {
static void Main() {
Console.WriteLine("Программа запущена");
}
}
}

Без необходимости using использовать не нужно – это может вызвать конфликты имён. Using используется как для библиотек, так и для пространств имён.

Но если к коду подключено два пространства, в которых имеются идентичные типы, к примеру, А содержит метод() и Б содержит метод(), то для системы будет неоднозначность - а обращаясь к метод(), мы вызываем его из А или из Б? В таком случае, придётся указать полный адрес. В Java используют import для импорта пакетов, в C++ - #include, а в C# - using.

Директива using static позволяет импортировать статические члены класса, чтобы использовать их без указания имени типа.

using static System.Console;
using static System.Math;

WriteLine("Корень из 16: " + Sqrt(16)); // Нет необходимости писать Console.WriteLine или Math.Sqrt

Это полезно при частом использовании статических членов. Как можно заметить. благодаря подключению System.Console, мы просто пишем WriteLine. В целом, удобно, но в крупных проектах не встречается - ведь чрезмерное использование using static может снизить читаемость, так как становится непонятно, откуда берётся метод.

Ключевое слово using используется не только как директива, но и как оператор для автоматического освобождения ресурсов. Оно работает с объектами, реализующими интерфейс IDisposable — например, файлы, соединения с базой данных, графические объекты. Там есть метод Dispose(), который выполняет определяемые приложением задачи, связанные с удалением, освобождением или сбросом неуправляемых ресурсов.

Можно встретить подобные конструкции:

using (var file = new StreamWriter("log.txt"))
{
file.WriteLine("Hello, World!");
} // Автоматически вызывается file.Dispose()

C# 8+ добавил using объявления (using declarations). Можно объявить переменную с using, и она будет автоматически освобождена в конце блока:

using var file = new StreamWriter("log.txt");
file.WriteLine("Hello, World!");
// Dispose() вызывается автоматически при выходе из области видимости

Но к этому ещё вернёмся.

В больших проектах одинаковые using повторяются в каждом файле (например, System, System.Collections.Generic). Чтобы избежать дублирования, C# 10 ввёл глобальные директивы. Для этого используется директива global using, которая убирает дубликаты во всем файле:

global using System;
global using System.Collections.Generic;

Такие директивы действуют во всём проекте, как будто они написаны в каждом файле. Размещать можно в отдельном файле, например, GlobalUsings.cs. Это уменьшит дублирование, упростит настройку проекта, повысит читаемость исходных файлов.

При формировании структуры проекта используйте иерархию по принципу

НазваниеКомпании.НазваниеПроекта.Модуль

И если глобальные пространства имён применяются для общего и не используются для всего подряд, то в каждом файле добавляются свои, нужные для кода файла пространства - это file-scoped namespaces, пространства в пределах соответствующего файла, куда они добавлены. Это уже можно и нужно применять, используя в проектах, если файл содержит один тип.

В случае возникновения конфликтов имён можете использовать алиасы или полные имена. Алиас - это короткий псевдоним для удобства, объявляющийся по шаблону:

using ИмяАлиаса = Полное.Имя.Пространства.Или.Типа;

Это пригодится, к примеру, если пространство имён очень длинное:

using DomainModels = MyCompany.Enterprise.Application.Core.Domain.Models;

И соответственно, при конфликте имён можно назвать пространства по разному и использовать алиас. Можно создать алиас даже не для всего пространства, а для конкретного типа:

using JsonConfig = System.Collections.Generic.Dictionary<string, object>;

JsonConfig config = new()
{
["version"] = "1.0",
["debug"] = true
};

Алиасы поддерживаются и глобально. Главное не переусердствовать - слишком много алиасов может запутать других разработчиков. В структуре проекта и формировании пространств имён, алиасов и в целом всех элементов вроде классов проверяйте на наличие дубликатов, старайтесь соблюдать культуру кода и принятые в коллективе правила, а также согласуйте названия. К примеру, DB в целом понятно любому (DataBase), а вот X1 - чёрт его знает что.