C# 事件/订阅 .... 收听未引用的项目
我正在开发一个尝试使用观察者模式的应用程序。 基本上我有一个基本表单,可以从中加载各种组件(表单)。
基本表单引用每个组件,并且某些组件相互引用。
如果我希望其中一个组件侦听基本表单(可能来自菜单等)引发的事件,那么如果不需要在组件中添加对基本表单的引用,我似乎无法实现这一目标。 这会导致“循环引用”。
是否可以监听/订阅未引用的项目中的事件?
I am developing an application trying to employ the Observer pattern. Basically I have a base form from which various components (forms) can be loaded.
The base form references each of the components and some of the components reference each other.
If I want one of the components to listen for events raised by the base form (perhaps from a menu etc) I can't seem to achieve this without needing to add a reference to the base form in the component. This causes a "circular reference".
Is it possible to listen/subscribe to events in projects which are not referenced?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
您可以通过多种方式解决这个问题。 一种简单的方法是拥有一个了解基本表单和各种组件的特殊类。 该类负责创建表单和组件。 由于它了解它们,因此它可以将事件处理程序附加到适当的事件。 它本质上只是将组件上的事件处理程序方法“插入”基本表单上的事件。
另一种方法是定义一个带有事件的接口,该接口将由主窗体实现。 可以在组件的构造函数中向组件传递此接口的实例。 然后他们可以附加到事件处理程序。 因此,组件只知道接口,而不知道基本形式。 这是“依赖抽象,而不是实现”原则的应用。 在这种情况下,基本形式将实现接口并了解组件,并在构造组件时将其自身传递给它们。 因此,依赖性是单向的。
然而,最终的解决方案是使用依赖注入容器,例如 StructureMap。 您将有一个配置方法,它将基本表单类注册为接口的默认实现者,以及各种组件类。 然后,StructureMap 可以根据需要创建类的实例,自动将接口注入到构造函数中。
You can solve this in various ways. One simple way is to have a special class which knows about the base form and the various components. This class is responsible for creating the form and the components. Since it knows about them, it can attach event handlers to the appropriate events. It essentially just "plugs in" event handler methods on the components to the events on the base form.
Another way is to define an interface with events which will be implemented by the main form. The components can be passed an instance of this interface in their constructors. They can then attach to the event handlers. Thus the components know about the interface only, not the base form. This is an application of the "depend on abstractions, not implementations" principle. The base form, in this case, would implement the interface and have knowledge of the components, passing itself to them when they are constructed. Thus the dependency is one-way.
The ultimate solution, however, is to use a dependency injection container such as StructureMap. You'd have a configuration method which registers the base form class as the default implementor of the interface, and the various component classes. StructureMap can then create instances of classes as required, injecting the interface into the constructors automatically.
Microsoft 有一个正在开发中的“托管可扩展性框架 (MEF)”,可能适合您。 从 MEF 概述来看:
MEF 的概述和下载位于 http:// www.codeplex.com/MEF/Wiki/View.aspx?title=概述&referringTitle=主页
Microsoft has a "Managed Extensibility Framework (MEF)" in development that might work for you. From the MEF overview:
The overview and downloads for MEF are at http://www.codeplex.com/MEF/Wiki/View.aspx?title=Overview&referringTitle=Home
您的框架应该定义用于连接事物的接口(请注意,事件可以在接口中定义,因此您可以将事件提升到接口)。 迈克·斯科特(Mike Scott)的“依赖抽象,而不是实现”的建议是正确的,但我在这里会更强一点 - 你应该按契约编程并使用分层设计 。
或者,您可以使用 INotifyPropertyChanged 等接口,它提供一个可用于通过反射检索信息的字符串,但这是一种脆弱的工作方式,应该是最后的手段。
Your framework should define the interfaces that are used to wire things up (note that events can be defined in interfaces, so you can promote the events to the interface). Mike Scott's advice of "depend on abstractions, not implementations" is spot on, but I'd be a little stronger here - you should program by contract and use a layered design.
Alternatively, you can use interfaces like INotifyPropertyChanged which provide a string that can be used to retrieve information via reflection, but that is such a fragile way to work and should be a last resort.
您可以采用 EventBroker 模式,如 Microsoft 的模式和实践中所示图书馆。
然而,我自己不确定这是否是一种很好的模式,就我个人而言,我更喜欢创建对象不互相引用的体系结构(始终是父子场景)并处理依赖关系问题而不是忽略它们。
You could go for the EventBroker pattern as seen in Microsoft's Patterns and Practices Library.
However I'm not sure myself if that's so good a pattern, personally I prefer to create architecture where objects don't reference one another (always a parent->child scenario) and deal with the dependency problems instead of ignoring them.
您是否担心循环引用或循环依赖? 使用观察者模式时,很难避免循环引用(运行时问题)。 循环依赖(设计时问题)总是可以消除的。
并使用类似以下内容向事件注册自己:
C# 中观察者模式的典型用法是观察者拥有对发布者对象的引用, 发送(并存储)对观察者事件处理程序的引用。 因此,除非您要立即删除对publisherObject 的引用,否则您将陷入循环引用。
当您希望垃圾收集器清理未使用的对象时,这通常只是一个问题。 publisherObject 内部对观察者事件处理程序的“隐藏”引用足以防止观察者被垃圾收集。 这有时被称为失效听众问题。 最简单的方法是将事件取消订阅放入观察者的 Dispose() 方法中(并记住在摆脱观察者时调用它)。
您可以解决这个问题,但它通常会增加相当多的复杂性,这在小型应用程序中可能是不值得的。 之前的发帖者已经建议了 EventBroker、MEF 和依赖注入路由。
如果您更关心循环依赖关系,那么最简单的答案是严格的父子层次结构,如 Quarrelsome 建议的那样。 父级(观察者)始终了解其子级,因此可以在需要时直接调用它们的属性和方法。 孩子们(出版商)不应该对他们的父母一无所知。 它们纯粹通过事件和函数返回值向上通信。 然后,子级之间的通信通过公共父级进行路由(向上的事件,向下的方法调用)。
请注意,由于事件机制的工作方式,您仍然会遇到循环引用(因此在处理时要小心),但您没有循环依赖项。 子组件甚至可以位于完全不同的组件中,该组件没有设计时引用包含父组件的组件。
请注意,对于父项和子项的定义,您可以灵活一些。 仅仅因为您的主表单创建了子表单并不意味着您必须在该方向上定义父子通信。 典型的插件风格架构可能具有创建插件实例的主要形式。 但是一旦创建,通信可能会将插件视为父/观察者,将主表单视为发布者/事件源。
Are you concerned about circular references or circular dependencies? Circular references (a run time issue) are very difficult to avoid when using the observer pattern. Circular dependencies (a design time issue) can always be got rid of.
Typical usage of the observer pattern in c# is for the observer have a reference to a publisher object and to register itself with the event using something like:
So the observer already has a reference to the publisherObject and what happens in the background is that publisherObject gets sent (and stores) a reference to the observer's event handler. So unless you are going to drop the reference to publisherObject immediately, you're stuck with a circular reference.
This is normally only a problem when you want the garbage collector to tidy up unused objects. The "hidden" reference to the observer's event handler inside publisherObject is enough to prevent the observer being garbage collected. This is sometimes referred to as the lapsed listener problem. Easiest way round it is to put event unsubscriptions in the observer's Dispose() method (and remember to call it when you get rid of the observer).
You can get round this, but it generally adds quite a lot of complexity that may not be warranted in a smallish app. Previous posters have already suggested the EventBroker, MEF and dependency injection routes.
If you're more concerned about circular dependencies, then the simplest answer is a strict parent-child hierarchy as Quarrelsome suggested. The parent (observer) always knows about its children so can call properties and methods on them directly if it needs to. The children (publishers) should know nothing about their parent. They communicate upwards purely via events and function return values. Communication between children is then routed via a common parent (events on the way up, method calls on the way down).
Note that you've still got circular references due to the way the event mechanism works (so be careful about disposing) but you don't have circular dependencies. The child can even be in completely different assembly that doesn't have a design time reference the one containing the parent.
Note that you can be a bit flexible about what you consider the parent and what the child. Just because your main form creates a sub form doesn't mean you have to define the parent-child communications in that direction. A typical plug-in style architecture may have the main form creating instances of the plugins. But once created, the communications will probably treat the plugin as the parent/observer and the main form as the publisher/event source.
只需创建一个包含事件类的项目:
然后从基本表单项目和组件项目引用该项目。 让基础表单创建事件类的实例并将其提供给他加载的所有组件:
Just create a project with a class for the Event:
Then reference this project from both, the baseform project and the components project. Let the baseform create an instance of the eventclass and give it to all components he loads: