c# - 如何禁用从一种类型的多个实例订阅事件并只允许一个?

标签 c# .net winforms events delegates

我的 Windows 窗体应用程序有一个主窗体(派生自基本 Form)。其他可以在那里打开的模式窗体派生 self 的类ManagedForm,它也派生自Form
我还有一个静态通知程序服务,它会触发一些这样的事件:

    public static class NotifierService
    {
        public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);

        private static readonly object Locker = new object();
        private static NotifierServiceEventHandler _notifierServiceEventHandler;

        #region Events

        public static event NotifierServiceEventHandler OnOk
        {
            add
            {
                lock (Locker)
                {
                    _notifierServiceEventHandler += value;

                    if (
                        _notifierServiceEventHandler.GetInvocationList()
                                                    .Count(
                                                        _ =>
                                                        _.Method.DeclaringType != null &&
                                                        value.Method.DeclaringType != null &&
                                                        _.Method.DeclaringType == value.Method.DeclaringType) <= 1)
                        return;

                    _notifierServiceEventHandler -= value;
                }
            }
            remove
            {
                lock (Locker)
                {
                    _notifierServiceEventHandler -= value;
                }
            }
        }

        // and many more events similar to previous...

        #endregion

        #region Event firing methods

        public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
        {
            NotifierServiceEventHandler handler;

            lock (Locker)
            {
                handler = _notifierServiceEventHandler;
            }

            if (handler == null) return;

            handler(typeof (NotifierService),
                    new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
        }

        #endregion
    }

所以在代码的某些地方,这些事件可以像这样触发:

NotifierService.NotifyExclamation("Fail!");

在主窗体中有用于通知目的的 StatusStrip 控件,并且由于主窗体订阅了这些事件——它们的消息将显示在状态条中。
但是!,正如我之前所说,用户可以打开其他表单,这些表单可以生成其他表单等等......(它们派生自一个类 ManagedForm ,该类将订阅 NotifierService 一旦被创建)。
在这些表单中,还有另一种逻辑如何通知用户——他们需要显示带有消息的 MessageBoxes。如您所见,我在事件访问器中添加了一些魔法,只允许任何类型的一个订阅者,因为没有这个,所有打开的表单都会生成它们自己的 MessageBoxes。但是,当一个子级 ManagedForm 生成了另一个子级并且第二个子级已关闭时,将不会显示 MessageBoxes。
我应该实现什么魔法来允许仅从第一个 ManagedForm 订阅?非常感谢任何想法。

编辑:建议的想法不能解决这个问题。我试图将事件更改为此:

private static readonly object Locker = new object();

private static EventHandler<NotifierServiceEventArgs> _myEvent;

public static event EventHandler<NotifierServiceEventArgs> OnOk
{
    add
    {
        if (_myEvent == null || _myEvent.GetInvocationList().All(_ => _.Method.DeclaringType != value.Method.DeclaringType))
        {
            _myEvent += value;
        }
    }
    remove
    {
        _myEvent -= value;
    }
}

然后我打开了一个模态子窗体并创建了一个事件已被 NotifierService 触发的情况。已生成并显示了一个 MessageBox(没关系)。之后,我从一开始就打开了另一个模式窗体,并创建了另一种情况,在这种情况下,另一个事件被触发了。已生成并显示一个 MessageBox(这也可以)。现在我正在关闭第二个表单并创建一个需要触发事件的情况。没有显示 MessageBoxes(但在主窗体的状态条中事件消息已正确显示,因此与我的第一个实现相比没有任何改变)。
我应该更改 remove 子句中的内容吗?我不需要只有一个订阅者应该是,我需要每个订阅者应该是不同的类型。对不起,如果英语不好。

最佳答案

您尝试解决问题的方式在设计上根本就是错误的。您的服务类定义了一个在某些情况下将被触发的事件。一些客户订阅该事件,以这种方式请求在事件发生时得到通知。这只是实现 Observer pattern 的 .NET 方式。 ,因此您的服务(作为主题或可观察对象)不应在订阅或通知部分应用任何逻辑,从而破坏该模式的全部目的。 Hans Passant 已经指出了您设计中的一些缺陷,但即使是他的解决方案也不完美,因为查看事件签名时,完全不清楚应该只注册表单实例方法 - 可以尝试使用静态方法、匿名 lambda/方法、一些类方法等。

因此,IMO 以下是您的一些可行选择。

(A) 保留您的 NotificationService 事件,但从订阅和通知部分移除任何“魔法”(简而言之,使用定义和触发事件的常规方式) 并将所需的逻辑放入您的订阅者中:

public static class NotifierService
{
    public delegate void NotifierServiceEventHandler(object sender, NotifierServiceEventArgs e);
    public static event NotifierServiceEventHandler OnOk;
    public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
    {
        var handler = OnOk;
        if (handler != null)
            handler(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
    }
}

假设只有事件表单应该处理通知,MainFormManagedForm 中的现有处理程序将在它们的方法主体中使用类似这样的东西

if (this != ActiveForm) return;
// do the processing

你甚至可以像这样创建一个基本表单

class NotifiedForm : Form
{
    protected override void OnActivated(EventArgs e)
    {
        base.OnActivated(e);
        NotifierService.OnOk += OnNotifyOK;
        // similar for other events
    }
    protected override void OnDeactivate(EventArgs e)
    {
        base.OnDeactivate(e);
        NotifierService.OnOk -= OnNotifyOK;
        // similar for other events
    }
    protected virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
    // similar for other events
}

并让您的 MainFormManagedForm(以及任何其他需要的)继承自它并覆盖 OnNotifyXXX 方法并应用它们的逻辑.

总而言之,这种方法将使您的服务保持抽象,并将决定权留给服务的客户

(B) 如果您的服务的唯一目的是充当专门针对您的表单的通​​知协调员,那么您可以删除事件以及订阅/取消订阅部分(因为 Application. OpenFormsForm.ActiveForm 已经提供了所需的足够信息)并处理您服务中的逻辑。为此,您需要某种基本接口(interface)或表单,最简单的方法是通过创建这样的基本表单类来使用与选项 (A) 中可选内容类似的方法

class NotifiedForm : Form
{
    public virtual void OnNotifyOK(object sender, NotifierServiceEventArgs e) { }
    // similar for other notifications
}

并让您的 MainFormManagedForm 和其他需要的继承自它。请注意,这里没有逻辑(检查 ActiveForm 等),因为现在这是调用者的责任。那么服务可能是这样的:

public static class NotifierService
{
    public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
    {
        var target = Form.ActiveForm as NotifiedForm;
        if (target != null)
            target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
    }
    // similar for other notifications
}

如果逻辑是只通知事件表单。

或者

public static class NotifierService
{
    public static void NotifyOk(string fullMessage = "Ok.", string shortMessage = null)
    {
        // Could also be a forward for, forach etc.
        for (int i = Application.OpenForms.Count - 1; i >= 0; i--)
        {
            var target = Application.OpenForms[i] as NotifiedForm;
            if (target != null /* && someOtherCritaria(target) */)
            {
                target.OnNotifyOK(typeof(NotifierService), new NotifierServiceEventArgs(StatusType.Ok, fullMessage, shortMessage ?? fullMessage));
                // Could also continue
                break;
            }
        }
    }
    // similar for other notifications
}

如果需要一些其他逻辑(我对此表示怀疑)。

希望对您有所帮助。无论如何,选项 (A) 更灵活,允许更多的使用场景,但如果使用场景是设计固定的,那么选项 (B) 更好,因为它对客户端的要求更少(因此更不容易出错)并在一个地方提供集中的应用程序逻辑。

关于c# - 如何禁用从一种类型的多个实例订阅事件并只允许一个?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31600922/

相关文章:

c# - 静态一次性元素

c# - 重复播放列表中的单首歌曲

c# - 如何更新 Unity GameObject 以沿样条曲线移动?

c# - 复制模板对象以从中创建新模板对象

c# - 从 C# 程序中反序列化 JSON 时,我是否需要使用 JavaScriptSerializer 以外的任何东西?

winforms - 从基本表单访问连接字符串会导致设计器错误

c# - 以编程方式添加到 DataGridView 单元格的 ComboBox 在单元格单击时不展开

c# - 更改exe的开始菜单图标

c# - 将值分配给通过引用传递的列表

c# - Sql 命令不起作用