← Назад к вопросам

Используешь ли Delegate в работе

1.0 Junior🔥 201 комментариев
#C# и ООП

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Использование делегатов в разработке на Unity

Да, делегаты — это фундаментальный инструмент в моей ежедневной работе с Unity и C#. Они являются краеугольным камнем для реализации событийно-ориентированной архитектуры, обратных вызовов (callbacks) и создания гибкого, слабосвязанного кода, что критически важно в геймдеве. Их использование выходит за рамки базовых сценариев и пронизывает большинство аспектов разработки игры.

Ключевые сценарии применения делегатов

  1. Система событий (Event System): Это наиболее распространённое использование. Вместо того чтобы делать прямые жёсткие ссылки между объектами (например, Player напрямую вызывает метод UIManager), я создаю события с помощью делегатов Action или EventHandler.
    *   **Пример:** При получении урона игроком генерируется событие `OnPlayerHealthChanged`. Интерфейс, система достижений, звуковой менеджер — все могут подписаться на это событие независимо, не зная друг о друге.

```csharp
public class PlayerHealth : MonoBehaviour
{
    // Объявляем событие с использованием обобщённого Action<текущееЗдоровье, максимальноеЗдоровье>
    public event System.Action<int, int> OnHealthChanged;

    private int _currentHealth = 100;

    public void TakeDamage(int damage)
    {
        _currentHealth -= damage;
        // Безопасный вызов события (проверка на наличие подписчиков)
        OnHealthChanged?.Invoke(_currentHealth, 100);

        if (_currentHealth <= 0)
        {
            Die();
        }
    }
}

public class HealthUI : MonoBehaviour
{
    [SerializeField] private Slider _healthSlider;
    [SerializeField] private PlayerHealth _playerHealth;

    private void Start()
    {
        // Подписываем метод UpdateHealthBar на событие
        _playerHealth.OnHealthChanged += UpdateHealthBar;
    }

    private void UpdateHealthBar(int current, int max)
    {
        _healthSlider.value = (float)current / max;
    }

    private void OnDestroy()
    {
        // ВАЖНО: отписываемся при уничтожении объекта
        _playerHealth.OnHealthChanged -= UpdateHealthBar;
    }
}
```

2. Обратные вызовы (Callbacks) для асинхронных операций: Делегаты идеально подходят для уведомления о завершении длительных задач.

    *   **Пример:** Загрузка ассетов с `Addressables` или `Resources.LoadAsync`, отправка веб-запроса, завершение анимации.

```csharp
public void LoadSceneAsync(string sceneName, System.Action<float> onProgress, System.Action onComplete)
{
    StartCoroutine(LoadSceneRoutine(sceneName, onProgress, onComplete));
}

private IEnumerator LoadSceneRoutine(string sceneName, System.Action<float> onProgress, System.Action onComplete)
{
    AsyncOperation asyncOp = SceneManager.LoadSceneAsync(sceneName);
    while (!asyncOp.isDone)
    {
        onProgress?.Invoke(asyncOp.progress); // Передаём прогресс
        yield return null;
    }
    onComplete?.Invoke(); // Уведомляем о завершении
}
```

3. Реализация шаблонов и гибких систем:

    *   **Стратегия (Strategy Pattern):** Делегат `Func` или `Action` может инкапсулировать алгоритм (например, способ расчёта урона или перемещения), который можно подменять в рантайме.
    *   **Наблюдатель (Observer Pattern):** Как показано в примере с событиями.
    *   **Фильтрация и сортировка:** Использование `Predicate<T>` и делегатов сравнения в `List.Find` или `Array.Sort` для кастомизированной логики.

  1. Работа с UI (UnityEvent и делегаты): Компоненты UI Button, Toggle и другие используют UnityEvent, который является сериализуемой обёрткой над делегатами для настройки в Инспекторе. На низком уровне это те же делегаты.

Важные аспекты и лучшие практики

  • Отписка (-=): Крайне важно отписывать методы от событий, когда объект, содержащий метод-подписчик, уничтожается или становится неактуальным. Иначе это приводит к утечкам памяти, когда сборщик мусора не может очистить объект из-за оставшейся ссылки на него в делегате. Я всегда делаю отписку в методах OnDestroy, OnDisable или при деактивации объекта.
  • Потокобезопасность: В Unity, где работа с делегатами обычно происходит в основном потоке, использование ?.Invoke() (null-conditional operator) безопасно. В многопоточных сценариях требуется дополнительная синхронизация.
  • Использование обобщённых делегатов: Я предпочитаю использовать встроенные обобщённые типы System.Action (для void-методов) и System.Func (для методов, возвращающих значение) вместо объявления своих кастомных делегатов, если это не требуется для особой ясности или сериализации. Это стандартизирует код.
  • event ключевое слово: Я всегда использую event при объявлении публичного делегата. Это добавляет защиту, предотвращая внешний код от полной перезаписи списка подписчиков (= вместо +=) или его принудительного вызова извне класса.

Итог: Делегаты — не просто "используемая" технология, а незаменимый паттерн проектирования, воплощённый в синтаксисе C#. Они лежат в основе взаимодействия систем в Unity, позволяя создавать чистый, поддерживаемый и масштабируемый код, где компоненты общаются, не создавая жёстких зависимостей. Без их грамотного применения построение сложной игровой архитектуры было бы значительно труднее и сопряжено с постоянными рефакторингами.