如何使用显式接口事件?

时间:2014-10-17 15:14:00

标签: c# events interface delegates explicit-interface

所以我做了一些像这样的接口:

public interface IDrawActions : ISimpleDrawable
{
    Action<GameTime> PreDrawAction { get; set; }
    Action<GameTime> PostDrawAction { get; set; }

    event EventHandler PreDrawActionChanged;
    event EventHandler PostDrawActionChanged;
}

实现这些(或几个)这些接口的任何类都变得混乱,所以我认为使用显式接口实现隐藏不常见的事件和属性是有意义的。但是,这样做,我得到了一个编译器错误:

  

事件的显式接口实现必须使用事件访问器语法

谷歌搜索引导我this rather helpful blog post

  

这暗示了编写自己的添加和删除访问器的主要原因之一:提供自己的底层数据存储。您可能想要这样做的一个原因是,如果您的类上有大量暴露事件,但这样的方式通常在任何时间点只有少数几个在实例上使用。 在这种情况下,维护每个事件的委托字段可能会产生大量内存开销。

这究竟如何节省资源?似乎事件的委托调用列表将为null,但如果您使用自己的自定义处理程序,它实际上将如何以及何时实例化?一切都被隐藏了!

1 个答案:

答案 0 :(得分:1)

粗体文本是指内存优化,当您有许多事件或许多对象实例时,它们都非常有用。在C#中创建事件的最基本支持是使用事件关键字。此关键字是以下生成代码的语法糖:

  • 包含代理的字段。代表形成链表。这是列表的头部,并在头部添加了添加内容。
  • 事件访问器,其中add方法使用委托字段插入到链接列表中,remove方法从链接列表中删除。链接列表的添加和删除也有隐藏它的语法糖,因此您只能看到“+ =”和“ - =”来添加或删除委托列表。

从这个意义上说, event 关键字产生的代码类似于C#自动实现属性中生成的代码。

开销是为每个事件维护一个单独的字段。这不是必需的,因为没有必要为支持类暴露的每个属性的数据维护单独的字段。我们可以虚拟化事件字段和属性字段。

我们如何具体消除事件的开销?我们在诸如VG.net之类的库中使用此方法,并且Microsoft在其代码中使用类似的方法:在单个字段中保留事件的集合。在大多数情况下,很少有实例有很多事件订阅者。最简单的集合是类实例的链接列表。集合中的每个元素都包含一个包含以下属性的类实例:

  • 事件标识符。每种独特类型的事件都有一个唯一标识符。最好使用小的东西,比如字节或整数,因为你不太可能有数百万种事件类型,即使是在一个巨大的库中也是如此。
  • 代表。代表可以是弱类型(代表)。

当您需要为订阅者添加事件处理程序时,可以使用唯一的事件类型标识符在集合中查找委托。第一次查找时,该集合将不包含它。在添加事件处理程序的情况下,您将向集合中添加一个元素,并在该元素中,使用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高效。

理想情况下,我们应该拥有不会强制我们明确优化数据结构的编程工具。准确指定对象在内存中的布局方式是分析器或智能运行时系统更好地处理的负担。在这方面我们仍然处于石器时代,在我曾经工作的每种编程语言中。