1. C# для Unity: ООП, коллекции, LINQ, исключения, SOLID
C# для Unity: ООП, коллекции, LINQ, исключения, SOLID
Эта статья закрывает фундамент C#, который требуется Unity-разработчику уровня middle: уверенное владение ООП, грамотный выбор коллекций, понимание LINQ (и его цены), корректная работа с исключениями и применение SOLID в архитектуре игровых систем.
С точки зрения Unity важно помнить: вы пишете не просто C#-код, а код, который работает в кадре, взаимодействует с движком, сериализацией, инспектором, сборщиком мусора и жизненным циклом объектов.
ООП в Unity: что реально применяется в проектах
Классы, объекты, поля, свойства, методы
В Unity подавляющее большинство логики живёт в классах:
MonoBehaviour.ScriptableObject.Ссылки:
Практическая рекомендация для middle: старайтесь выносить логику из MonoBehaviour в обычные классы, а MonoBehaviour использовать как слой привязки к сцене и событиям движка.
Инкапсуляция: управление доступом и защитой состояния
Инкапсуляция — это контроль над тем, кто и как может менять состояние объекта.
private и открывайте доступ через методы или свойства.[SerializeField] private ..., чтобы сериализация работала, а внешний код не мог менять поле напрямую.Почему это важно в Unity:
Наследование и композиция: как принято в Unity
Unity архитектурно заточен под композицию: объект на сцене собирается из компонентов.
Пример подхода через интерфейс (полиморфизм без жёсткого наследования):
Полиморфизм: виртуальные методы и интерфейсы
Полиморфизм позволяет работать с разными реализациями единообразно.
virtual/override — когда есть базовый класс и вы хотите переопределять поведение.В Unity интерфейсы особенно полезны для взаимодействий:
IInteractable для объектов, с которыми можно взаимодействовать.IDamageable для всего, что получает урон.IMovable для всего, что двигается (неважно, игрок это, NPC или лифт).Ссылка:
Абстракции данных через ScriptableObject
ScriptableObject удобно использовать как данные, отделённые от поведения:
Ключевой выигрыш: данные можно править без изменений кода, а логику — тестировать отдельно.
Коллекции в C# для Unity: выбор структуры под задачу
Коллекции — это основа производительного кода: неправильная структура данных легко превращает стабильные 60 FPS в просадки.
Ссылка:
Массивы и List
T[] — фиксированный размер, быстро, минимум накладных расходов.List<T> — динамический размер, удобные методы (Add, Remove, Find), но возможны перераспределения памяти при росте.Практика:
new List<T>(capacity).Update) избегайте частых Add/Remove, если это вызывает лишние выделения памяти.Dictionary и HashSet
Dictionary<TKey, TValue> — быстрый доступ по ключу (например, id -> данные).HashSet<T> — быстро проверяет наличие элемента (уникальные элементы).Важно для Unity:
Dictionary по умолчанию плохо сериализуется в инспекторе (встроенная сериализация Unity исторически не поддерживает обычные Dictionary так же прозрачно, как List). Часто используют обёртки, кастомные инспекторы или хранят данные иначе.Queue и Stack
Queue<T> — FIFO (первым пришёл, первым вышел). Пример: очередь задач или событий.Stack<T> — LIFO. Пример: история состояний, откат действий, стек UI-экранов.Быстрый справочник: что когда использовать
| Задача | Рекомендуемая структура |
|---|---|
| Список объектов, по которым вы проходите циклом | List<T> или массив |
| Поиск объекта по уникальному ключу (id, имя, тип) | Dictionary<TKey, TValue> |
| Проверка “есть ли элемент в наборе” | HashSet<T> |
| Последовательная обработка задач по очереди | Queue<T> |
| Откат/история, вложенность, “последний добавлен — первый обработан” | Stack<T> |
LINQ в Unity: мощно, но нужно знать цену
LINQ делает код компактным и выразительным, но в Unity важно понимать:
Ссылки:
Deferred execution: отложенное выполнение
Многие LINQ-выражения выполняются не сразу, а при перечислении.
Это полезно для композиции запросов, но может быть опасно, если:
Если нужен результат “здесь и сейчас”, фиксируйте:
ToArray()ToList()Но помните: это создаёт новые объекты в памяти.
Где LINQ уместен в Unity
Хорошие места:
UpdateОсторожно в:
Update, FixedUpdate, LateUpdateПример: создание словаря по списку (инициализация один раз):
Исключения в Unity: как применять безопасно
Исключения (exceptions) — механизм сигнализации об ошибках выполнения. В Unity необработанное исключение обычно приводит к ошибке в Console и прерыванию текущего выполнения метода.
Ссылка:
try/catch/finally и throw
try — код, который может упастьcatch — обработка конкретной ошибкиfinally — выполнится всегда (даже если была ошибка)throw — выброс исключенияПрактические правила для Unity-проектов
Try...-паттерн и проверки.Пример безопасного Try-подхода:
SOLID в Unity: принципы, которые реально улучшают проект
SOLID помогает делать код расширяемым, тестируемым и устойчивым к росту проекта. В Unity это особенно критично: игры быстро обрастают системами, а “быстрые решения” в компонентах превращаются в неуправляемую сеть зависимостей.
Ссылка:
Single Responsibility Principle
Один модуль — одна ответственность.
Плохой признак в Unity: один MonoBehaviour одновременно:
Правильнее разделять:
PlayerInput только читает вводPlayerMotor только двигаетHealth только отвечает за здоровьеHealthView только отображает здоровьеOpen/Closed Principle
Открыт для расширения, закрыт для изменения.
В Unity это часто достигается так:
ScriptableObject как расширяемые конфигурацииПример идеи: вместо switch по типу оружия — интерфейс IWeapon и разные реализации.
Liskov Substitution Principle
Объекты дочернего класса должны заменять объекты базового без поломки логики.
В Unity частый антипример: базовый класс обещает одно поведение, а наследник “ломает контракт” (например, базовый TakeDamage гарантирует уменьшение HP, а наследник вдруг игнорирует урон без явной причины).
Практика:
Interface Segregation Principle
Лучше несколько маленьких интерфейсов, чем один большой.
Плохо:
ICharacter с 20 методами (движение, атака, инвентарь, диалоги)Хорошо:
IMovable, IAttackable, IInventoryOwner, ITalkableТак компоненты получают только то, что им реально нужно.
Dependency Inversion Principle
Зависеть нужно от абстракций, а не от конкретных реализаций.
В Unity это обычно означает:
Мини-пример: компоненту нужен ввод, но не важно, клавиатура это или тач.