C# собеседования
753 subscribers
104 photos
222 links
Подготовка к собеседованиям на позицию C#-разработчик

Еще больше на сайте https://frontview-it.ru

Backend собеседования - @frontview_backend
C# работа - @frontview_csharp_vacancies
Все IT вакансии - @frontview_all_vacancies
Download Telegram
🔥 Что такое Razor и как он используется в ASP.NET MVC?

Razor — это синтаксис шаблонов представлений в ASP.NET MVC, который позволяет комбинировать C# код с HTML для динамической генерации веб-страниц. Он обеспечивает удобный и лаконичный способ интеграции серверного кода в представления, облегчая разработку и поддержку приложений.

Пример использования Razor в представлении:


@model MyApp.Models.User

<!DOCTYPE html>
<html>
<head>
<title>User Profile</title>
</head>
<body>
<h1>@Model.Name</h1> <!-- Отображение имени пользователя -->
<p>Email: @Model.Email</p> <!-- Отображение email пользователя -->

<!-- Условный вывод на основе серверной логики -->
@if(Model.IsAdmin)
{
<p>Administrator Access</p>
}
else
{
<p>Standard User</p>
}
</body>
</html>


В этом примере @ используется для перехода от HTML к C# коду. Синтаксис Razor позволяет вставлять переменные, выполнять условия и циклы прямо в представлении.

Основные преимущества Razor:

- Простой синтаксис: Минимум элементов шаблонизации, что делает код более читаемым.
- Интеграция с C#: Полная поддержка C# с возможностью использования пространства имен, классов и методов.
- Высокая производительность: Представления компилируются в классы, что ускоряет их выполнение.
- Поддержка IntelliSense: Помощь при написании кода в Visual Studio.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🔥 Какие фреймворки для unit-тестирования существуют в C#?

В C# доступны несколько фреймворков для юнит-тестирования, наиболее популярные из них:

1. MSTest

Стандартный фреймворк от Microsoft, интегрированный в Visual Studio.

2. NUnit

Один из самых известных фреймворков, предоставляющий широкий набор атрибутов и возможностей.

3. xUnit

Современный фреймворк, фокусирующийся на расширяемости и гибкости.

4. MBUnit (Gallio)

Фреймворк, расширяющий возможности NUnit, включая параметризованные тесты и другие функции.

5. Fixie

Гибкий фреймворк, позволяющий настроить конвенции для написания и выполнения тестов.

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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
🔥 Объясни разницу между try-catch и try-finally

try-catch и try-finally — это конструкции для обработки исключений в C#.

try-catch

try-catch используется для перехвата и обработки исключений, возникающих в блоке try. Если в блоке try происходит исключение, управление передается в соответствующий блок catch, где можно обработать ошибку.

Пример:


try
{
// Код, который может вызвать исключение
int result = 10 / divisor;
}
catch (DivideByZeroException ex)
{
// Обработка исключения деления на ноль
Console.WriteLine("Деление на ноль невозможно");
}


try-finally

try-finally обеспечивает выполнение блока finally независимо от того, произошло исключение в блоке try или нет. Это полезно для освобождения ресурсов или выполнения завершающих операций.

Пример:


FileStream file = null;
try
{
// Открытие файла для чтения
file = File.Open("data.txt", FileMode.Open);
// Чтение данных из файла
}
finally
{
// Гарантированное закрытие файла
if (file != null)
file.Close();
}


Ключевые различия:

- try-catch используется для перехвата и конкретной обработки исключений.
- try-finally гарантирует выполнение кода в блоке finally независимо от исключений, что полезно для очистки ресурсов.

Также возможно комбинировать оба:


try
{
// Код, который может вызвать исключение
}
catch (Exception ex)
{
// Обработка исключения
}
finally
{
// Код, который всегда выполнится
}


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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8
🔥 Что такое Identity Framework и как его использовать?

Identity Framework — это система для аутентификации и авторизации в приложениях ASP.NET Core. Она позволяет добавлять функциональность регистрации, входа, сброса пароля и управления ролями.

Для настройки Identity Framework:


// Регистрация ApplicationDbContext с использованием SQL Server
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

// Добавление сервисов Identity
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();


В контроллерах можно использовать _userManager и _signInManager для управления пользователями:


// Создание нового пользователя
var user = new IdentityUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);

// Вход пользователя
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, false);


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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍81
🔥 Объясни процесс сборки мусора в .NET

Garbage Collection (GC) в .NET — автоматический процесс управления памятью, который освобождает память, занятую объектами, недостижимыми из кода.

GC разделяет управляемую кучу на поколения: 0, 1 и 2. Новые объекты помещаются в поколение 0. При сборке мусора поколение 0 проверяется первым. Объекты, пережившие сборку, перемещаются в следующее поколение, что повышает эффективность, так как короткоживущие объекты удаляются быстрее.

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


GC.Collect(); // Принудительный вызов GC (не рекомендуется)


Для правильного управления неуправляемыми ресурсами следует реализовать интерфейс IDisposable и метод Dispose:


public class ResourceHolder : IDisposable
{
private IntPtr unmanagedResource; // Неуправляемый ресурс

public void Dispose()
{
// Освобождение неуправляемого ресурса
ReleaseResource(unmanagedResource);
GC.SuppressFinalize(this); // Подавление финализации
}

~ResourceHolder()
{
Dispose();
}
}


Такой подход обеспечивает корректное освобождение ресурсов и интеграцию с GC.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
C# собеседования pinned «Подписывайся на наши новые каналы! 👩‍💻 Git 🖥 SQL 👩‍💻 QA»
🔥 Как использовать лямбда-выражения с делегатами?

Лямбда-выражения позволяют создавать анонимные методы и часто используются с делегатами для краткости и удобства.

Например, есть делегат Func<int, int>:


// Делегат, принимающий int и возвращающий int
Func<int, int> square = x => x * x;


Здесь x => x * x — лямбда-выражение, которое присваивается делегату square.

Лямбда-выражения могут быть переданы как аргументы метода:


// Метод, принимающий делегат
void Process(Func<int, int> operation)
{
// Вызов делегата с аргументом 5
int result = operation(5);
Console.WriteLine(result);
}

// Вызов метода с лямбда-выражением
Process(x => x + 10);


Также можно использовать лямбда-выражения с LINQ:


int[] numbers = { 1, 2, 3, 4, 5 };

// Использование лямбда-выражения в методе Where
var evenNumbers = numbers.Where(n => n % 2 == 0);

foreach (var n in evenNumbers)
{
Console.WriteLine(n); // Выводит четные числа
}


Таким образом, лямбда-выражения упрощают работу с делегатами, делая код более лаконичным и удобочитаемым.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🔥 Что такое асинхронное программирование и зачем оно нужно?

Асинхронное программирование позволяет выполнять операции без блокировки основного потока, что особенно полезно для длительных задач, таких как ввод-вывод или сетевые запросы.

В C# асинхронность достигается с помощью ключевых слов async и await.


using System.IO;
using System.Threading.Tasks;

// Асинхронный метод чтения файла
async Task<string> ReadFileAsync(string path)
{
// Асинхронное чтение файла
using (StreamReader reader = new StreamReader(path))
{
return await reader.ReadToEndAsync();
}
}


Асинхронное программирование позволяет улучшить производительность и отзывчивость приложений. Вместо ожидания завершения операции программа может продолжать выполнять другие задачи.

При вызове асинхронного метода используется await для ожидания результата:


// Вызов асинхронного метода
string content = await ReadFileAsync("file.txt");


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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
🔥 Как работать с HashSet<T> и в каких случаях он полезен?

HashSet<T> — это коллекция, которая хранит уникальные элементы без определенного порядка. Полезна для быстрого поиска и предотвращения дубликатов.

Пример создания и использования:


// Создание HashSet для строк
HashSet<string> fruits = new HashSet<string>();

// Добавление элементов
fruits.Add("Apple");
fruits.Add("Banana");
fruits.Add("Orange");
fruits.Add("Apple"); // Не будет добавлен, т.к. "Apple" уже есть

// Проверка наличия элемента
bool hasBanana = fruits.Contains("Banana"); // true

// Перебор элементов
foreach (var fruit in fruits)
{
Console.WriteLine(fruit); // Выводит "Apple", "Banana", "Orange" в произвольном порядке
}


HashSet<T> эффективен для задач, где нужны уникальные элементы и быстрые операции поиска, добавления и удаления.

Операции множеств:


HashSet<int> setA = new HashSet<int> { 1, 2, 3 };
HashSet<int> setB = new HashSet<int> { 3, 4, 5 };

// Объединение множеств
setA.UnionWith(setB); // setA теперь содержит 1, 2, 3, 4, 5

// Пересечение множеств
setA.IntersectWith(setB); // setA теперь содержит 3

// Разность множеств
setA.ExceptWith(setB); // setA содержит элементы, которые есть в setA, но нет в setB


HashSet<T> идеален для сценариев, где важна уникальность данных, таких как избавление от дубликатов в списке или выполнение операций над множествами.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
🔥 Как использовать несколько блоков catch для разных типов исключений?

В C# можно использовать несколько блоков catch для обработки разных типов исключений. Это позволяет выполнять специфические действия в зависимости от возникшей ошибки.


try
{
// Код, который может вызвать исключение
int[] numbers = { 1, 2, 3 };
int number = numbers[5]; // IndexOutOfRangeException
}
catch (IndexOutOfRangeException ex)
{
// Обработка выхода за пределы массива
Console.WriteLine("Индекс находится за пределами массива.");
}
catch (NullReferenceException ex)
{
// Обработка null-ссылки
Console.WriteLine("Обращение к объекту по null-ссылке.");
}
catch (Exception ex)
{
// Обработка всех остальных исключений
Console.WriteLine($"Произошла ошибка: {ex.Message}");
}


Блоки catch должны располагаться от наиболее специфичных к общим. Первый блок перехватывает IndexOutOfRangeException, второй — NullReferenceException, а третий ловит все остальные исключения.

Также можно использовать фильтры исключений для дополнительной проверки:


try
{
// Код, который может вызвать исключение
}
catch (Exception ex) when (ex.Message.Contains("специфическая ошибка"))
{
// Обработка исключений с определенным сообщением
Console.WriteLine("Обнаружена специфическая ошибка.");
}


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

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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🔥 Объясни разницу между XmlSerializer и DataContractSerializer

XmlSerializer и DataContractSerializer сериализуют объекты в XML, но имеют разные особенности.

XmlSerializer:

- Требует публичного безпараметрического конструктора.
- Сериализует только публичные свойства и поля.
- Позволяет точно управлять XML через атрибуты.

Пример:


public class Person
{
public string Name { get; set; }
}

XmlSerializer serializer = new XmlSerializer(typeof(Person));


DataContractSerializer:

- Не требует публичного конструктора.
- Сериализует члены, помеченные [DataMember].
- Использует атрибуты [DataContract] и [DataMember].

Пример:


[DataContract]
public class Person
{
[DataMember]
public string Name { get; set; }
}

DataContractSerializer serializer = new DataContractSerializer(typeof(Person));


Различия:

- Контроль XML: XmlSerializer дает более точный контроль.
- Производительность: DataContractSerializer быстрее.
- Требования: XmlSerializer более требователен к классам.

Выбор зависит от необходимости контроля над XML и требований к производительности.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
👇 Другие направления для подготовки тут:

👩‍💻 Frontend
👩‍💻 Python
👩‍💻 Go
👩‍💻 Java
👩‍💻 C/C++
👩‍💻 PHP
👩‍💻 QA
🖥 SQL
👩‍💻 Git
Please open Telegram to view this post
VIEW IN TELEGRAM
1
🔥 Как профилировать приложение на C# для поиска узких мест?

Профилирование приложения на C# помогает обнаружить узкие места и оптимизировать производительность.

Использование Profiler в Visual Studio:

1. Открыть проект в Visual Studio.
2. Перейти в меню Debug > Performance Profiler.
3. Выбрать необходимые инструменты, например, CPU Usage для анализа загрузки процессора или Memory Usage для учета потребления памяти.
4. Запустить профилирование и воспроизвести сценарии использования приложения.
5. После завершения анализа просмотреть отчеты, чтобы выявить методы и участки кода с наибольшей нагрузкой.

Использование класса Stopwatch для замеров:


using System.Diagnostics;

// Начало измерения времени
Stopwatch stopwatch = Stopwatch.StartNew();

// Код, который необходимо проанализировать
PerformHeavyOperation();

// Остановка таймера
stopwatch.Stop();
Console.WriteLine($"Время выполнения: {stopwatch.ElapsedMilliseconds} мс");


Данный код замеряет время выполнения метода PerformHeavyOperation.

Сторонние инструменты профилирования:

- JetBrains dotTrace: мощный инструмент для детального анализа производительности.
- Redgate ANTS Performance Profiler: предоставляет визуализацию нагрузки и потребления ресурсов.
- PerfView: бесплатный инструмент от Microsoft для сбора и анализа трассировки событий.

Рекомендации:

- Сосредоточиться на наиболее ресурсозатратных методах.
- Оптимизировать алгоритмы и использовать асинхронность там, где это уместно.
- Профилировать различные сценарии использования для комплексной оценки производительности.

Периодическое профилирование и оптимизация критических участков кода значительно улучшают эффективность приложения.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3
🔥 Объясни концепцию record типов в C#

Record типы в C# (начиная с версии 9.0) представляют собой ссылочные типы, предназначенные для работы с неизменяемыми данными и семантикой значений.

Пример объявления record:


public record Person(string Name, int Age);


Данный record Person имеет свойства Name и Age.

При создании экземпляров record типов:


var person1 = new Person("Alice", 30);
var person2 = new Person("Alice", 30);


Особенность record в том, что они сравниваются по значению:


bool areEqual = person1 == person2; // true


areEqual будет true, так как значения свойств совпадают.

Record типы автоматически создают неизменяемые объекты. Свойства можно объявлять с init:


public record Car
{
public string Make { get; init; }
public string Model { get; init; }
}

var car = new Car { Make = "Toyota", Model = "Corolla" };


Cвойства Make и Model можно установить только при создании объекта.

Существуют возможности копирования с изменением:


var person3 = person1 with { Age = 31 };


Cоздан новый объект person3 на основе person1, но с измененным свойством Age.

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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍51
🔥 Как написать простой запрос LINQ для выборки элементов из списка?

LINQ (Language Integrated Query) позволяет удобно работать с коллекциями в C#. Для выборки элементов из списка можно использовать простой запрос LINQ.

Например, есть список чисел, и нужно выбрать только чётные числа:


List<int> numbers = new List<int> {1, 2, 3, 4, 5, 6};

// Выборка чётных чисел из списка
var evenNumbers = numbers.Where(n => n % 2 == 0).ToList();


Можно также использовать синтаксис запросов:


List<int> numbers = new List<int> {1, 2, 3, 4, 5, 6};

// Выборка чётных чисел с использованием синтаксиса запросов
var evenNumbers = (from n in numbers
where n % 2 == 0
select n).ToList();


В результате переменная evenNumbers будет содержать числа 2, 4 и 6.

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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
🔥 Что такое утечка памяти и как ее избежать в C#?

Утечка памяти в C# — это ситуация, когда память не освобождается сборщиком мусора из-за продолжающихся ссылок на объекты, которые больше не нужны.

Для предотвращения утечек памяти следует:

- Правильно управлять событиями и делегатами. Отписываться от событий, когда они более не требуются.


// Отписка от события
eventHandler -= EventMethod;


- Использовать using для IDisposable. Это гарантирует освобождение неуправляемых ресурсов.


// Использование using для IDisposable
using (var resource = new Resource())
{
// Работа с ресурсом
}


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

- Контролировать кэширование. Удалять устаревшие данные из кэша.

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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
🔥 Что такое переменная и константа? В чем их отличие?

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


int number = 10; // Объявление переменной number типа int со значением 10
number = 20; // Изменение значения переменной number на 20


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


const double Pi = 3.1415; // Объявление константы Pi типа double со значением 3.1415
// Pi = 3.14; // Ошибка: значение константы нельзя изменить


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

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
🔥 Как работает оператор null в C#?

В C# значение null обозначает отсутствие объекта или данные неопределенного значения. Его можно присвоить переменным ссылочных типов и типам, допускающим значение null (nullable types).

Основные операторы, связанные с null:

1. Оператор ?? (null-объединение):

Возвращает левый операнд, если он не равен null, иначе возвращает правый операнд.


string name = null;
string displayName = name ?? "Unknown"; // Если name == null, то displayName будет "Unknown"


2. Оператор ?. (null-условный доступ):

Позволяет безопасно обращаться к членам объекта, который может быть null, предотвращая исключение NullReferenceException.


Person person = null;
string firstName = person?.FirstName; // Если person == null, то firstName будет null


3. Оператор ??= (присваивание при null):

Присваивает значение правого операнда левому только если левый операнд равен null.


int? number = null;
number ??= 10; // Если number == null, то number присваивается 10


4. Оператор switch с шаблоном null:

Позволяет выполнять специальные действия, если значение равно null.


object obj = null;

switch (obj)
{
case null:
// Обработка случая, когда obj == null
break;
default:
// Обработка остальных случаев
break;
}


Использование этих операторов помогает писать более безопасный и лаконичный код, эффективно обрабатывая значения, которые могут быть null, и избегая избыточных проверок.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥1
🔥 Чем отличаются абстрактные классы от интерфейсов?

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

Абстрактные классы:

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


public abstract class Animal
{
public string Name; // Поле имени животного

public abstract void Speak(); // Абстрактный метод

public void Sleep()
{
// Реализация метода сна
}
}


Интерфейсы:

- Содержат только объявления методов и свойств (до C# 8.0), начиная с C# 8.0 могут иметь реализации по умолчанию.
- Не могут иметь полей (только константы).
- Не поддерживают конструкторы.
- Класс может реализовывать несколько интерфейсов.


public interface IMovable
{
void Move(); // Метод для перемещения
}


Основные отличия:

- Наследование: Абстрактный класс используется для строгой иерархии, интерфейсы — для гибкого контракта.
- Множественная реализация: Возможна только с интерфейсами.
- Члены класса: Абстрактные классы могут иметь реализацию и состояние, интерфейсы — нет (до C# 8.0).

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
🔥 Что такое IDisposable и как он помогает управлять памятью?

В C# интерфейс IDisposable определяет метод Dispose(), который используется для явного освобождения неуправляемых ресурсов, таких как файловые дескрипторы, соединения с базой данных и другие ресурсы, не управляемые сборщиком мусора.

Реализуя IDisposable, можно контролировать освобождение ресурсов и предотвращать утечки памяти.

Пример реализации IDisposable:


public class ResourceHolder : IDisposable
{
private bool disposed = false; // Флаг для отслеживания состояния освобождения

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // Отключение финализации
}

protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
// Освобождение управляемых ресурсов
}
// Освобождение неуправляемых ресурсов

disposed = true;
}
}

~ResourceHolder()
{
Dispose(false);
}
}


При использовании такого класса рекомендуется применять using, что обеспечивает автоматический вызов Dispose():


// Использование using гарантирует вызов Dispose()
using (var resource = new ResourceHolder())
{
// Работа с ресурсом
}


Таким образом, IDisposable позволяет корректно освобождать ресурсы, улучшая управление памятью и предотвращая возможные утечки.

Ставь 👍, если было полезно!
Еще больше ответов для подготовки к собеседованиям на сайте 👈
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5