C#Events / Subscriptions ....收听未引用的项目

时间:2008-12-14 22:19:23

标签: c# events delegates subscription

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

基本表单引用了每个组件,并且一些组件相互引用。

如果我希望其中一个组件监听基本表单引发的事件(可能来自菜单等),我似乎无法实现这一点,而无需在组件中添加对基本表单的引用。这会导致“循环引用”。

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

6 个答案:

答案 0 :(得分:1)

您可以通过各种方式解决此问题。一种简单的方法是有一个特殊的类,它知道基本形式和各种组件。该类负责创建表单和组件。由于它知道它们,它可以将事件处理程序附加到适当的事件。它基本上只是将组件上的事件处理程序方法“插入”基础表单上的事件。

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

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

答案 1 :(得分:1)

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

  

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

MEF的概述和下载位于http://www.codeplex.com/MEF/Wiki/View.aspx?title=Overview&referringTitle=Home

答案 2 :(得分:1)

您的框架应该定义用于连接的接口(请注意,事件可以在接口中定义,因此您可以将事件提升到接口)。迈克·斯科特的“依赖于抽象,而不是实施”的建议是现实,但我在这里会更强一些 - 你应该按合同编程并使用分层设计

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

答案 3 :(得分:0)

您可以使用Microsoft's Patterns and Practices库中的EventBroker模式。

但是我不确定自己是否这么好的模式,我个人更喜欢创建对象不引用彼此的架构(总是父 - >子场景)并处理依赖问题而不是忽略它们。

答案 4 :(得分:0)

您是否关注循环引用或循环依赖?使用观察者模式时,很难避免循环引用(运行时问题)。循环依赖(设计时间问题)总是可以摆脱。

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

publisherObject.SomeEvent += MyEventHandler();

因此观察者已经有了对publisherObject的引用,后台发生的事情是publisherObject被发送(并存储)对观察者事件处理程序的引用。因此,除非您要立即删除对publisherObject的引用,否则您将无法使用循环引用。

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

你可以解决这个问题,但它通常会增加很多复杂性,这在一个小应用程序中可能是不合理的。以前的海报已经提出了EventBroker,MEF和依赖注入路线。

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

请注意,由于事件机制的工作方式,您仍然会获得循环引用(因此请小心处理)但您没有循环依赖项。孩子甚至可以处于完全不同的组件中,该组件没有设计时参考包含父母的那个。

请注意,您可以对您认为父母和孩子的内容有一点灵活性。仅仅因为您的主窗体创建子窗体并不意味着您必须在该方向上定义父子通信。典型的插件式架构可以具有创建插件实例的主表单。但是一旦创建,通信可能会将插件视为父/观察者,将主要形式视为发布者/事件源。

答案 5 :(得分:0)

只需创建一个包含事件类的项目:

public class BaseFormEventClass
{
    public EventHandler<EventArgs> BaseFormDidSomething;
}

然后从baseform项目和组件项目中引用此项目。让baseform创建一个eventclass的实例,并将它提供给他加载的所有组件:

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