首页 > 解决方案 > 为什么要明确实施事件?

问题描述

我正在通过 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 个事件,因为继承层次结构

标签: c#.net

解决方案


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

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. 您将看到它没有与其事件对应的字段。另一方面,如果您编写自己的类而不使用“事件字典”方法,并尝试打印出它的字段,您将看到您声明的每个事件都有一个字段。


推荐阅读