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

Можно ли сделать цепочку вызова делегатов?

1.3 Junior🔥 142 комментариев
#C# и ООП#Другое

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

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

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

Можно ли сделать цепочку вызова делегатов?

Да, безусловно можно и даже нужно! В C# и Unity создание цепочек вызова делегатов — это мощный и часто используемый паттерн. Он основан на встроенной возможности комбинирования делегатов с помощью операторов +, +=, -, -=. Такую цепочку часто называют мультикаст-делегатом (multicast delegate).

Как это работает

Когда вы комбинируете несколько методов в один делегат, они добавляются в его список вызовов (invocation list). При вызове делегата все методы из этого списка выполняются последовательно, в том порядке, в котором они были добавлены.

Практический пример в Unity

Рассмотрим типичный сценарий: система событий для обновления состояния игрока.

// 1. Объявляем тип делегата
public delegate void PlayerStatusHandler(string status);

public class Player : MonoBehaviour
{
    // 2. Создаем экземпляр делегата как событие (для безопасности)
    public static event PlayerStatusHandler OnStatusChanged;

    void Start()
    {
        // 3. Подписываем несколько методов на событие, формируя цепочку.
        // Порядок подписи определяет порядок вызова.
        OnStatusChanged += LogStatusToConsole;
        OnStatusChanged += UpdateUI;
        OnStatusChanged += PlaySoundEffect;
    }

    void TakeDamage()
    {
        // 4. При вызове события все подписанные методы выполнятся один за другим.
        if (OnStatusChanged != null)
            OnStatusChanged("Player took damage!");
    }

    // 5. Методы-подписчики, которые образуют цепочку.
    private void LogStatusToConsole(string status)
    {
        Debug.Log($"Log: {status}");
    }

    private void UpdateUI(string status)
    {
        // Код для обновления интерфейса...
        Debug.Log($"UI Updated: {status}");
    }

    private void PlaySoundEffect(string status)
    {
        // Код для воспроизведения звука...
        Debug.Log($"Sound played for: {status}");
    }

    void OnDestroy()
    {
        // Важно! Отписываемся при уничтожении объекта во избежание утечек памяти.
        OnStatusChanged -= LogStatusToConsole;
        OnStatusChanged -= UpdateUI;
        OnStatusChanged -= PlaySoundEffect;
    }
}

Ключевые особенности и важные замечания

  • Порядок вызова: Методы вызываются в порядке их добавления в список вызовов.
  • Тип возвращаемого значения: Для создания цепочки через + или += делегат должен иметь возвращаемый тип void. Если делегат возвращает значение (например, int), при вызове цепочки будет возвращено значение только последнего выполнившегося метода. Это обычно нежелательно для multicast-делегатов.
  • Использование событий (event): В Unity, особенно для публичных делегатов, настоятельно рекомендуется использовать ключевое слово event. Оно добавляет защиту:
    *   Подписчики могут только добавлять (`+=`) или удалять (`-=`) свои методы.
    *   **Только класс-издатель может вызвать делегат напрямую** (например, в методе `TakeDamage()`), что предотвращает его случайный вызов или перезапись извне.
  • Проверка на null: Перед вызовом делегата всегда проверяйте, есть ли у него подписчики (if (OnStatusChanged != null)). В современных версиях C# можно использовать более безопасный синтаксис с null-условным оператором:
    OnStatusChanged?.Invoke("Player took damage!");
    
  • Отписка (Unsubscribe): Чтобы избежать ошибок, "висячих" ссылок и утечек памяти (особенно при загрузке/выгрузке сцен), необходимо отписывать методы, когда объект, владеющий ими, уничтожается или становится неактуальным. Это часто делается в методах OnDisable() или OnDestroy().

Вывод: Создание цепочек делегатов — это фундаментальный механизм C# для реализации гибких и слабосвязанных систем обратного вызова. В Unity этот паттерн лежит в основе многих архитектурных решений, от простых UI-уведомлений до сложных систем глобальных игровых событий. Используя его правильно, вы значительно повышаете модульность и удобство поддержки вашего кода.