C# с нуля: Основы и Объектно-Ориентированное Программирование

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

1. Основы языка C#: синтаксис, типы данных и управляющие конструкции

Основы языка C#: синтаксис, типы данных и управляющие конструкции

Добро пожаловать в курс C# с нуля! Если вы читаете эту статью, значит, вы решили освоить один из самых мощных и востребованных языков программирования в мире. C# (произносится как «си-шарп») — это язык, разработанный компанией Microsoft. Он используется повсеместно: от создания корпоративных приложений и веб-сервисов до разработки мобильных игр на движке Unity.

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

Структура программы на C#

Любое путешествие в программирование начинается с классической программы «Hello, World!». Давайте посмотрим, как она выглядит на C#, и разберем каждую строчку.

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

!Иерархия структуры программы: Пространство имен содержит классы, классы содержат методы, методы содержат код.

Разберем элементы по порядку:

  • using System; — это подключение библиотеки. System — это стандартное пространство имен, где хранятся базовые инструменты, например, работа с консолью.
  • namespace MyFirstApp — пространство имен. Представьте, что это фамилия вашей программы. Оно нужно, чтобы имена ваших классов не конфликтовали с чужими.
  • class Program — класс. Это контейнер для данных и методов. В C# весь код должен жить внутри классов.
  • static void Main(string[] args) — это точка входа. Когда вы запускаете программу, компьютер ищет именно метод Main и начинает выполнение команд внутри него.
  • Console.WriteLine("..."); — команда вывода текста на экран. Обратите внимание: в конце каждой команды в C# обязательно ставится точка с запятой ;.
  • Переменные и типы данных

    Программам нужно где-то хранить информацию: возраст пользователя, цену товара, имя персонажа. Для этого используются переменные. Переменная — это именованная область в памяти компьютера.

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

    !Переменные как коробки: каждая коробка предназначена только для определенного типа содержимого.

    Основные типы данных

    Вот самые часто используемые типы:

    * int (Integer) — целые числа. Пример: 10, -5, 2023. * double — дробные числа (числа с плавающей точкой). Пример: 3.14, -0.5. * string — строка (текст). Обязательно заключается в двойные кавычки. Пример: "Привет". * char — один символ. Заключается в одинарные кавычки. Пример: 'A', '!'. * bool (Boolean) — логический тип. Может быть только true (истина) или false (ложь).

    Объявление и инициализация

    Чтобы создать переменную, мы пишем тип, затем имя, и (необязательно) сразу присваиваем значение через знак равно =.

    Если вы попытаетесь написать int number = "текст";, компилятор выдаст ошибку еще до запуска программы. Это защищает вас от множества глупых ошибок.

    Арифметика в программировании

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

    Где — результат вычисления, — входное значение переменной, и — числовые константы. В коде это будет выглядеть так:

    Основные операторы: * + (сложение) * - (вычитание) (умножение) * / (деление) * % (остаток от деления)

    > Важно помнить: при делении двух целых чисел (int) результат тоже будет целым. Дробная часть просто отбрасывается. 5 / 2 вернет 2, а не 2.5. Чтобы получить дробь, хотя бы одно число должно быть double.

    Ввод данных с консоли

    Программа становится интересной, когда она взаимодействует с пользователем. Для чтения текста используется команда Console.ReadLine().

    Однако ReadLine всегда возвращает строку (string). Если вы введете возраст «25», для программы это будет просто текст «25», который нельзя умножать или делить. Чтобы превратить строку в число, нужно использовать конвертацию.

    Управляющие конструкции

    Код не всегда должен выполняться линейно сверху вниз. Иногда нам нужно принимать решения или повторять действия. Для этого существуют управляющие конструкции.

    Условные операторы (if, else)

    Конструкция if позволяет выполнить блок кода, только если условие истинно. Это похоже на принятие решений в реальной жизни: «Если на улице дождь, я возьму зонт».

    !Блок-схема работы условного оператора if-else.

    Синтаксис:

    Операторы сравнения: * == (равно) * != (не равно) * > (больше), < (меньше) * >= (больше или равно), <= (меньше или равно)

    Обратите внимание на двойное равно == для сравнения. Одинарное = используется только для присваивания значения.

    Циклы (Loops)

    Циклы позволяют повторять блок кода несколько раз. Это избавляет нас от дублирования команд.

    #### Цикл while

    Цикл while работает до тех пор, пока условие истинно. «Пока вода в чайнике не закипела — жди».

    Здесь мы использовали оператор инкремента ++, который увеличивает значение переменной на единицу.

    #### Цикл for

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

    Рассмотрим математическую запись суммы ряда чисел:

    Где — итоговая сумма, — переменная счетчика, которая меняется от 1 до 5, а — знак суммирования. Мы складываем . В коде это реализуется так:

    В скобках for три части, разделенные точкой с запятой:

  • int i = 1 — инициализация счетчика (выполняется один раз в начале).
  • i <= 5 — условие продолжения (проверяется перед каждой итерацией).
  • i++ — действие после итерации (обычно увеличение счетчика).
  • Заключение

    Сегодня мы сделали первый и самый важный шаг. Вы узнали: * Как выглядит структура программы на C#. * Что такое переменные и почему важны типы данных. * Как заставить программу принимать решения с помощью if. * Как повторять действия с помощью циклов while и for.

    Этих знаний уже достаточно, чтобы писать простые текстовые квесты, калькуляторы или программы для решения математических задач. В следующей статье мы перейдем к сердцу C# — Объектно-Ориентированному Программированию (ООП). Мы узнаем, как создавать свои собственные типы данных и моделировать реальные объекты в коде.

    Практикуйтесь, экспериментируйте с кодом и не бойтесь ошибок — это лучший способ учиться!

    2. Методы и массивы: организация кода и работа с коллекциями

    Методы и массивы: организация кода и работа с коллекциями

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

  • Код становится слишком длинным и запутанным.
  • Переменных становится слишком много (представьте, что нужно хранить оценки 30 учеников).
  • Сегодня мы решим эти проблемы с помощью двух мощных инструментов: методов и массивов.

    Методы: разделяй и властвуй

    Представьте, что вы пишете программу для робота, который готовит завтрак. Процесс «сделать бутерброд» включает в себя: взять хлеб, намазать масло, положить сыр. Если роботу нужно сделать 10 бутербродов, копировать эти три команды 10 раз — плохая идея. Код станет огромным, а если вы решите заменить сыр на колбасу, придется менять код в 10 местах.

    В программировании для этого существуют методы (в других языках их часто называют функциями). Метод — это именованный блок кода, который можно вызывать столько раз, сколько нужно.

    !Метод работает как мини-фабрика: принимает данные, обрабатывает их и выдает результат.

    Структура метода

    Давайте посмотрим на метод, который складывает два числа:

    Разберем его анатомию:

  • static — модификатор (пока просто запомните, что он нужен для вызова из Main).
  • intтип возвращаемого значения. Это то, что метод отдаст нам в конце работы. Если метод ничего не возвращает, используется слово void.
  • Sumимя метода. Принято называть методы глаголами или действиями, с большой буквы.
  • (int a, int b)параметры. Это данные, которые нужны методу для работы.
  • return — ключевое слово, которое завершает работу метода и возвращает результат.
  • Вызов метода

    Чтобы код внутри метода сработал, его нужно «вызвать» в главной части программы (Main).

    Методы void

    Иногда нам не нужно ничего считать, а нужно просто выполнить действие: вывести текст на экран, сохранить файл или проиграть звук. Такие методы имеют тип void (пустота).

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

    Массивы: хранение коллекций

    Вернемся к примеру с учениками. Если у вас в классе 5 человек, вы можете создать 5 переменных: grade1, grade2, grade3... А если их 1000? Создавать тысячу переменных — безумие.

    Здесь на помощь приходят массивы. Массив — это структура данных, которая хранит набор значений одного типа под одним именем.

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

    Создание массива

    Чтобы объявить массив, мы используем квадратные скобки [] после типа данных.

    Здесь new int[5] говорит компьютеру: «Выдели в памяти место ровно для 5 целых чисел».

    Также можно сразу заполнить массив значениями:

    Индексы: счет начинается с нуля

    Это самое важное правило, которое часто сбивает с толку новичков: в программировании нумерация начинается с нуля.

    * Первый элемент массива имеет индекс 0. * Второй элемент — индекс 1. * Последний элемент массива длиной N имеет индекс N-1.

    Если вы попытаетесь обратиться к numbers[5], программа «упадет» с ошибкой IndexOutOfRangeException, потому что элемента с индексом 5 не существует (есть только 0, 1, 2, 3, 4).

    Совмещаем циклы и массивы

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

    Классический цикл for

    У каждого массива есть свойство .Length, которое показывает его длину. Используем его в цикле.

    Цикл foreach

    В C# есть специальный, более удобный цикл для перебора коллекций — foreach (для каждого). Он сам берет элементы по очереди, и вам не нужно думать об индексах.

    Используйте foreach, когда вам нужно просто прочитать все элементы. Используйте for, если вам нужно менять элементы (например, numbers[i] = 0) или идти в обратном порядке.

    Практический пример: Среднее арифметическое

    Давайте применим знания математики и программирования. Нам нужно найти среднее арифметическое оценок в массиве.

    Формула среднего арифметического выглядит так:

    Где: * — среднее арифметическое (результат). * — количество элементов (длина массива). * — знак суммирования (сумма всех элементов). * — каждый отдельный элемент массива.

    Реализуем это в коде:

    Обратите внимание: переменную sum мы сделали типом double. Если бы мы использовали int, то при делении 26 / 6 мы получили бы 4, а не 4.333..., так как целочисленное деление отбрасывает дробную часть.

    Многомерные массивы

    Иногда данных в одну строчку недостаточно. Например, для создания поля в игре «Морской бой» или шахматной доски нужна сетка. Это называется двумерный массив.

    Визуально это выглядит как таблица, где первый индекс — это номер строки, а второй — номер столбца.

    Заключение

    Сегодня мы сделали огромный шаг вперед. Теперь вы умеете:

    * Упаковывать повторяющийся код в методы, делая программу структурированной. * Хранить большие объемы данных в массивах. * Обрабатывать эти данные с помощью циклов for и foreach.

    В следующей статье мы перейдем к самой сути C# — Классам и Объектам. Мы узнаем, как создавать свои собственные типы данных, описывать сущности реального мира и связывать данные с методами в единое целое. Это и есть Объектно-Ориентированное Программирование.

    Не забывайте практиковаться: попробуйте написать метод, который находит самое большое число в массиве. Удачи!

    3. Введение в классы и объекты: создание собственных типов данных

    Введение в классы и объекты: создание собственных типов данных

    Добро пожаловать на третью лекцию курса C# с нуля! В предыдущих статьях мы научились оперировать базовыми типами данных (int, string, bool) и управлять логикой программы с помощью циклов и методов. Мы писали код, который выполнялся линейно или прыгал по условиям внутри одного большого метода Main.

    Но что, если мы хотим написать программу для интернет-магазина? Нам нужно хранить данные о товарах. У каждого товара есть название, цена, артикул, вес и производитель. Создавать пять разных массивов (names[], prices[] и так далее) и следить, чтобы индексы совпадали — это путь к ошибкам и хаосу.

    Здесь на сцену выходит Объектно-Ориентированное Программирование (ООП). Это подход, который позволяет нам создавать свои собственные типы данных, объединяя разрозненные переменные и методы в единые сущности — объекты.

    Философия ООП: Класс и Объект

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

    Представьте, что вы инженер на заводе по производству роботов.

  • Класс — это чертеж или схема. На бумаге написано: «Робот должен иметь две руки, лазер и серийный номер». Сам чертеж ничего не делает, он просто описывает, каким должен быть робот.
  • Объект (или экземпляр) — это конкретный робот, собранный по этому чертежу. Он стоит в цеху, его можно потрогать, включить, и у него есть свой уникальный серийный номер.
  • !Слева — класс (чертеж), справа — объект (реализация).

    По одному чертежу (классу) можно создать тысячи роботов (объектов). В коде это работает точно так же.

    Создание первого класса

    В C# классы объявляются с помощью ключевого слова class. Давайте создадим класс для описания персонажа в игре.

    Разберем, что мы написали: * class Character — мы объявили новый тип данных под названием Character. * public — это модификатор доступа. Он говорит о том, что эта переменная доступна всем. Если не написать public, переменная будет скрыта (по умолчанию private), и мы не сможем к ней обратиться извне. * Внутри класса мы объявили три переменные. В терминологии ООП переменные внутри класса называются полями.

    Теперь Character — это такой же полноценный тип данных, как int или string. Но пока это только чертеж.

    Создание объектов

    Чтобы «родить» персонажа, нам нужно создать экземпляр класса. Это делается в методе Main с помощью ключевого слова new.

    Оператор new выделяет память в компьютере под новый объект. Переменная hero — это пульт управления, который ссылается на эту область памяти.

    > Важно понимать: hero и monster — это два совершенно разных объекта. Изменение здоровья орка никак не повлияет на здоровье Артура, хотя они созданы по одному чертежу.

    Методы внутри классов (Поведение)

    Данные (поля) — это хорошо, но объекты должны что-то делать. В прошлом уроке мы писали методы отдельно. В ООП методы пишутся внутри класса и работают с данными этого класса.

    Добавим нашему персонажу возможность атаковать и выводить информацию о себе.

    Обратите внимание: внутри методов класса нам не нужно передавать name или damage как параметры. Метод и так «видит» поля своего класса. Мы просто пишем name, и программа понимает, что речь идет об имени текущего объекта.

    Теперь используем это в Main:

    Математика в методах

    Программирование игр или бизнес-приложений часто требует расчетов. Допустим, мы хотим рассчитать «боевую мощь» (Combat Power) персонажа, чтобы понять, насколько он силен. Пусть формула зависит от здоровья и урона.

    Где: * — итоговая боевая мощь (Combat Power). * — текущее здоровье персонажа (Health). * — урон персонажа (Damage). * — коэффициент баланса.

    Реализуем этот метод в классе:

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

    Конструкторы: правильное рождение объектов

    В примере выше мы создавали героя, а потом вручную, строчка за строчкой, задавали ему имя и здоровье:

    Если мы забудем задать имя, переменная name останется пустой (null), что может привести к ошибкам. Чтобы этого избежать, используются конструкторы.

    Конструктор — это специальный метод, который вызывается автоматически в момент создания объекта (при использовании new). Его имя всегда совпадает с именем класса, и у него нет возвращаемого типа (даже void).

    Теперь создать персонажа «неправильно» или «пустым» невозможно. Компилятор заставит нас передать данные сразу:

    Ключевое слово this

    Часто имена параметров в конструкторе делают такими же, как имена полей, чтобы не путаться. Но как компьютеру понять, где параметр name, а где поле класса name?

    Для этого используется ключевое слово this. Оно означает «этот объект».

    Ссылочные типы данных

    В первой лекции мы говорили, что int и double — это простые типы (значимые). Если скопировать число int a = 5; int b = a;, то b станет копией a. Изменение b не затронет a.

    С классами все иначе. Классы — это ссылочные типы (Reference types).

    !Переменные ссылочного типа хранят не сам объект, а ссылку (адрес) на него.

    Посмотрите на этот коварный пример:

    Почему? Потому что warrior2 = warrior1 не создает нового воина. Эта команда просто копирует адрес в памяти. Теперь и warrior1, и warrior2 указывают на один и тот же объект. Изменяя одного, вы меняете и другого, потому что это физически одна и та же сущность.

    Заключение

    Поздравляю! Вы перешли Рубикон. Понимание классов и объектов — это то, что отличает новичка от разработчика. Мы узнали:

  • Класс — это шаблон, Объект — это конкретный экземпляр.
  • Данные хранятся в полях, а поведение описывается методами.
  • Конструктор гарантирует, что объект будет создан с правильными начальными данными.
  • Объекты передаются по ссылке, а не копируются целиком.
  • В следующей статье мы углубимся в тему ООП и разберем три его кита: Инкапсуляцию, Наследование и Полиморфизм. Это позволит нам создавать сложные иерархии и защищать данные от неправильного использования.

    4. Инкапсуляция и наследование: защита данных и построение иерархий

    Инкапсуляция и наследование: защита данных и построение иерархий

    Приветствую вас на четвертой лекции курса C# с нуля! В прошлой статье мы познакомились с классами и объектами. Мы создали класс Character, наделили его здоровьем и именем, и научили атаковать. Казалось бы, всё работает отлично.

    Но давайте представим ситуацию: вы пишете игру, и ваш коллега (или вы сами через месяц) случайно написал такой код:

    Персонаж с отрицательным здоровьем продолжает бегать, а герой без имени ломает интерфейс. Или другая ситуация: вы хотите создать Мага, Лучника и Воина. Вы копируете код класса Character три раза и меняете пару строк. А потом решаете добавить всем уровень (level). Вам придется менять код в трех разных местах. Это неудобно и ведет к ошибкам.

    Сегодня мы решим эти проблемы с помощью двух китов Объектно-Ориентированного Программирования (ООП): Инкапсуляции и Наследования.

    Инкапсуляция: защита внутреннего мира

    Инкапсуляция — это механизм, который объединяет данные и методы, работающие с ними, в классе, и скрывает детали реализации от пользователя.

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

    !Инкапсуляция защищает данные, предоставляя доступ к ним только через специальные методы.

    Модификаторы доступа

    В C# мы управляем доступом с помощью ключевых слов:

    * public — доступно всем. Это наш «руль» и «педали». * private — доступно только внутри этого же класса. Это «двигатель под капотом».

    Давайте защитим здоровье нашего персонажа. Сделаем поле health приватным.

    Теперь, чтобы изменить здоровье, мы обязаны использовать метод SetHealth. Если мы попытаемся передать -50, метод превратит это в 0. Мы защитили объект от некорректных данных.

    Свойства (Properties)

    Писать методы Get и Set для каждой переменной (как в примере выше) — это стиль языка Java или C++. В C# придумали более элегантный инструмент — Свойства.

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

    В коде использования это выглядит так, будто мы работаем с обычной переменной:

    Ключевое слово value в блоке set — это то значение, которое мы пытаемся присвоить свойству.

    Автоматические свойства

    Если вам не нужна проверка данных, а нужно просто хранить значение, можно использовать сокращенную запись:

    Под капотом C# сам создаст скрытое приватное поле, но код станет намного чище.

    Наследование: построение иерархии

    Теперь перейдем ко второй проблеме. У нас есть Character. Мы хотим создать Warrior (Воин) и Mage (Маг). У них много общего: имя, здоровье, позиция. Но есть и различия: у воина есть броня, у мага — мана.

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

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

    Синтаксис наследования

    В C# наследование обозначается двоеточием :.

    Теперь посмотрим, как это работает в Main:

    Класс Mage получил всё, что было у Character, абсолютно бесплатно. Это соответствует принципу DRY (Don't Repeat Yourself — не повторяйся).

    Модификатор protected

    Мы знаем public (открыто всем) и private (только мне). Но что, если мы хотим, чтобы поле было доступно наследникам, но закрыто для остального мира?

    Для этого существует protected.

    Из Main обратиться к speed будет нельзя.

    Конструкторы и ключевое слово base

    Конструкторы не наследуются. Если у родителя есть конструктор с параметрами, наследник обязан его вызвать. Для этого используется ключевое слово base.

    Когда создается Mage, сначала отрабатывает конструктор Character (заполняет имя), и только потом конструктор Mage (заполняет ману).

    Практический пример: Расчет урона

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

    Формула снижения урона:

    Где: * — итоговый урон, который получит персонаж. * — исходный «сырой» урон от удара. * — показатель брони защищающегося. * — константа баланса.

    Реализуем это в классе Warrior, который наследуется от Character.

    Здесь мы видим, как наследник (Warrior) использует свою уникальную характеристику (Armor) и общее свойство (Health), унаследованное от родителя, для реализации сложной логики.

    Виртуальные методы и переопределение (Override)

    Иногда наследник хочет не просто добавить что-то новое, а изменить поведение родителя. Например, обычный персонаж при атаке просто пишет «Атакую», а Маг должен писать «Кидаю фаербол».

    Для этого в родительском классе метод помечается как virtual (виртуальный, то есть «можно изменить»), а в наследнике — как override (переопределить).

    Теперь, если мы вызовем Attack() у объекта класса Mage, выполнится именно его версия метода. Это начало темы Полиморфизма, которую мы подробно разберем в следующей статье.

    Заключение

    Сегодня мы сделали наш код профессиональным и безопасным.

  • Инкапсуляция позволила нам скрыть внутренние переменные и контролировать их изменение через Свойства. Теперь никто не сможет задать персонажу отрицательное здоровье.
  • Наследование избавило нас от дублирования кода. Мы создали общий класс Character и расширили его специализированными классами Mage и Warrior.
  • В следующей статье мы объединим всё это с Полиморфизмом и Интерфейсами, чтобы создать по-настоящему гибкую систему, где разные объекты могут взаимодействовать друг с другом, не зная деталей реализации соседа.

    Экспериментируйте с кодом: попробуйте создать класс Archer (Лучник), который наследуется от Character, имеет свойство ArrowsCount (количество стрел) и переопределяет метод атаки так, чтобы стрелы тратились при выстреле.

    5. Полиморфизм, абстракция и интерфейсы: гибкость программной архитектуры

    Полиморфизм, абстракция и интерфейсы: гибкость программной архитектуры

    Приветствую вас на пятой лекции курса C# с нуля! Мы уже проделали большой путь: научились создавать переменные, писать методы, проектировать классы и выстраивать иерархии наследования. В прошлой статье мы создали классы Mage (Маг) и Warrior (Воин), которые наследуются от общего предка Character (Персонаж).

    Однако, если мы попытаемся написать код для управления армией, состоящей из разных типов войск, мы столкнемся с неудобствами. Нам придется создавать отдельные массивы для магов и воинов, писать отдельные циклы для их атаки. Это нарушает принцип универсальности.

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

    Полиморфизм: Один интерфейс — множество реализаций

    Слово «полиморфизм» пришло из греческого языка и означает «много форм». В программировании это способность объекта вести себя по-разному в зависимости от того, кем он является на самом деле, даже если мы обращаемся к нему как к общему предку.

    Представьте, что вы командир. Вы даете команду «Атаковать!» всему отряду. Вам не нужно подходить к каждому солдату и объяснять: «Ты бей мечом, а ты читай заклинание». Вы даете одну общую команду, а каждый боец выполняет её по-своему.

    !Иллюстрация принципа полиморфизма: единая команда вызывает разное поведение у разных объектов.

    Виртуальные методы и переопределение

    Чтобы это заработало в C#, нам нужны два ключевых слова:

  • virtual — указывает в базовом классе, что метод может быть изменен наследниками.
  • override — указывает в классе-наследнике, что мы изменяем поведение базового метода.
  • Вернемся к нашему классу Character:

    Магия полиморфизма в действии

    Самое интересное происходит, когда мы помещаем разных персонажей в один массив типа Character.

    Результат вывода: * Конан наносит мощный удар мечом! * Гэндальф запускает огненный шар! * Новичок просто бьет рукой.

    Это и есть полиморфизм. Переменная member имеет тип Character, но в памяти компьютера лежат разные объекты, и C# знает, какую версию метода вызвать для каждого из них.

    Математический взгляд на эффективность

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

    Где: * — общий урон отряда. * — количество бойцов в отряде. * — знак суммирования (сумма по всем элементам от 1 до N). * — -й юнит (персонаж) в массиве. * — функция (метод) получения урона конкретного юнита.

    В коде это выглядит элегантно и просто:

    Абстракция: Запрет на неопределенность

    Посмотрите на код выше. Мы создали объект `new Character { Name =