C#活动/订阅。。。。倾听未被引用的项目

C#活动/订阅。。。。倾听未被引用的项目,c#,events,delegates,subscription,C#,Events,Delegates,Subscription,我正在开发一个应用程序,试图采用观察者模式。基本上我有一个基本表单,可以从中加载各种组件(表单) 基础表单引用每个组件,而一些组件相互引用 如果我想让其中一个组件侦听基窗体(可能是从菜单等)引发的事件,那么如果不需要在组件中添加对基窗体的引用,我似乎无法实现这一点。这会导致“循环引用” 是否可以侦听/订阅未引用项目中的事件?您可以选择库中所示的EventBroker模式 然而,我自己也不确定这是否是一种好的模式,我个人更喜欢创建对象之间不相互引用的体系结构(始终是父->子场景),并处理依赖性问题

我正在开发一个应用程序,试图采用观察者模式。基本上我有一个基本表单,可以从中加载各种组件(表单)

基础表单引用每个组件,而一些组件相互引用

如果我想让其中一个组件侦听基窗体(可能是从菜单等)引发的事件,那么如果不需要在组件中添加对基窗体的引用,我似乎无法实现这一点。这会导致“循环引用”


是否可以侦听/订阅未引用项目中的事件?

您可以选择库中所示的EventBroker模式


然而,我自己也不确定这是否是一种好的模式,我个人更喜欢创建对象之间不相互引用的体系结构(始终是父->子场景),并处理依赖性问题,而不是忽略它们。

您可以通过各种方式解决这一问题。一个简单的方法是使用一个特殊的类,它知道基本形式和各种组件。此类负责创建表单和组件。因为它知道它们,所以可以将事件处理程序附加到适当的事件。它本质上只是将组件上的事件处理程序方法“插入”到基窗体上的事件中

另一种方法是定义带有事件的接口,这些事件将由主窗体实现。组件可以在其构造函数中传递此接口的实例。然后,它们可以附加到事件处理程序。因此,组件只知道接口,而不知道基本形式。这是“依赖抽象,而不是实现”原则的应用。在这种情况下,基本表单将实现接口并了解组件,并在构建组件时将其自身传递给它们。因此,依赖关系是单向的

但是,最终的解决方案是使用依赖项注入容器,例如。您将拥有一个配置方法,该方法将基本表单类注册为接口的默认实现者以及各种组件类。然后StructureMap可以根据需要创建类的实例,将接口自动注入构造函数

微软正在开发一个“托管可扩展性框架(MEF)”,它可能适合您。从MEF概述中:

MEF为主机应用程序提供了一种标准的方法来公开自身和使用外部扩展。从本质上讲,扩展可以在不同的应用程序之间重用。但是,扩展仍然可以以特定于应用程序的方式实现。扩展本身可以相互依赖,MEF将确保它们以正确的顺序连接在一起(另一件您不必担心的事情)


MEF的概述和下载位于

您的框架应该定义用于连接的接口(请注意,事件可以在接口中定义,因此您可以将事件升级到接口)。Mike Scott提出的“依赖抽象,而不是实现”的建议很贴切,但我在这里要强调一点——你应该按合同编程,并使用分层设计


或者,您也可以使用像INotifyPropertyChanged这样的接口,它提供了一个字符串,可用于通过反射检索信息,但这是一种非常脆弱的工作方式,应该是最后的选择。

您是否担心循环引用或循环依赖?使用观察者模式时,循环引用(运行时问题)很难避免。循环依赖(设计时问题)总是可以消除的

c#中观察者模式的典型用法是,观察者拥有对发布者对象的引用,并使用以下内容向事件注册自身:

publisherObject.SomeEvent += MyEventHandler();
因此,观察者已经有了对publisherObject的引用,而在后台发生的事情是publisherObject被发送(并存储)对观察者事件处理程序的引用。因此,除非您要立即删除对publisherObject的引用,否则您只能使用循环引用

这通常只是当您希望垃圾收集器整理未使用的对象时的问题。publisherObject中对观察者事件处理程序的“隐藏”引用足以防止对观察者进行垃圾收集。这有时被称为失效侦听器问题。最简单的方法是将事件取消订阅放在观测者的Dispose()方法中(记住在摆脱观测者时调用它)

你可以绕开它,但它通常会增加相当多的复杂性,这在一个小型应用程序中可能是不必要的。以前的海报已经建议使用EventBroker、MEF和依赖注入路线

如果您更关心循环依赖,那么最简单的答案是严格的父子层次结构。父对象(观察者)总是知道它的子对象,因此如果需要,可以直接调用它们的属性和方法。孩子们(出版商)应该对他们的父母一无所知。它们纯粹通过事件和函数返回值向上通信。然后,子级之间的通信通过公共父级路由(向上的事件,向下的方法调用)

注意,由于事件机制的工作方式,您仍然有循环引用(因此小心处理),但您没有循环依赖项。子级甚至可以位于完全不同的程序集中,该程序集中没有包含父级的设计时引用

请注意你对父母和孩子的看法有点灵活。主窗体创建子窗体并不意味着您必须在该方向定义父子通信。典型的插件式拱门
public class BaseFormEventClass
{
    public EventHandler<EventArgs> BaseFormDidSomething;
}
public class MyComponent
{
    public MyComponent(BaseFormEventClass eventClass)
    {
        eventClass.BaseFormDidSomething += this.EventClass_BaseFormDidSometing;
    }
    // ...
}

public class BaseForm
{
    private BaseFormEventClass eventClass = new BaseFormEventClass();

    private void LoadComponents()
    {
        MyComponent component1 = new MyComponent(this.eventClass);
    }

    private void RaiseBaseFormDidSomething()
    {
        EventHandler<EventArgs> handler = eventClass.BaseFormDidSomething;
        if (handler != null) handler(this, EventArgs.Empty);
    }
}