【问题标题】:why explicitly implementing an event?为什么要明确实施事件?
【发布时间】:2021-03-14 16:00:44
【问题描述】:

我正在阅读 Jeffrey Richter 通过 C# 编写的 CLR,其中说:

public event EventHandler<NewMailEventArgs> NewMail;

当 C# 编译器编译上述行时,它会将这一行源代码翻译成 以下三个构造:

private EventHandler<NewMailEventArgs> NewMail = null;

// 2. A PUBLIC add_Xxx method (where Xxx is the Event name)
public void add_NewMail(EventHandler<NewMailEventArgs> value) {
  ... // use Delegate.Combine internally
}

// 3. A PUBLIC remove_Xxx method (where Xxx is the Event name) allows methods to unregister interest in the event.
public void remove_NewMail(EventHandler<NewMailEventArgs> value) {
  ... // use Delegate.Remove internally
}

作者说:

System.Windows.Forms.Control 类型定义了大约 70 个事件。如果 Control 类型通过允许编译器隐式生成 add 和 remove 访问器方法和委托字段来实现事件,那么每个 Control 对象将有 70 个委托字段,仅用于事件!因为大多数程序员只关心少数几个事件,所以从 Control 派生类型创建的每个对象都会浪费大量内存。为了有效地存储事件委托,每个公开事件的对象都将维护一个集合(通常是一个字典),其中某种事件标识符作为键,一个委托列表作为值。

例如,我们应该在一个类型中显式地实现一个事件:

public sealed class EventKey { }

public sealed class EventSet {
   private readonly Dictionary<EventKey, Delegate> m_events = new Dictionary<EventKey, Delegate>();

   // Adds an EventKey -> Delegate mapping if it doesn't exist or combines a delegate to an existing EventKey
   public void Add(EventKey eventKey, Delegate handler) {
     ...
   }

   // Removes a delegate from an EventKey (if it exists) and
   // removes the EventKey -> Delegate mapping if the last delegate is removed
   public void Remove(EventKey eventKey, Delegate handler) {
     ...
   }

   // Raises the event for the indicated EventKey
   public void Raise(EventKey eventKey, Object sender, EventArgs e) {
      ... // use Delegate.DynamicInvoke internally
   }
}

public class TypeWithLotsOfEvents {
   private readonly EventSet m_eventSet = new EventSet();

   protected static readonly EventKey s_fooEventKey = new EventKey();

   public event EventHandler<FooEventArgs> Foo {
      add { m_eventSet.Add(s_fooEventKey, value); }
      remove { m_eventSet.Remove(s_fooEventKey, value); }
   }
   ...
}

我不明白为什么这种方法效率更高,它仍然需要声明它包含的每个事件,并且对于 TypeWithLotsOfEvents 的派生类型,子实例将包含所有父代的委托字段,所以你可以保存什么?以包含 70 个事件的 windows 窗体控件类型为例,任何派生的控件类型也必须包含 70 个事件,因为继承层次结构

【问题讨论】:

  • I don't why this approach is more efficient 大概是因为没有 70 个字段,就像它说的那样。
  • 哪本书?这听起来像是过早的优化。
  • 我将引用的段落解释为 Control 类为您提供了该集合。我不认为它建议你自己实现这样的集合。
  • the child instances will contain all the parent's delegate fields, so nothing you can save?你是怎么得出这个结论的?
  • 我好像记得你以前犯过这种误解:方法、属性和事件不会仅仅因为存在就耗尽每个对象的内存,它们每个类只使用一次内存。只有字段会增加每个对象的内存使用量,因此仅将已注册的事件存储在字典中会更有效

标签: c# .net


【解决方案1】:

这里要注意的重要一点是,这...

public event EventHandler<FooEventArgs> Foo {
  add { m_eventSet.Add(s_fooEventKey, value); }
  remove { m_eventSet.Remove(s_fooEventKey, value); }
}

不生成任何字段。

只生成两种方法:

private void add_Foo(EventHandler<FooEventArgs> value) {
    m_eventSet.Add(s_fooEventKey, value); 
}

private void remove_Foo(EventHandler<FooEventArgs> value) {
    m_eventSet.Remove(s_fooEventKey, value);
}

这与自动实现的属性如何生成支持字段以及 getter 和 setter 方法非常相似,而非自动实现的属性仅生成 getter 和 setter 方法。

请注意,您仍将拥有字段 s_fooEventKey,但该字段是静态的,因此只有一个实例,而不是每个 TypeWithLotsOfEvents 实例的字段实例。所以我们都很好。

尝试使用反射打印出Control 的私有字段。您会看到它没有与其事件对应的字段。另一方面,如果您编写自己的类而不使用“事件字典”方法,并尝试打印出它的字段,您将看到您声明的每个事件都有一个字段。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-06
    • 2022-01-15
    • 2011-05-06
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    • 1970-01-01
    相关资源
    最近更新 更多