根据规范监听事件并调用回调?
Listen for event and invoke callback, based on specification?
我目前正在构建一个自定义任务管理器,我想知道是否可以告诉任务管理器侦听特定事件(下面的 OnSomething
),然后在任务引发该事件。但是,在精神上我看不出如何监听一个在基础 class 级别不存在的事件。例如,我有一个基础 class,其中包含有关名为 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和设置。
您当然可以将事件更改为“原始”委托:
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);
但这通常不是一个好主意,因为您正在失去事件的私有范围 - 对于事件,一个人只能 add\remove 委托,对于原始委托,任何人都可以做任何事情,例如调用或设置为 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);
在我看来,在你的场景中丑陋、不安全且不值得。
我目前正在构建一个自定义任务管理器,我想知道是否可以告诉任务管理器侦听特定事件(下面的 OnSomething
),然后在任务引发该事件。但是,在精神上我看不出如何监听一个在基础 class 级别不存在的事件。例如,我有一个基础 class,其中包含有关名为 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和设置。
您当然可以将事件更改为“原始”委托:
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);
但这通常不是一个好主意,因为您正在失去事件的私有范围 - 对于事件,一个人只能 add\remove 委托,对于原始委托,任何人都可以做任何事情,例如调用或设置为 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);
在我看来,在你的场景中丑陋、不安全且不值得。