根据规范监听事件并调用回调?

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);

在我看来,在你的场景中丑陋、不安全且不值得。