粗体文本指的是内存优化,当您有许多事件或许多对象实例时,它非常有用,所有事件都包含许多事件。在 C# 中创建事件的最基本支持是使用event关键字。此关键字是以下生成代码的语法糖:
- 包含委托的字段。代表形成链表。这是列表的头部,添加内容插入到头部。
- 事件访问器,其中 add 方法使用委托字段插入到链表中,而 remove 方法从链表中删除。链表添加和删除也有隐藏它的语法糖,所以你只能看到“+=”和“-=”来添加或删除委托列表。
从这个意义上说,event关键字生成的代码类似于从 C# 自动实现的属性生成的代码。
开销来自为每个事件维护一个单独的字段。这不是必需的,就像没有必要为支持类公开的每个属性的数据维护一个单独的字段一样。我们可以虚拟化事件字段和属性字段。
我们如何专门消除事件的开销?我们在 VG.net 等库中使用此方法,Microsoft 在其代码中使用类似方法:将事件集合保存在单个字段中。在大多数情况下,很少有实例有很多事件订阅者。最简单的集合是类实例的链表。集合中的每个元素都包含一个包含以下属性的类实例:
- 事件标识符。每种唯一类型的事件都有一个唯一标识符。最好使用小的东西,比如字节或整数,因为即使在一个巨大的库中,也不可能拥有数百万个事件类型。
- 一个代表。委托可以是弱类型(Delegate)。
当您需要为订阅者添加事件处理程序时,您可以使用唯一的事件类型标识符在集合中查找委托。第一次查找时,该集合将不包含它。在添加事件处理程序的情况下,您将向集合中添加一个元素,并在该元素中,使用 Delegate.Combine 添加到存储在那里的委托。要删除处理程序,请使用 Delegate.Remove。
以下是 VG.net 中真实代码的示例:
private static readonly int MouseDownEvent = EventsProperty.CreateEventKey();
public event ElementMouseEventHandler MouseDown
{
add { AddHandler(MouseDownEvent, value); }
remove { RemoveHandler(MouseDownEvent, value); }
}
public virtual void OnMouseDown(ElementMouseEventArgs args)
{
ElementMouseEventHandler handler =
FindHandler(MouseDownEvent) as ElementMouseEventHandler;
if (handler != null)
handler(this, args);
}
internal void AddHandler(int key, Delegate value)
{
EventsProperty p = (EventsProperty)GetOrInsertProperty(EventsProperty.Key);
p.AddHandler(key, value);
}
internal void RemoveHandler(int key, Delegate value)
{
EventsProperty p = (EventsProperty)GetProperty(EventsProperty.Key);
if (p == null)
return;
p.RemoveHandler(key, value);
}
internal Delegate FindHandler(int key)
{
EventsProperty p = (EventsProperty)GetProperty(EventsProperty.Key);
if (p == null)
return null;
return p[key];
}
我们不仅虚拟化了事件,还虚拟化了属性。对于 VG.net,所有事件都包含在一个虚拟属性 (EventProperty) 中,并且大多数公共属性也是虚拟化的,尽管我们将最有可能一起使用的属性值捆绑在一起。这使我们能够在所有实例上提供许多属性和事件,而每个实例这些属性或事件使用的内存为零,除非:
- 对于属性,该属性设置为非默认值。
- 对于事件,某些东西订阅了该事件。
即使内存中有数百万个图形对象,即使在低端硬件上运行,这些类型的优化也使 VG.net 高效。
理想情况下,我们应该拥有不会强迫我们显式优化数据结构的编程工具。准确指定对象在内存中的布局方式是分析器或智能运行时系统更好地处理的负担。在这方面,我们还处于石器时代,在我曾经使用过的每一种编程语言中。