Конструктор, который принимает делегат в качестве параметра
вот упрощенный случай. У меня есть класс, который хранит делегат, который будет вызван по завершению:
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