Каково предназначение инструкции using в начале кодового файла

Search code, repositories, users, issues, pull requests…

Provide feedback

Saved searches

Use saved searches to filter your results more quickly

Sign up

Последнее обновление: 18.02.2022

В прошлой теме, где рассматривалась реализация метода Dispose, говорилось, что для его вызова можно использовать следующую конструкцию try..catch:

Test();

void Test()
{
    Person? tom = null;
    try
    {
        tom = new Person("Tom");
    }
    finally
    {
        tom?.Dispose();
    }
}
public class Person : IDisposable
{
    public string Name { get;}
    public Person(string name) => Name = name;

    public void Dispose() => Console.WriteLine($"{Name} has been disposed");
}

Однако синтаксис C# также предлагает синонимичную конструкцию для автоматического вызова метод Dispose — конструкцию using:

using (Person tom = new Person("Tom"))
{
}

Конструкция using оформляет блок кода и создает объект некоторого типа, который реализует интерфейс IDisposable, в частности, его метод Dispose. При завершении блока
кода у объекта вызывается метод Dispose.

Важно, что данная конструкция применяется только для типов, которые реализуют интерфейс IDisposable.

Ее использование:

Test();

void Test()
{
    using (Person tom = new Person("Tom"))
    {
        // переменная tom доступна только в блоке using
        // некоторые действия с объектом Person
        Console.WriteLine($"Name: {tom.Name}");
    }
    Console.WriteLine("Конец метода Test");
}
public class Person : IDisposable
{
    public string Name { get;}
    public Person(string name) => Name = name;

    public void Dispose() => Console.WriteLine($"{Name} has been disposed");
}

Консольный вывод:

Name: Tom
Tom has been disposed
Конец метода Test

Здесь мы видим, что по завершении блока using у объекта Person вызывается метод Dispose. Вне блока кода using объект tom не существует.

Начиная с версии C# 8.0 мы можем задать в качестве области действия всю окружающую область видимости, например, метод:

Test();

void Test()
{
    using Person tom = new Person("Tom");
    
    // переменная tom доступна только в блоке using
    // некоторые действия с объектом Person
    Console.WriteLine($"Name: {tom.Name}");
    Console.WriteLine("Конец метода Test");
}
public class Person : IDisposable
{
    public string Name { get;}
    public Person(string name) => Name = name;

    public void Dispose() => Console.WriteLine($"{Name} has been disposed");
}

В данном случае using сообщает компилятору, что объявляемая переменная должна быть удалена в конце области видимости — то есть в конце метода Test.
Соответственно мы получим следующий консольный вывод:

Name: Tom
Конец метода Test
Tom has been disposed

Освобождение множества ресурсов

Для освобождения множества ресурсов мы можем применять вложенные конструкции using. Например:

void Test()
{
	using (Person tom = new Person("Tom"))
	{
		using (Person bob = new Person("Bob"))
		{
			Console.WriteLine($"Person1: {tom.Name}    Person2: {bob.Name}");
		}// вызов метода Dispose для объекта bob
	} // вызов метода Dispose для объекта tom
	Console.WriteLine("Конец метода Test");
}

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

Мы можем сократить это определение:

void Test()
{
	using (Person tom = new Person("Tom"))
	using(Person bob = new Person("Bob"))
	{
		Console.WriteLine($"Person1: {tom.Name}    Person2: {bob.Name}");
	} // вызов метода Dispose для объектов bob и tom
	Console.WriteLine("Конец метода Test");
}

И, как уже было выше сказано, в C# мы можем задать в качестве области действия для объектов, создаваемых в конструкции using, весь метод:

private static void Test()
{
	using Person tom = new Person { Name = "Tom" };
	using Person bob = new Person { Name = "Bob" };
	
	Console.WriteLine($"Person1: {tom.Name}    Person2: {bob.Name}");
	
	Console.WriteLine("Конец метода Test");
} // вызов метода Dispose для объектов bob и tom

When I write code I don’t only want to write code that is correct. I also want to write code that is understandable, and maintainable. I want to deliver code that is easy to read not only for the compiler but also for other human beings. After all, humans will read my code more frequently than compilers.

I have been thinking what are the single most important keywords that help us write readable code. Probably this question doesn’t make much sense, but const and using are definitely among these. We already discussed const a lot, this time it’s time to see how using using can improve our code.

We are going to review the 4 ways we can use it:

  • type aliasing with using
  • introducing complete namespaces with using-directive
  • introducing members of another namespace with using-declaration
  • importing class members with using-declaration

Aliasing

In old C++ we could use typedef to give another name, to give an alias for our types. Sometimes you might want to use it instead of strong typing, just to benefit from more meaningful names like int.

1
typedef int Horsepower;

Other times you want to shorten long types for easier usage:

1
typedef std::vector<std::string>::iterator Iterator;

Since C++11 we can use using instead of typedef to achieve the same results.

1
2
using Horsepower = int;
using Iterator = std::vector<std::string>::iterator;

Why would you use using over the good old typedef? Just read the above statements! Exactly like the T.43 core guideline says, it’s more readable! The keyword has a very clear meaining, then the name comes first and the old comes after a =.

Besides, using can be used more generally. It can be used for template aliases where typedef would lead to a compilation error.

1
2
3
4
5
template<typename T>
typedef std::map<int, T> MapT;      // error

template<typename T>
using MapT = std::map<int, T>;   // OK

Using-directive in namespace and block scope

You’ve probably seen many code examples that right after the #include statements contain the line using namespace std.

You’ve probably seen lots of such application code.

You’ve probably been told that it’s bad.

It’s particularly bad if you do in at the global scope in a header file, just like [SF.7 from the Core Guidelines says]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// bad.h
#include <iostream>
using namespace std; // bad 

// user.cpp
#include "bad.h"

// some function that happens to be named copy
bool copy(/*... some parameters ...*/);

int main()
{
  // now overloads local ::copy and std::copy, could be ambiguous
  copy(/*...*/);
}

In my opinion, even the fact that as a reader you cannot be sure where a function comes from is bad. This is a simplistic example, but when you use using namespace in a long .cpp file it’s hard to keep track of where certain objects come from. I prefer having using-declarations instead and I also often introduce alias namespaces.

1
2
3
4
5
6
7
8
9
10
11
12
//some.h
#include <other.h>

using mcs = mynamespace::component::subcomponent;

msc::Class foo();
//some.cpp
msc::Class foo() {
  using msc::AnotherClass;
  AnotherClass bar;
  // ...
}

As such, I don’t pollute the global namespace. What you have to keep in mind is that when you introduce a using-directive into a header file at the global namespace header, you don’t just mess things up in the current scope.

If you include the header file in other files, you’ll also bring the inclusion of all those introduced symbols. If you introduce different header files with different global levels using-directives, the situation becomes even worse and the results of name lookup might depend on the order of inclusion.

To avoid all such problems, just follow SF.7 and don’t write using namespace at global scope in a header file.

Using-declaration in namespace and block scope

While the using-directive brings all the symbols of a namespace into the current scope, a using-declaration will bring only one item!

1
2
using std::string;
string foo{"bar"};

In the above example, we just demonstrated how it works. After using std::string, we can refer to std::string without mentioning the std namespace.

It’s still something not to overuse! A using-declaration may also expand an overload set. It’s less dangerous to use it at a file scope than having a using-directive at the same scope, but risks still remain.

Starting from C++20, you can also introduce scoped enumerators into a namespace of block scope!

1
2
3
4
5
6
enum class Color { red, green, blue };

class MyClass {
  using Color::red;
  Color c = red; // This is OK from C++20
};

In fact, it would also work with the old-style unscoped enum, but why would we do that?

Importing base class members with using-declaration

With using-declaration, you can introduce base class members — including constructors — into derived classes. It’s an easy way of exposing protected base class members as public in the derived class. It can be used both for functions and variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

class Base {
 protected:
  void foo() {
    std::cout << "Base::foo()\n";
  }
 
 int m_i = 42; 
};


class Derived : public Base {
 public:
  using Base::foo;
  using Base::m_i;
};

int main() {
  Derived d;
  d.foo();
  std::cout << d.m_i << '\n';
}
/*
Base::foo()
42
*/

If you try to modify the above example and remove any of the two using-declarations, you’ll see the compilation failing.

If the derived class already has a member with the same name, the compilation will not. The imported symbol from the base class will be hidden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>

class Base {
 protected:
  void foo() {
    std::cout << "Base::foo()\n";
  }
};


class Derived : public Base {
 public:
  using Base::foo;
  
  void foo() {
    std::cout << "Derived::foo()\n";
  }
};

int main() {
  Derived d;
  d.foo();
}
/*
Derived::foo()
*/

I find this technique really useful for unit testing. When you’re writing a mock by hand, you often have to expose protected member functions from the base class, from the class that you are about to mock.

One way of doing it is forwarding the call.

Hopefully, the function’s name in the mock is not changed, but I’ve seen it a couple of times. It really puts an extra burden on the maintainers when they realize that there is a better option.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ClassUnderTest {
 public:
  virtual void testMe() {
  }
  
  virtual void testMeToo() {
  }
};

class MockClassUnderTest : public ClassUnderTest {
 public:
  void testMe() override {
     ClassUnderTest::testMe(); 
  }
  
  void mockedTestMeToo() {
      ClassUnderTest::testMeToo(); 
  } 
};

Apart from tying a lot of unnecessary code, the problem above is that if the parameter list of testMe or testMeToo changes, you’ll also have to update MockClassUnderTest. You can get rid of that need by using using.

1
2
3
4
5
class MockClassUnderTest : public ClassUnderTest {
 public:
  using ClassUnderTest::testMe; 
  using ClassUnderTest::testMeToo;
};

Now we have less code and it’s more understandable what’s happening. As a bonus, even the maintenance is simplified.

Conclusion

In this article, we discussed the 4 different ways that we can use the using keyword. It’s the right way to create aliases and import base class members in derived classes. At the same time, they can be also used to introduce whole namespaces into the current scope which can be particularly dangerous. Last but not least, using can also introduce single types to the current scope which is a less dangerous option than introducing whole namespaces, still, it should be used with care.

Connect deeper

If you liked this article, please

  • hit on the like button,
  • subscribe to my newsletter
  • and let’s connect on Twitter!

Если операторы
cout
и cin
применяются очень часто, то использование
идентификатора std::
перед ними становится обременительным.
Эту проблему можно решить двумя способами.
Первый заключается в том, что в начале
текста кода можно сообщить компилятору
об использовании объектов cout
и cin
(или любых других) только из стандартной
библиотеки, как показано в листинге
2.1.

Листинг 2.1.
Использование
ключевого слова
using

1: #include
<
iostream>

2: Int main()

3: {

4: Using std::cout; // Вместо этих двух строк можно записать

5: Using std::endl; /* одну: using namespace std;*/

6:

7: cout
<< “Hello there.\n”;

8: cout
<< “Here is 5: ” << 5 << “\n”;

9: cout
<< “The manipulator endl “;

10: cout
<< “writes a new line to the screen.”;

11: cout
<< endl;

12: cout
<< “Here is a very big number: \t” << 70000;

13: cout
<< endl;

14: cout
<< “Here is the sum of 8 and 5: \t”;

15: cout
<< 8+5 <<endl;

16: cout
<< “Here is a fraction: \t”;

17: cout
<< (float) 5/8 << endl;

18: cout
<< “And a very very big number: \t”;

19: cout
<< (double) 7000*7000 << endl;

20: Return 0;

21: }

РЕЗУЛЬТАТ

Hello
there.

Here
is 5: 5

The
manipulator endl writes a new line to the screen.

Here
is a very big number: 70000;

Here
is the sum of 8 and 5: 13

Here
is a fraction: 0.625

And
a very very big number: 4.9
е+07

Помимо
стандартного объекта cout
в данной программе используется
стандартный оператор endl,
который выводит на экран символ новой
строки. В строке 12 (и в некоторых других)
используется еще один символ форматирования
\t,
который вставляет символ табуляции,
используемый для выравнивания выводимой
информации. Идентификатор float
указывает объекту cout,
что результат должен выводиться как
вещественное (с плавающей точкой) число.
Идентификатор double
устанавливает вывод результата в
экспоненциальном представлении.

Второй способ
избежать необходимости писать каждый
раз std::
перед стандартными операторами
заключается в том, что вместо строки
using
std::
cout;
используется определение пространства
имен using
namespace
std.

Преимуществом
формы записи using
namespace
std;
является отсутствие необходимости
указывать каждый используемый объект
явно. Недостатком является риск
непреднамеренного использования
объектов из несоответствующей библиотеки.
Ортодоксы предпочитают писать std::
перед каждым экземпляром cout
и endl.
Лентяи предпочитают использовать
пространство имен (using
namespace
std ;)
и рисковать.

    1. Комментарии

В строках 4 и 5
программы используются комментарии.
Комментарии
представляют собой текст, который
игнорируется компилятором, но позволяет
описать прямо в исходном коде назначение
отдельной строки или целого блока.

В языке С++
используются два типа комментариев: с
двойной наклонной чертой (//) и с сочетанием
наклонной черты и звездочки (/*). Комментарий
с двойной наклонной чертой (его называют
комментарием в стиле С++) велит компилятору
игнорировать все, что следует за этими
символами, вплоть до конца текущей
строки. Комментарий с двойной наклонной
чертой и звездочкой (его называют
комментарием в стиле С) велит компилятору
игнорировать все, что следует за символами
(/*), до того момента, пока не встретится
символ завершения комментария: звездочки
и наклонной черты (*/). Каждой открывающей
паре символов /*
должна соответствовать закрывающая
пара символов */.

Большинство
программистов используют в основном
комментарий в стиле С++ (//), а комментарий
в стиле С (/* */) применяют для временного
отключения больших участков программы.

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]

  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #
  • #

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

Директива – это…

Директива – это указание. Определение соответствующего понятия в программировании напоминает термин «команда». Он используется для описания некоторых конструкций ЯП. Указывает, как компилятор или любой иной транслятор должен обрабатывать вводимые данные.

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

Иногда подобные компоненты отвечают за определение глобального поведения, а в некоторых ситуациях действуют локально. В программах на C не являются обязательными. Они могут быть проигнорированы компилятором. Чаще всего носят предписывающий характер. В самом языке директивы (using и не только) не отвечают за выполнение тех или иных действий ЯП – они лишь корректируют поведение компиляторов.

Пространства имен – что это

Пространства имен (namespace) – это то, что позволяет выделить определенные части приложений в логические блоки. Поддерживается работа с:

  • классами;
  • интерфейсами;
  • прочими элементами программного кода.

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

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

Членами могут выступать:

  • пространства имен;
  • структуры;
  • делегаты.

Пространства имен можно охарактеризовать как группу классов, обладающих общими признаками.

Свойства

Перед тем как изучить директиву using, требуется запомнить следующие свойства именных пространств:

  • организация масштабных проектов по созданию программных кодов;
  • разделение происходит при помощи оператора «.» (точки);
  • global – корневая область имен.

Using – директива, которая позволяет исключить требование на указание названия области имен для каждого класса. Данный компонент избавляет от полной классификации «названий». Далее соответствующий элемент будет рассмотрен более подробно.

Using – описание

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

Using directive поддерживает применение двух модификаторов:

  1. Модификатор global. Он будет работать точно так, как и добавление одной директивы use к каждому исходному документу в пределах проекта. Первое появление состоялось в C Sharp 10 версии.
  2. Модификатор static. Он производит импорт элементов static, а также вложенных типов из одного типа, а не из всех в пределах области «названий».

Оба модификатора можно использовать вместе для импорта статистических членов из того или иного типа во всех исходных проектных файлах.

Сейчас C# поддерживает создание псевдонимов для namespace. Для этого используется директива псевдонимов using:

using Project = PC.MyCompany.Project;

Модификатор global используется в using alias. Без него область будет ограничиваться файлом, в пределах которого находится директива.

Особенности

Директива using в C# имеет ряд особенностей. Она отображается в нескольких местах – все зависит от ситуации:

  1. Начало документа в исходном коде. Перед тем, как объявлять именные «области», а также типы.
  2. В любом namespace, но до пространственных имен, объявленных в первой «области». Здесь обязательно, чтобы не использовался модификатор global. Если он присутствует, директива using располагается перед всеми объявлениями.

Если не соблюдать данные принципы, при попытке обработки кода появится ошибка компиляции CS1529.

Директива using создается для того, чтобы использовать типы в пространстве имен без необходимости указания его самого. Если использовать using System, больше не придется вводить полный путь. Методы в приложении получится вызывать быстрее и короче.

Using – это ключевое слово, которое в C# имеет широкое применение. Оно используется для создания операторов using. Данные элементы используются для грамотной обработки объектов IDisposable – шрифтов и файлов.

Псевдонимы

Using alias – это директива, позволяющая создавать псевдонимы для namespaces. У нее не может быть открытого универсального типа в правой части. Создать его для List<T> не получится, но можно для List<int>.

Если отсутствует импорт пространств имен System, полные имена базовых типов окажутся недоступными:

Для подключения using alias потребуется:

  1. Добавить в References желаемые сборки.
  2. Открыть соответствующую папку и кликнуть по сборке правой кнопкой мышки.
  3. Выбрать раздел Properties.
  4. Нажать на свойство Aliases.

После этого остается заменить значение global на свое собственное название.

Наглядный пример

Для того, чтобы хорошо разбираться в директивах и их uses, стоит изучить наглядный пример. Ниже приведен фрагмент кода. Он будет взять за «базу» для изучения directive и оператора:

Здесь в операторе using в круглых скобках написано выражение, обработка которого приводит к получению специального объекта. Он будет реализовывать интерфейс System IDisposable. Если объект не реализует интерфейс, на экране появится сообщение об ошибке.

В приведенном фрагменте объект определяется за пределами оператора using. Соответствующий код может быть записан так:

После обработки блока using в C происходит автоматический вызов метода Dispose(). Он осуществляется для переданного ранее объекта и располагается в интерфейсе System.IDisposable.

При использовании потоков необходимо запомнить – при завершении оперирования ими требуется завершать соответствующие процессы. Аналогичные принципы применяются к базам данных, сокетам, иным источникам. Метод Dispose() будет каждый раз завершать работу с потоком и высвобождать ресурсы.

А вот более простой фрагмент программы:

Здесь:

  1. Using namespace std указывает компилятору на импорт всех имен из пространства std в текущую область видимости. В приведенном примере таковым является main().
  2. После использования неполного идентификатора count происходит преобразование в std::count.

Данный вариант подойдет для того, чтобы упрощать программный код. С ним не придется вручную прописывать и обновлять все неполные имена до полных.

Как лучше освоить

C# – язык, который пользуется спросом у разработчиков. Для того, чтобы хорошенько изучить его директивы, а также пространства имен и иные элементы, можно заняться самообразованием. Это не лучшее решение – обучение может затянуться, а доказать полученные навыки и знания документально окажется невозможно. Именно поэтому рекомендуется отдать предпочтение дистанционным онлайн курсам.

Они рассчитаны на широкую публику и прекрасно совмещаются со всеми делами – уроки можно просматривать в любое время. Курсы рассчитаны на срок до 12 месяцев. Пользователи получают шанс выбрать одновременно одно или несколько инновационных IT-направлений для изучения. Процесс сопровождается домашними заданиями и богатой практикой. В конце обучения выдается электронный сертификат, подтверждающий приобретенный спектр навыков и знаний.

Хотите освоить современную IT-специальность? Огромный выбор курсов по востребованным IT-направлениям есть в Otus!

Понравилась статья? Поделить с друзьями:
  • Руководство пользователя руководство по acronis disk director
  • Био дигидрокверцетин инструкция по применению цена отзывы аналоги
  • Fms 3300 руководство оператора
  • Детская кровать с бортиком своими руками пошаговая инструкция
  • Электронная книга kindle amazon d01100 инструкция