Конструктор, который принимает делегат в качестве параметра

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

public class Animation
{
     public delegate void AnimationEnd();
     public event AnimationEnd OnEnd;
}

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

public class WaitForDelegate
{
    public delegateFired = false;

    // How to express the generic type here?
    public WaitForDelegate<F that's a delegate>(F trigger)
    {
        trigger += () => { delegateFired = true; };
    }
}

спасибо заранее!


спасибо Альберто Монтейру я просто использую System.Action как тип для события. Теперь мой вопрос: как передать событие конструктору, чтобы он мог зарегистрировать себя? Это может быть очень глупый вопрос.

public class Example
{
    Animation animation; // assume initialized

    public void example()
    {
        // Here I can't pass the delegate, and get an error like
        // "The event can only appear on the left hand side of += or -="
        WaitForDelegate waiter = new WaitForDelegate(animation.OnEnd);
    }
}

2 ответов


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

таким образом, класс анимации может быть таким:

public class Animation
{
     public event Action OnEnd;
}

но вы можете передавать события в качестве параметров, если вы попытаетесь, что вы получите эту ошибку компиляции

событие может отображаться только в левой части += или -="

поэтому давайте создадим интерфейс и объявим событие там

public interface IAnimation
{
    event Action OnEnd;
}

Используя подход интерфейса, у вас нет внешних зависимостей, и вы можете иметь много классов, которые реализуют это, также является хорошей практикой, зависит от абстракций вместо конкретных типов. Существует аббревиатура SOLID, которая объясняет 5 принципов о лучшем OO-коде.

и тогда ваш класс анимации реализует это

Obs.: на CallEnd метод только для целей тестирования

public class Animation : IAnimation
{
    public event Action OnEnd;

    public void CallEnd()
    {
        OnEnd();
    }
}

и теперь вы WaitForDelegate получит IAnimation, поэтому класс может обрабатывать любой класс, который реализует IAnimation класс

public class WaitForDelegate<T> where T : IAnimation
{
    public WaitForDelegate(T animation)
    {
        animation.OnEnd += () => { Console.WriteLine("trigger"); };
    }
}

тогда мы можем проверить код, который мы сделали со следующим кодом

    public static void Main(string[] args)
    {
        var a = new Animation();

        var waitForDelegate = new WaitForDelegate<IAnimation>(a);

        a.CallEnd();
    }

результат

триггер

вот рабочая версия на dotnetfiddle

https://dotnetfiddle.net/1mejBL

полезный совет

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

давайте еще раз посмотрим метод CallEnd, который я добавил для test

public void CallEnd()
{
    OnEnd();
}

событие OnEnd может не иметь значения, а затем, если вы попытаетесь его вызвать, вы получите исключение Null Reference.

Итак, если вы используете C# 5 или ниже, сделай что-нибудь вроде этого!--13-->

public void CallEnd()
{
    var @event = OnEnd;
    if (@event != null)
        @event();
}

С C# 6 это может быть так

public void CallEnd()
    => OnEnd?.Invoke();

больше объяснений, вы могли бы иметь этот код

public void CallEnd()
{
    if (OnEnd != null)
        OnEnd();
}

этот код, который выше, вероятно, заставит вас думать, что вы в безопасности от исключения Null Reference, но с многопоточным решением вы не. Это потому, что событие OnEnd может быть установлено в null между выполнением if (OnEnd != null) и OnEnd();

есть хорошая статья Джона Скита о это, вы не можете видеть вызов обработчика чистых событий с помощью C# 6