Три фундаментальных принципа объектно-ориентированного программирования (ООП), на которых строится архитектура приложений в C#.
1. Инкапсуляция
Это механизм скрытия внутреннего состояния и реализации объекта от внешнего мира, а также объединение данных (полей) и методов, работающих с этими данными, в единую сущность (класс).
Реализация в C#: Осуществляется с помощью модификаторов доступа (private, protected, internal, public). Состояние класса обычно хранится в private полях, а доступ к ним предоставляется через public методы или свойства (properties) с аксессорами get и set.
Смысл: Защита данных от некорректного изменения извне и скрытие сложности реализации.
2. Наследование
Механизм, позволяющий создать новый класс (производный/наследник) на основе существующего (базового/родительского). Наследник перенимает все поля, свойства и методы родителя.
Реализация в C#: Обозначается двоеточием: class Derived : Base. В C# поддерживается только множественное наследование интерфейсов, но множественное наследование реализаций (классов) запрещено.
3. Полиморфизм
Способность системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта. Буквально — “множество форм”. Позволяет одному и тому же коду работать с разными типами данных.
Динамический полиморфизм: В C# родительский класс объявляет метод как virtual (или abstract), а класс-наследник переопределяет его логику с помощью ключевого слова override.
Подробный пример синтаксиса:
// 1. ИНКАПСУЛЯЦИЯ: Скрываем поле balance, даем доступ через методыpublic class BankAccount{ private decimal balance; // Скрытое состояние public void Deposit(decimal amount) { if (amount > 0) balance += amount; // Защита от некорректных данных } public decimal GetBalance() => balance;}// 2. НАСЛЕДОВАНИЕ: Класс Animal - базовый, Dog - наследникpublic class Animal{ public string Name { get; set; } // Ключевое слово virtual разрешает переопределение метода public virtual void Speak() { Console.WriteLine("Животное издает звук"); }}public class Dog : Animal{ // 3. ПОЛИМОРФИЗМ: Переопределяем поведение метода public override void Speak() { Console.WriteLine($"{Name} говорит: Гав!"); }}// Использование полиморфизмаAnimal myPet = new Dog { Name = "Шарик" };myPet.Speak(); // Выведет "Шарик говорит: Гав!", хотя тип переменной Animal
2. Классы и объекты в С#
Класс (Class)
Класс в C# — это пользовательский ссылочный тип данных (reference type), представляющий собой чертеж, шаблон или метаданные. Он описывает структуру: какие данные (поля) и какое поведение (методы) будут иметь сущности.
Объект (Object / Instance)
Объект — это конкретный экземпляр класса, созданный в оперативной памяти (в куче - Heap) с помощью оператора new.
Подробный пример синтаксиса:
// Объявление класса (чертеж)public class Car{ // Поля (состояние объекта) public string Model; public int Year; // Конструктор (вызывается при создании объекта) public Car(string model, int year) { Model = model; Year = year; } // Метод (поведение) public void Drive() { Console.WriteLine($"Машина {Model} ({Year}) поехала!"); }}class Program{ static void Main() { // Создание объекта (инстанцирование) с помощью оператора new // myCar - это ссылка в стеке, указывающая на данные в куче Car myCar = new Car("Toyota", 2022); // Обращение к методу объекта myCar.Drive(); // Вывод: Машина Toyota (2022) поехала! }}
3. Обработка параметров командной строки в C#
Параметры командной строки — это строковые аргументы, которые передаются программе операционной системой в момент её запуска через консоль (например, app.exe input.txt -verbose).
1. Через параметр метода Main:
Массив args содержит переданные аргументы, начиная с первого пользовательского аргумента.
2. Через класс Environment:
Метод Environment.GetCommandLineArgs() возвращает массив, где нулевой индекс [0] — это всегда путь к исполняемому файлу программы.
Подробный пример синтаксиса:
Запуск программы: dotnet run myFile.txt --force
class Program{ // Способ 1: Через параметры Main static void Main(string[] args) { Console.WriteLine("=== Способ 1: массив args ==="); if (args.Length > 0) { for (int i = 0; i < args.Length; i++) { // Выведет: [0]: myFile.txt, [1]: --force Console.WriteLine($"Аргумент [{i}]: {args[i]}"); } } // Способ 2: Через класс Environment (доступен в любом месте кода) Console.WriteLine("\n=== Способ 2: Environment ==="); string[] envArgs = Environment.GetCommandLineArgs(); // Выведет путь к .dll/.exe файлу Console.WriteLine($"Путь к программе: {envArgs[0]}"); // Выведет myFile.txt Console.WriteLine($"Первый аргумент: {envArgs[1]}"); }}
4. Разделение интересов (Separation of Concerns, SoC)
Разделение интересов — это фундаментальный принцип программной инженерии, направленный на разделение программы на независимые секции (модули, классы), каждая из которых отвечает за отдельную задачу (например: логика БД отдельно от отрисовки UI).
Подробный пример синтаксиса:
Плохой код (всё в одном месте):
public class BadApp { public void DoEverything() { // 1. Читает из БД string data = "SELECT * FROM Users"; // 2. Делает расчет string parsed = data + " processed"; // 3. Выводит на экран Console.WriteLine(parsed); }}
Хороший код (разделение интересов):
// Слой 1: Работа с данными (Data Access)public class UserRepository{ public string GetUserFromDb() => "John Doe";}// Слой 2: Бизнес-логика (Business Logic)public class UserService{ private UserRepository _repo = new UserRepository(); public string FormatUserName() { string name = _repo.GetUserFromDb(); return $"User: {name.ToUpper()}"; // Логика обработки }}// Слой 3: Представление (UI / View)public class UserConsoleView{ private UserService _service = new UserService(); public void ShowUser() { // UI ничего не знает о БД, он только выводит строку Console.WriteLine(_service.FormatUserName()); }}
5. Ввод и вывод информации. Форматирование строк
В C# консольный I/O осуществляется через статический класс System.Console.
Существует три основных способа форматирования: String.Format, конкатенация и современная строковая интерполяция (через знак $).
Подробный пример синтаксиса:
class Program{ static void Main() { // --- ВВОД --- Console.Write("Введите ваше имя: "); string name = Console.ReadLine(); // Читаем строку до Enter Console.Write("Введите ваш возраст: "); int age = int.Parse(Console.ReadLine()); double price = 1250.50; // --- ВЫВОД И ФОРМАТИРОВАНИЕ --- // 1. Композитное форматирование (устаревающий подход) Console.WriteLine("Привет, {0}! Тебе {1} лет.", name, age); // 2. Строковая интерполяция (современный подход со знаком $) Console.WriteLine($"Привет, {name}! Тебе {age} лет."); // 3. Спецификаторы формата внутри интерполяции Console.WriteLine($"Цена товара: {price:C}"); // Вывод валюты (напр. 1 250,50 ₽) Console.WriteLine($"Число Пи: {Math.PI:F2}"); // Фиксированная точка: 3.14 Console.WriteLine($"Возраст (с нулями): {age:D4}"); // Добивка нулями: 0025 // Считывание одной клавиши без нажатия Enter Console.WriteLine("Нажмите любую клавишу для выхода..."); Console.ReadKey(); }}
6. Методы
Метод — это именованный блок кода, содержащий последовательность инструкций.
В C# параметры в методы могут передаваться по значению, по ссылке (ref), как выходные (out) и входные (in).
Подробный пример синтаксиса:
class MathUtils{ // 1. Обычный метод (передача по значению) public int Add(int a, int b) { return a + b; } // 2. Expression-bodied метод (сокращенный синтаксис) public int Multiply(int a, int b) => a * b; // 3. Параметр OUT: метод возвращает bool, а результат вычисления кладет в out-переменную public bool TryDivide(int a, int b, out int result) { if (b == 0) { result = 0; // Обязаны присвоить значение до выхода! return false; } result = a / b; return true; } // 4. Параметр REF: передача адреса памяти, метод меняет саму исходную переменную public void Swap(ref int x, ref int y) { int temp = x; x = y; y = temp; } // 5. Необязательные и именованные параметры public void PrintInfo(string name, int age = 18) // age по умолчанию = 18 { Console.WriteLine($"{name}, {age}"); }}// Использование:MathUtils utils = new MathUtils();// Вызов метода с outif (utils.TryDivide(10, 2, out int divResult)) Console.WriteLine($"Результат: {divResult}"); // Выведет 5// Вызов метода с ref (переменные должны быть инициализированы)int val1 = 5, val2 = 10;utils.Swap(ref val1, ref val2); // Теперь val1 = 10, val2 = 5// Именованные параметрыutils.PrintInfo(age: 25, name: "Ivan");
7. Циклы: for, foreach/in
1. Цикл for — классический цикл со счетчиком. Выполняется, пока истинно условие.
2. Цикл foreach — перебирает элементы коллекций. Элемент цикла доступен только для чтения (read-only), и коллекцию нельзя менять во время перебора.
Подробный пример синтаксиса:
int[] numbers = { 10, 20, 30, 40, 50 };// === ЦИКЛ FOR ===Console.WriteLine("Цикл for:");// инициализация ; условие ; итерацияfor (int i = 0; i < numbers.Length; i++){ // В for можно изменять элементы массива по индексу if (numbers[i] == 30) numbers[i] = 99; Console.WriteLine($"Индекс {i}: {numbers[i]}");}// === ЦИКЛ FOREACH ===Console.WriteLine("\nЦикл foreach:");foreach (int num in numbers){ // num = 100; // ОШИБКА КОМПИЛЯЦИИ: num доступна только для чтения Console.WriteLine($"Значение: {num}");}
8. Циклы: while, do/while
Циклы while и do/while используются для выполнения блока кода до тех пор, пока заданное логическое условие остается истинным.
1. Цикл while (цикл с предусловием)
Проверяет условие до выполнения тела цикла. Если условие ложно изначально, тело цикла не выполнится ни одного раза.
2. Цикл do/while (цикл с постусловием)
Проверяет условие после выполнения тела цикла. Главная особенность: тело цикла гарантированно выполнится хотя бы один раз.
Подробный пример синтаксиса:
// === ЦИКЛ WHILE ===int count = 0;Console.WriteLine("Цикл while:");while (count < 3) { Console.WriteLine($"Счетчик: {count}"); count++;}// === ЦИКЛ DO/WHILE ===string password;Console.WriteLine("\nЦикл do/while:");do{ // Этот блок выполнится минимум один раз, даже если пароль сразу верный Console.Write("Введите пароль (123): "); password = Console.ReadLine();} while (password != "123"); // Проверка в конце. Обязательна точка с запятой!Console.WriteLine("Доступ разрешен.");
9. Условие if…else. Конструкция switch.
Основные конструкции ветвления в C#.
if...else проверяет логическое выражение (bool).
switch сравнивает значение с набором констант (или паттернов). В C# запрещено проваливание между непустыми блоками case (обязателен break).
Подробный пример синтаксиса:
int temp = 15;// === КОНСТРУКЦИЯ IF...ELSE ===if (temp > 25) { Console.WriteLine("Жарко");} else if (temp >= 10 && temp <= 25) { Console.WriteLine("Тепло"); // Сработает этот блок} else{ Console.WriteLine("Холодно");}// === КЛАССИЧЕСКИЙ SWITCH ===int day = 3;switch (day){ case 1: Console.WriteLine("Понедельник"); break; // Обязательный выход case 2: case 3: // Проваливание разрешено только для пустых case Console.WriteLine("Вторник или Среда"); break; default: Console.WriteLine("Другой день"); break;}// === СОВРЕМЕННОЕ ВЫРАЖЕНИЕ SWITCH (C# 8.0+) ===// Возвращает значение напрямуюstring result = day switch{ 1 => "Понедельник", 2 or 3 => "Вторник или Среда", _ => "Другой день" // _ заменяет default};Console.WriteLine(result);
10. Поразрядный оператор И
Поразрядное (битовое) И (AND) обозначается символом &.
Сравнивает числа побитово: результат равен 1 только если оба соответствующих бита равны 1. В остальных случаях 0.
Применяется для маскирования битов (извлечения или проверки состояния конкретного бита).
Подробный пример синтаксиса:
// 12 в двоичном: 0000 1100// 10 в двоичном: 0000 1010int a = 12; int b = 10;// a & b: 0000 1100// & 0000 1010// ----------------// Итог: 0000 1000 (это 8 в десятичной)int result = a & b; Console.WriteLine(result); // Выведет 8// ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ: Проверка на четность// Последний бит нечетного числа всегда 1. Маска "1" обнулит все биты кроме последнего.int number = 7; // 0000 0111if ((number & 1) == 1) { Console.WriteLine("Число нечетное");}
11. Поразрядный оператор ИЛИ
Поразрядное (битовое) ИЛИ (OR) обозначается символом |.
Результат равен 0 только если оба бита равны 0. Если хотя бы один бит 1, результат будет 1.
Применяется для установки (включения) битов или комбинирования флагов.
Подробный пример синтаксиса:
// 12 в двоичном: 0000 1100// 10 в двоичном: 0000 1010int a = 12; int b = 10;// a | b: 0000 1100// | 0000 1010// ----------------// Итог: 0000 1110 (это 14 в десятичной)int result = a | b; Console.WriteLine(result); // Выведет 14// ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ: Установка флагов (комбинирование прав)[Flags] // Атрибут для работы с битовыми маскамиenum FileAccess { None = 0, Read = 1, Write = 2, Execute = 4 }// Даем пользователю права на чтение (1) и запись (2)FileAccess userAccess = FileAccess.Read | FileAccess.Write; Console.WriteLine((int)userAccess); // Выведет 3 (0011 в двоичном)
12. Поразрядный оператор исключающее ИЛИ
Поразрядное исключающее ИЛИ (XOR) обозначается символом ^.
Результат равен 1, если соответствующие биты операндов различны. Результат равен 0, если биты одинаковы.
Применяется для инвертирования битов по маске и в простейшей криптографии.
Подробный пример синтаксиса:
// 12 в двоичном: 0000 1100// 10 в двоичном: 0000 1010int a = 12; int b = 10;// a ^ b: 0000 1100// ^ 0000 1010// ----------------// Итог: 0000 0110 (это 6 в десятичной)int result = a ^ b; Console.WriteLine(result); // Выведет 6// ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ: Шифрование или инверсия битовint key = 42;int message = 150;int encrypted = message ^ key; // Зашифровалиint decrypted = encrypted ^ key; // Расшифровали обратно (свойство XOR)// ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ: Обмен значений переменных без третьейint x = 5, y = 10;x ^= y;y ^= x;x ^= y;Console.WriteLine($"x={x}, y={y}"); // Выведет x=10, y=5
13. Поразрядный оператор НЕ
Поразрядное (битовое) НЕ (NOT) обозначается символом ~ (тильда). Это унарный оператор.
Инвертирует каждый бит числа: заменяет все нули на единицы, а все единицы на нули.
Применяется для создания обратных масок при сбросе (выключении) конкретных битов.
Подробный пример синтаксиса:
byte a = 12; // 0000 1100// ~a: инверсия всех бит// Итог: 1111 0011 (в знаковом int это даст отрицательное число -13)int result = ~a; Console.WriteLine(result); // Выведет -13 (т.к. -x = ~x + 1)// ПРАКТИЧЕСКОЕ ПРИМЕНЕНИЕ: Сброс (выключение) конкретного бита// Допустим, мы хотим выключить 3-й бит (значение 4) в числе 15 (0000 1111)int mask = 4; // 0000 0100int inverse = ~mask; // 1111 1011int data = 15; // 0000 1111data = data & ~mask; // Побитовое И с инвертированной маской// Итог: 0000 1011 (это 11 в десятичной)Console.WriteLine(data); // Выведет 11
14. Простые массивы
Простой (одномерный) массив — это ссылочный тип данных (Reference Type), хранящий фиксированное количество элементов одного типа в непрерывном блоке памяти (в куче). Индексация начинается с нуля.
Подробный пример синтаксиса:
// 1. Объявление с выделением памяти (элементы заполняются нулями по умолчанию)int[] numbers = new int[5]; numbers[0] = 10;numbers[1] = 20;// 2. Объявление с немедленной инициализацией (синтаксический сахар)string[] names = { "Alice", "Bob", "Charlie" };// 3. Получение размера массиваConsole.WriteLine($"Размер массива: {names.Length}"); // 3// 4. Современные фишки C# (Индексы и диапазоны)string lastElement = names[^1]; // Выведет "Charlie" (^1 значит первый с конца)string[] firstTwo = names[0..2]; // Создаст новый массив с "Alice", "Bob" (элемент 2 не включается)// 5. Перебор массиваforeach (string name in names){ Console.WriteLine(name);}
15. Прямоугольный массив
Прямоугольный массив — это истинно многомерный массив, элементы которого организованы в виде матрицы. Каждая строка имеет строго одинаковую длину. Разделителем измерений в скобках выступает запятая [,].
Подробный пример синтаксиса:
// 1. Объявление двумерного массива (3 строки, 4 столбца)int[,] matrix = new int[3, 4];matrix[0, 0] = 5; // Присваивание// 2. Объявление с немедленной инициализациейint[,] grid = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 }};// 3. Получение размеров матрицыint totalElements = grid.Length; // 9int rows = grid.GetLength(0); // 3 (длина нулевого измерения - строк)int cols = grid.GetLength(1); // 3 (длина первого измерения - столбцов)// 4. Перебор элементов прямоугольного массиваfor (int i = 0; i < grid.GetLength(0); i++){ for (int j = 0; j < grid.GetLength(1); j++) { Console.Write($"{grid[i, j]} \t"); } Console.WriteLine(); // Переход на новую строку после вывода столбцов}
16. Ломаный массив (Зубчатый массив)
Ломаный (зубчатый) массив (Jagged Array) — это массив массивов. Каждая его строка (внутренний массив) является самостоятельным объектом, поэтому строки могут иметь разную длину. Объявляется с помощью двойных квадратных скобок [][].
Подробный пример синтаксиса:
// 1. Объявление ломаного массива (3 строки)int[][] jagged = new int[3][];// 2. Инициализация каждой строки (выделение памяти под внутренние массивы)jagged[0] = new int[4]; // Строка 0: 4 элементаjagged[1] = new int[2]; // Строка 1: 2 элементаjagged[2] = new int[] { 1, 2, 3 }; // Строка 2: 3 элемента сразу с инициализацией// Присваивание значенияjagged[0][1] = 42;// 3. Объявление с немедленной инициализацией всего массиваint[][] jagged2 ={ new int[] { 1, 2 }, new int[] { 3, 4, 5, 6 }, new int[] { 7 }};// 4. Перебор ломаного массиваConsole.WriteLine("Элементы ломаного массива:");for (int i = 0; i < jagged2.Length; i++) // Проход по строкам (внешнему массиву){ for (int j = 0; j < jagged2[i].Length; j++) // Проход по элементам i-й строки { Console.Write($"{jagged2[i][j]} "); } Console.WriteLine();}
17. Перечисление в C# (enum)
Перечисление (enum) — это значимый тип данных (Value Type), задающий набор именованных целочисленных констант. Повышает читаемость кода, избавляя от “магических чисел”.
Подробный пример синтаксиса:
// 1. Базовое перечисление (по умолчанию тип int, отсчет с 0)enum Days { Mon, Tue, Wed, Thu, Fri, Sat, Sun }// 2. Перечисление с явным указанием типа и значенийenum HttpCode : short{ OK = 200, NotFound = 404, ServerError = 500}// 3. Перечисление как битовые флаги (позволяет комбинировать значения)[Flags]enum Permissions : byte{ None = 0, // 0000 Read = 1, // 0001 Write = 2, // 0010 Execute = 4, // 0100 All = 7 // 0111 (Read | Write | Execute)}class Program{ static void Main() { Days today = Days.Wed; // Каст (преобразование) в число Console.WriteLine($"День номер: {(int)today}"); // Выведет 2 // Работа с битовыми флагами Permissions myRights = Permissions.Read | Permissions.Write; // 3 (0011) // Проверка флага встроенным методом (C# 4+) if (myRights.HasFlag(Permissions.Write)) { Console.WriteLine("У вас есть права на запись"); } }}
18. Структуры (struct)
Структура (struct) — это пользовательский значимый тип данных (Value Type).
Экземпляры структур хранятся в стеке (Stack), а не в куче.
При передаче структуры происходит полное побитовое копирование её значений (а не ссылки).
Не поддерживает наследование от других классов/структур (но может реализовывать интерфейсы).
Подробный пример синтаксиса:
// Объявление структурыpublic struct Point{ public int X; public int Y; // Конструктор структуры public Point(int x, int y) { X = x; Y = y; } public void Print() => Console.WriteLine($"({X}, {Y})");}class Program{ static void ModifyPoint(Point p) { p.X = 100; // Изменяется КОПИЯ структуры, оригинал останется нетронутым } static void Main() { Point p1 = new Point(10, 20); // Память выделяется в стеке Point p2 = p1; // ПРОИСХОДИТ ПОЛНОЕ КОПИРОВАНИЕ ДАННЫХ p2.X = 50; // p1.X останется равен 10 ModifyPoint(p1); p1.Print(); // Выведет (10, 20). Изменение внутри метода не сработало! }}
19. Пространства имен (namespace)
Пространство имен (namespace) служит для логической группировки классов и предотвращения конфликтов имен. Чтобы использовать тип из другого пространства, нужно указать директиву using.
Подробный пример синтаксиса:
// Файл NetworkClient.csnamespace App.Network{ public class Client { public void Connect() => Console.WriteLine("Connected"); }}// Файл DatabaseClient.cs// СОВРЕМЕННЫЙ СИНТАКСИС (C# 10+): File-scoped namespace (без фигурных скобок)namespace App.Database;public class Client{ public void Save() => Console.WriteLine("Saved");}// Файл Program.csusing App.Network; // Подключаем пространство именusing DbClient = App.Database.Client; // Создаем алиас (псевдоним) для конкретного классаclass Program{ static void Main() { // Используем класс напрямую благодаря using App.Network; Client netClient = new Client(); // Используем другой класс с таким же именем через алиас DbClient dbClient = new DbClient(); }}
20. Области видимости и Модификаторы доступа
Область видимости задается фигурными скобками {}. Модификаторы доступа контролируют видимость членов класса (полей, методов) из других частей программы.
Подробный пример синтаксиса:
public class BankAccount{ // Видно ТОЛЬКО внутри этого класса private decimal _balance; // Видно внутри этого класса и во всех классах-наследниках protected string OwnerName; // Видно во всем текущем проекте (Assembly), но скрыто от других DLL internal string BankCode; // Доступно из любого места программы public void Deposit(decimal amount) { // Область видимости переменной fee ограничена этим методом decimal fee = 10; if (amount > 1000) { // Область видимости переменной bonus ограничена блоком if decimal bonus = amount * 0.05m; _balance += (amount + bonus - fee); } // Console.WriteLine(bonus); // ОШИБКА: bonus здесь не существует }}
21. Перегрузка методов (Method Overloading)
Перегрузка — это создание нескольких методов с одинаковым именем, но разным набором параметров (сигнатурой). Компилятор сам решает, какой метод вызвать, исходя из переданных аргументов.
Подробный пример синтаксиса:
public class Printer{ // 1. Метод принимает строку public void Print(string text) { Console.WriteLine($"Текст: {text}"); } // 2. ПЕРЕГРУЗКА: То же имя, но принимает целое число public void Print(int number) { Console.WriteLine($"Число: {number}"); } // 3. ПЕРЕГРУЗКА: То же имя, но принимает два числа public void Print(int a, int b) { Console.WriteLine($"Сумма: {a + b}"); } // public string Print(int number) { return "123"; } // ОШИБКА: Возвращаемый тип не входит в сигнатуру. // Нельзя перегрузить метод только изменением возвращаемого типа!}class Program{ static void Main() { Printer p = new Printer(); p.Print("Hello"); // Вызовет 1-й метод p.Print(42); // Вызовет 2-й метод p.Print(10, 20); // Вызовет 3-й метод }}
22. Указатель this
this — это скрытая ссылка на текущий экземпляр класса. Применяется для разрешения конфликтов имен, цепочек конструкторов и передачи текущего объекта в другие методы. Не работает в static методах.
Подробный пример синтаксиса:
public class Employee{ private string name; private int salary; // 1. РАЗРЕШЕНИЕ КОНФЛИКТА ИМЕН (Shadowing) // Имя параметра совпадает с именем поля public void SetName(string name) { this.name = name; // this.name - поле, name - параметр метода } // 2. ЦЕПОЧКА КОНСТРУКТОРОВ (Constructor chaining) // Вызывает другой конструктор этого же класса перед выполнением своего тела public Employee(string name) : this(name, 50000) { // ... } public Employee(string name, int salary) { this.name = name; this.salary = salary; } // 3. ПЕРЕДАЧА САМОГО СЕБЯ public void RegisterInCompany(Company company) { // Передаем текущий объект (себя) в метод другого класса company.AddEmployee(this); }}