简体   繁体   English

如何使用显式接口事件?

[英]How do I work with explicit interface events?

So I made a few interfaces like this: 所以我做了一些像这样的接口:

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

    event EventHandler PreDrawActionChanged;
    event EventHandler PostDrawActionChanged;
}

Any classes that implement this (or several) of these interfaces got kinda cluttered, so I thought it would make sense to use explicit interface implementation to hide the uncommon events and properties away. 实现这些(或几个)这些接口的任何类都变得混乱,所以我认为使用显式接口实现隐藏不常见的事件和属性是有意义的。 But, doing so, I got a compiler error: 但是,这样做,我得到一个编译器错误:

An explicit interface implementation of an event must use event accessor syntax 事件的显式接口实现必须使用事件访问器语法

And Googling it lead me to this rather helpful blog post : 谷歌搜索引导我到这个相当有用的博客文章

This hints at one of the primary reasons for writing your own add and remove accessors: to provide your own underlying data store. 这暗示了编写自己的添加和删除访问器的主要原因之一:提供您自己的底层数据存储。 One reason you might want to do this is if you have lots of exposed events on your class, but in such a way that only a few are typically in use on an instance at any point in time. 您可能想要这样做的一个原因是,如果您的类上有大量暴露事件,但这样的方式通常在任何时间点只有少数几个在实例上使用。 In such a scenario, there can be significant memory overhead associated with maintaining a delegate field for each event. 在这种情况下,可能存在与维护每个事件的委托字段相关联的显着内存开销。

How exactly does this save on resources? 这究竟如何节省资源? It seems that the delegate invocation list for the event would be null, but how and when would it actually be instantiated if you're using your own custom handlers? 似乎事件的委托调用列表将为null,但是如果您使用自己的自定义处理程序,它实际上将如何以及何时实例化? Everything is hidden away! 一切都隐藏了!

The text in bold refers to a memory optimization which is extremely useful when you have many events, or many object instances, all with many events. 粗体文本是指内存优化,当您有许多事件或许多对象实例时,它们都非常有用。 The most basic support for creating events in C# is to use the event keyword. 在C#中创建事件的最基本支持是使用event关键字。 This keyword is syntactic sugar for the following generated code: 此关键字是以下生成代码的语法糖:

  • A field to contain a delegate. 包含委托的字段。 Delegates form linked lists. 代表形成链表。 This is the head of the list, and additions are inserted at the head. 这是列表的头部,并在头部插入添加项。
  • Event accessors, where the add method inserts into the linked list using the delegate field, and the remove method removes from the linked list. 事件访问器,其中add方法使用委托字段插入到链接列表中,remove方法从链接列表中删除。 The linked list addition and removal also has syntactic sugar hiding it, so you see only "+=" and "-=" to add to, or remove from, the delegate list. 链接列表的添加和删除也有隐藏它的语法糖,因此您只能看到“+ =”和“ - =”添加到委托列表或从委托列表中删除。

In this sense, the event keyword produces code similar to the generated code from C# auto-implemented properties. 从这个意义上说, event关键字产生的代码类似于C#自动实现属性中生成的代码。

The overhead comes in maintaining a separate field for each event. 开销是为每个事件维护一个单独的字段。 This is not necessary, just as it is not necessary to maintain a separate field for the data backing each property exposed by a class. 这不是必需的,因为没有必要为支持类暴露的每个属性的数据维护单独的字段。 We can virtualize both event fields and property fields. 我们可以虚拟化事件字段和属性字段。

How do we eliminate the overhead for events specifically? 我们如何具体消除事件的开销? We use this method in libraries such as VG.net, and Microsoft uses similar methods in their code: keep a collection of events in a single field. 我们在诸如VG.net之类的库中使用此方法,并且Microsoft在其代码中使用类似的方法:在单个字段中保留事件的集合。 In most cases, few instances have many event subscribers. 在大多数情况下,很少有实例有很多事件订阅者。 The simplest collection is a linked list of class instances. 最简单的集合是类实例的链接列表。 Each element in the collection consists of a class instance containing the following properties: 集合中的每个元素都包含一个包含以下属性的类实例:

  • An event identifier. 事件标识符。 There is one unique identifier per unique type of event. 每种独特类型的事件都有一个唯一标识符。 It is best to use something small, like a byte or integer, since you are unlikely to have millions of event types, even across a huge library. 最好使用小的东西,比如字节或整数,因为你不太可能有数百万种事件类型,即使是在一个巨大的库中也是如此。
  • A delegate. 代表。 The delegate can be weakly typed (Delegate). 代表可以是弱类型(委托)。

When you need to add an event handler for a subscriber, you look up the delegate in your collection, using the unique event type identifier. 当您需要为订阅者添加事件处理程序时,可以使用唯一的事件类型标识符在集合中查找委托。 The first time you look it up, the collection will not contain it. 第一次查找时,该集合将不包含它。 In the case of event handler additions, you will add an element to your collection, and within that element, add to the delegate stored there, using Delegate.Combine. 在添加事件处理程序的情况下,您将向集合中添加一个元素,并在该元素中,使用Delegate.Combine添加到存储在那里的委托。 To remove a handler, you use Delegate.Remove. 要删除处理程序,请使用Delegate.Remove。

Here is an example from real code in VG.net: 以下是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];
    }

We virtualized not only events, but properties as well. 我们不仅虚拟化了事件,还虚拟了属性。 For VG.net, all events are contained in one virtual property (EventProperty), and most public properties are also virtualized, although we bundle together property values that are most likely used together. 对于VG.net,所有事件都包含在一个虚拟属性(EventProperty)中,并且大多数公共属性也是虚拟化的,尽管我们将最可能一起使用的属性值捆绑在一起。 This enables us to provide many properties and events on all instances, while there is zero memory used by these properties or events per instance, unless: 这使我们能够在所有实例上提供许多属性和事件,而每个实例的这些属性或事件使用的内存为零,除非:

  • For properties, the property is set to a non-default value. 对于属性,该属性设置为非默认值。
  • For events, something subscribes to the event. 对于事件,某事订阅了该事件。

These types of optimization make VG.net efficient even when there are millions of graphical objects in memory, even if running on low-end hardware. 即使在低端硬件上运行,即使内存中有数百万个图形对象,这些类型的优化也使VG.net高效。

Ideally, we should have programming tools that do not force us to optimize data structures explicitly. 理想情况下,我们应该拥有不会强制我们明确优化数据结构的编程工具。 Specifying exactly how objects are laid out in memory is a burden better handled by a profiler or smart run-time system. 准确指定对象在内存中的布局方式是分析器或智能运行时系统更好地处理的负担。 We are still in the stone age in this respect, in every programming language I have ever worked in. 在这方面我们仍处于石器时代,在我曾经使用的每种编程语言中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM