C#::何时使用事件或从事件处理接口派生的对象集合?

我认为这是一个简单的“问题”,我已经找到了几个解决方案,但我不知道哪种方式可以用于C#中的最佳实践。

在应用程序的生命周期中,我有一个主对象(比如一个单例)。 这个“MasterClass”创建了一堆新类型的对象,每次调用MasterClass.Instance.CreateSlaveObject时都会说“SlaveClass”。

此MasterClass还监视一些其他对象的状态更改,当发生这种情况时,通知它创建的SlaveClass对象的更改。 看起来很简单。

因为我来自原生的C ++世界,所以我首先使用它来创建一个界面

Interface IChangeEventListener { void ChangeHappened(); } 

我从中派生出“SlaveClass”。 然后在我的“MasterClass”中,我有:

 ... IList slaveList; ... CreateSlaveObject { ... slaveList.Add(slave); } ... ChangeHappened() { ... foreach(var slave in slaveList) { slave.ChangeHappened(); } } 

这很有效。 但是我一直在想,如果有另一种(更好的)方法可以做到这一点。 所以我对这个主题进行了更多研究,并看到了C#事件。

因此,我不是在MasterClass中维护一个奴隶集合,而是将MasterClass注入SlaveClass的ctor(或通过一个属性),让SlaveClass对象将它的ChangeHappened作为事件处理程序添加。 这将说明:

  ...Master... public delegate void ChangeHappenedDelegate(object sender, NewsInfoArgs args); public event NewUpdateDelegate ChangeHappenedEvent; .... public SlaveClass (MasterClass publisher) //inject publisher service { publisher.ChangeHappenedEvent += ChangeHappened; } 

但这似乎是Slave和Master之间不必要的耦合,但我喜欢所提供的内置事件通知机制的优雅。

那么我应该保留当前的代码,还是转向基于事件的方法(使用发布者注入)? 为什么?

或者如果你可以提出一个我可能错过的替代解决方案,我也会很感激。

好吧,在我看来,像你所展示的事件和界面是同一枚硬币的两面(至少在你描述它的背景下),但它们实际上是两面

我对事件的看法是“我需要订阅你的活动,因为我需要你告诉我你什么时候发生的事情”。

接口方式是“我需要在你身上调用一种方法来通知你发生了一些事情”。

它听起来可能是一样的,但它在说话的人不同,在这两种情况下,它都是你正在谈论的“大师class”,这就完全不同了。

请注意,如果您的slave类有一个适用于在master类中发生某些事情时调用的方法,那么您不需要包含代码来挂钩它,您可以在您的类中轻松执行此操作CreateSlaveClass方法:

 SlaveClass sc = new SlaveClass(); ChangeHappenedEvent += sc.ChangeHappened; return sc; 

这基本上将使用事件系统,但让MasterClass代码完成事件的所有连接。

SlaveClass对象是否与单例类一样长寿? 如果没有,那么你需要在它们变得陈旧/不再需要的情况下处理它,如上面的情况(基本上在你和我的两个中),你在MasterClass中持有对那些对象的引用,因此它们除非您强行删除这些事件或取消注册接口,否则永远不会有资格进行垃圾回收。


为了解决SlaveClass与MasterClass不同的问题,你将遇到同样的耦合问题,正如你在评论中所指出的那样。

“处理”(注意引号)的一种方法可能是没有真正直接链接到SlaveClass对象上的正确方法,而是创建一个内部将调用此方法的包装器对象。 这样做的好处是包装器对象可以在内部使用WeakReference对象,这样一旦你的SlaveClass对象有资格进行垃圾收集,它就可能被收集了,然后当你下次尝试在它上面调用正确的方法时,你会注意到这一点,因此你必须清理。

例如,像这样(在这里我输入没有Visual Studio intellisense和编译器的好处,请采用此代码的含义,而不是语法(错误)。)

 public class WrapperClass { private WeakReference _Slave; public WrapperClass(SlaveClass slave) { _Slave = new WeakReference(slave); } public WrapperClass.ChangeHappened() { Object o = _Slave.Target; if (o != null) ((SlaveClass)o).ChangeHappened(); else MasterClass.ChangeHappenedEvent -= ChangeHappened; } } 

在你的MasterClass中,你会这样做:

 SlaveClass sc = new SlaveClass(); WrapperClass wc = new WrapperClass(sc); ChangeHappenedEvent += wc.ChangeHappened; return sc; 

收集SlaveClass对象后,从MasterClass到事件处理程序的下一次调用(但不会早于该事件)通知它们更改,所有那些不再有对象的包装器将被删除。

我认为这只是个人偏好的问题……我个人喜欢使用事件,因为它更符合.NET“哲学”。

在您的情况下,如果MasterClass是单例,则不需要将其传递给SlaveClass的构造函数,因为可以使用singleton属性(或方法)检索它:

 public SlaveClass () { MasterClass.Instance.ChangeHappenedEvent += ChangeHappened; } 

由于您似乎拥有MasterClass的单​​例实例,为什么不订阅MasterClass.Instance.ChangeHappenedEvent? 它仍然是一个紧密耦合,但相对整洁。

虽然事件是暴露公共订阅/取消订阅function的常规范例,但在许多情况下,当订阅/取消订阅function不需要公开时,它们不是最佳范例。 在这种情况下,唯一允许订阅/取消订阅事件的是您自己创建的事件,因此不需要公共订阅/取消订阅方法。 此外,主人对奴隶的了解远远超过了典型的事件发布者对订阅者的了解。 因此,我赞成让master明确处理订阅的连接/断开连接。

我可能会添加的一个function,即通过主服务器和从服务器之间的耦合使某些function变得更容易,如果所有外部引用都被放弃,将允许对从服务器进行垃圾收集。 这样做的最简单方法可能是让主人将’WeakReference’列表保留给奴隶。 当需要通知奴隶发生了某些事情时,请通过列表,解除引用任何仍然存在的WeakReference,并通知奴隶。 如果自上次扫描后添加了列表中超过一半的项目且列表包含至少250个左右的项目,则将所有实时引用复制到新列表并替换旧列表。

另一种避免必须解除引用WeakReference对象的方法是让公共“slave”对象成为私有对象的包装器,让公共对象覆盖Finalize,让私有对象知道没有外部引用它存在。 这将需要为对象的公共访问添加额外级别的强间接,但是它将避免创建 – 甚至是暂时 – 对被放弃对象的强引用。