c# - 根据规范监听事件并调用回调?
问题描述
我目前正在构建一个自定义任务管理器,我想知道是否可以告诉任务管理器侦听特定事件(OnSomething
如下),然后在任务引发该事件时调用回调方法。但是,从精神上讲,我看不到如何侦听基类级别不存在的事件。例如,我有一个基类,其中包含有关任务的基本信息,称为CustomTask
:
public abstract class CustomTask {
public bool IsRunning { get; private set; } = false;
public void Start() {
IsRunning = true;
DoSomething();
IsRunning = false;
}
protected abstract void DoSomething();
}
为了 SO 读者,我已经简化了定义,但你明白了它的要点。它包含基本的细节,一些启动和取消的方法,提供基本的状态管理(IsRunning
这里简化)等。
然后我有从 派生的自定义任务CustomTask
,在这种情况下,让我们关注一个名为 的示例任务CustomTaskA
。它包含一个名为 的事件的定义OnSomething
,某个地方的某个人可能想要监听该事件:
public sealed class CustomTaskA : CustomTask {
protected override void DoSomething() => RaiseOnSomething(this, new EventArgs());
public event EventHandler<EventArgs> OnSomething;
private void RaiseOnSomething(object sender, EventArgs e) => OnSomething?.Invoke(sender, e);
}
现在,CustomTaskManager
注册任务,通过 跟踪它们Guid
,管理它们等等,但为了简单起见:
public sealed class CustomTaskManager {
// Singleton setup.
private static CustomTaskManager _instance = new CustomTaskManager();
public static CustomTaskManager Instance {
get {
// Simplified for SO.
if (_instance == null)
_instance = new CustomTaskManager();
return;
}
}
// Collection of tasks.
private Dictionary<Guid, CustomTask> _tasks = new Dictionary<Guid, CustomTask>();
// Register and start a task.
public bool TryRegisterAndStartTask(CustomTask task, out Guid taskId) {
taskId = Guid.Empty;
try {
// Register task.
taskId = Guid.NewGuid();
_tasks.Add(taskId, task);
// Listen for events.
// Start task.
task.Start();
} catch (Exception e) {
// Log exception.
}
return false;
}
}
在注册启动任务时,我想告诉任务管理器我要监听OnSomething
,如果OnSomething
被调用,我希望任务管理器调用一个方法OnSomethingWasRaised
。例如:
TaskManager.Instance.TryRegisterAndStartTask(task, out Guid taskId, task.OnSomething, OnSomethingWasRaised);
private static void OnSomethingWasRaised(object sender, EventArgs e) {
Console.WriteLine("Woohoo!");
}
我知道指定和调用回调方法是完全可能的,并且通过反射来监听事件是合理的。
有没有办法(使用或不使用反射)监听派生对象上定义的指定事件,然后调用指定的回调方法?
注意:请原谅任何语法错误,因为我手动输入了片段以使其最小化。
解决方案
像这样的(建议的)方法的问题:
TryRegisterAndStartTask(task, out Guid taskId, task.OnSomething, OnSomethingWasRaised);
是你不能将事件作为参数传递,或者将它存储在变量中,因为事件只是一组两个方法(添加和删除),就像属性是一组两个方法 get 和 set。
您当然可以将事件更改为“原始”委托:
public EventHandler<EventArgs> OnSomething;
这个你可以通过参考传递:
public bool TryRegisterAndStartTask(CustomTask task, ref EventHandler<EventArgs> del, EventHandler<EventArgs> sub, out Guid taskId) {
taskId = Guid.Empty;
// subscribe
del += sub;
...
}
CustomTaskManager.Instance.TryRegisterAndStartTask(task, ref task.OnSomething, OnSomethingWasRaised, out var taskId);
但这通常不是一个好主意,因为您正在失去事件的私有范围 - 对于事件,只能添加\删除委托,对于原始委托,任何人都可以做任何事情,比如调用或设置为 null。
如果常规事件仍然存在 - 这意味着反射是实现目标的唯一方法,甚至更糟 - 您必须通过字符串名称而不是实际引用来引用要订阅的事件,尽管您可以使用nameof(task.OnSomething)
. 然后,您将丢失订阅委托类型的编译时验证。假设您想订阅event Action Something
但在那里传递Func<string>
委托。它将使用反射方法编译得很好,并且仅在运行时失败。
尽管如此,如果你坚持会看起来像这样:
public bool TryRegisterAndStartTask(CustomTask task, string eventName, Delegate sub, out Guid taskId) {
taskId = Guid.Empty;
// subscribe
var ev = task.GetType().GetEvent(eventName, BindingFlags.Public | BindingFlags.Instance);
var addMethod = ev.GetAddMethod(); // this can be null or private by the way
addMethod.Invoke(task, new [] {sub});
...
}
并像这样调用:
var task = new CustomTaskA();
EventHandler<EventArgs> handler = OnSomethingWasRaised;
CustomTaskManager.Instance.TryRegisterAndStartTask(task, nameof(task.OnSomething), handler, out var taskId);
在我看来,在你的场景中丑陋、不安全、不值得。
推荐阅读
- c# - c#如何正确编译代码以进行分发?
- c - 使用空格来表示字符串结束的错误
- google-cloud-platform - 如何使语音文本理解字符和数字,而不是单词
- yum - dnf 看不到本地托管存储库中的包
- docker - 在 docker 镜像中运行 Jupyter 并连接到外部 YARN 集群
- node.js - NPX-CRA:创建反应应用程序,使用 npx 引发错误
- mysql - MySQL select 上个月完成
- mysql - 如何在链接表中获取我的 sql 查询的确切结果
- amazon-web-services - cognito Oauth2.0 第三方认证
- laravel - 用户提交表单后,Laravel 5.6 签名 URL 无效