简体   繁体   English

C#中的虚方法或事件

[英]Virtual Methods or Events in C#

I'm currently writing a little C# library to simplify implementing little physical simulations/experiments. 我目前正在编写一个小的C#库来简化实现很少的物理模拟/实验。

The main component is a SimulationForm that runs a timer-loop internally and hides boilerplate-code from the user. 主要组件是SimulationForm ,它在内部运行定时器循环并隐藏用户的样板代码。 The experiment itself will just be defined through three methods: 实验本身将通过三种方法定义:

  1. Init() (Initialize everything) Init() (初始化所有内容)
  2. Render(Graphics g) (Render current simulation state) Render(Graphics g) (渲染当前模拟状态)
  3. Move(double dt) (Move experiment for dt seconds) Move(double dt) (移动实验dt秒)

I just wondered what is the better choice for letting the user implement these functions: 我只是想知道让用户实现这些功能的更好选择是什么:

1) Virtual methods to be overridden by the inheriting form 1)由继承表单覆盖的虚拟方法

protected virtual void Init() {} 
...

or 要么

2) Events 2)事件

public event EventHandler<MoveSimulationEventArgs> Move = ...
...

Edit : Note that the methods should not be abstract anyway. 编辑 :请注意,方法不应该是抽象的。 In fact, there are even more and none of them has to be implemented. 其实,还有更和他们都没有得到执行。 It's often convenient to leave them out since many simulations don't need them. 由于许多模拟不需要它们,因此将它们放在外面通常很方便。

The cool thing about this being a "normal form" is that you can write 关于这是一个“正常形式”的酷事是你可以写

partial class frmMyExperiment : SimulationForm {
}

and you're perfectly able to interact with the designer and all inherited controls and settings/properties. 并且您完全能够与设计器和所有继承的控件和设置/属性进行交互。 I don't want to lose this features by having a completely different approach. 我不想通过采用完全不同的方法来失去这些功能。

I prefer using virtual methods in this case. 在这种情况下,我更喜欢使用虚方法。

Also, if the methods are required in order to function, you can make your class an abstract class . 此外,如果需要方法才能运行,则可以将类设为抽象类 This is the best in terms of usability, since it causes the compiler to enforce the usage. 这在可用性方面是最好的,因为它会导致编译器强制使用。 If the user tries to use your class/Form without implementing the methods, the compiler will complain. 如果用户尝试使用您的类/表而不实现方法,编译器将会抱怨。

Events would be a bit inappropriate here, since this really isn't something where you want more than a single implementation (events allow multiple subscribers), and it, conceptually, is more of a functional of the object, and less a notification caused by the object. 事件在这里有点不合适,因为这不是你想要多于一个实现(事件允许多个订阅者)的东西,并且从概念上讲,它更像是对象的功能,而不是由物体。

Interestingly, I've had to make a similar choice in a design I worked on recently. 有趣的是,我不得不在最近工作的设计中做出类似的选择。 One approach is not strictly superior to another. 一种方法并不严格优于另一种方法。

In your example, given no additional information, I would probably choose virtual methods - particularly since my intuition leads me to believe you would use inheritance to model different types of experiments, and you don't need the ability to have multiple subscribers (as events allow). 在你的例子中,没有额外的信息,我可能会选择虚拟方法 - 特别是因为我的直觉让我相信你会使用继承来模拟不同类型的实验,而你不需要拥有多个订阅者的能力(作为事件)允许)。

Here are some of my general observations about choosing between these patterns: 以下是关于在这些模式之间进行选择的一些一般性观察:

Events are great: 活动很棒:

  1. when you don't need the caller to return any information, and when you want extensibility without requiring subclassing. 当您不需要调用者返回任何信息时,以及何时需要可扩展性而不需要子类化。
  2. if you want to allow the caller to have multiple subscribers that can listen and respond to the event. 如果您希望允许呼叫者拥有多个可以侦听和响应事件的订阅者。
  3. because they naturally arrange themselves into the template method pattern - which helps to avoid introducing the fragile base class problem . 因为它们自然地将自己安排到模板方法模式中 - 这有助于避免引入脆弱的基类问题

The biggest problem with events is that managing the lifetime of subscribers can get tricky, and you can introduce leaks and even functional defects when subscribers stay subscribed for longer than necessary. 事件的最大问题是管理订阅者的生命周期可能会变得棘手,并且当订阅者订阅的时间超过必要时间时,您可能会引入泄漏甚至功能缺陷。 The second biggest problem is that allowing multiple subscribers can create a confusing implementation where individual subscribers step on each other - or exhibit order dependencies. 第二个最大的问题是允许多个订阅者可以创建一个令人困惑的实现,其中各个订阅者互相踩踏 - 或表现出顺序依赖性。

Virtual methods work well: 虚拟方法运行良好:

  1. when you only want inheritors to be able to alter the behavior of a class. 当你只希望继承者能够改变一个类的行为时。
  2. when you need to return information from the call (which event's don't support easily) 当你需要从通话中返回信息时(哪个事件不容易支持)
  3. when you only want a single subscriber to a particular extension point 当您只需要一个订阅者到特定扩展点时
  4. when you want derivatives of derivatives to be able to override behavior. 当你想要衍生物的衍生物能够覆盖行为时。

The biggest problem with virtual methods is that you can easily introduce the fragile base class problem into your implementation. 虚方法的最大问题是您可以轻松地将脆弱的基类问题引入到您的实现中。 Virtual methods are essentially a contract with derived classes that you must document clearly so that inheritors can provide a meaningful implementation. 虚方法本质上是与派生类的契约,您必须清楚地记录它们,以便继承者可以提供有意义的实现。

The second biggest problem with virtual methods, is that it can introduce a deep or broad tree of inheritance just to customize how a behavior of a class is customized in a particular case. 虚方法的第二大问题是它可以引入深度或广泛的继承树,以便定制在特定情况下如何自定义类的行为。 This may be ok, but I generally try avoiding inheritance if there isn't a clear is-a relationship in the problem domain. 这可能没问题,但如果问题域中没有明确的is-a关系,我通常会尝试避免继承。

There is another solution you could consider using: the strategy pattern . 您可以考虑使用另一种解决方案: 策略模式 Have your class support assignment of an object (or delegate) to define how Render, Init, Move, etc should behave. 让您的类支持对象(或委托)的分配,以定义Render,Init,Move等应该如何表现。 Your base class can supply default implementations, but allow external consumers to alter the behavior. 您的基类可以提供默认实现,但允许外部使用者更改行为。 While similar to event, the advantage is that you can return value to the calling code and you can enforce only a single subscriber. 虽然与事件类似,但优点是您可以将值返回给调用代码,并且您只能强制执行一个订阅者。

A little help for deciding: 决定一点帮助:

Do you want to notify other (multiple) objects? 您想通知其他 (多个)对象吗? Then use events. 然后使用事件。

Do you want to make different implementations possible / use polymorphism? 你想让不同的实现成为可能/使用多态吗? Then use virtual / abstract methods. 然后使用虚拟/抽象方法。

In some cases, even combinidng both ways is a good solution. 在某些情况下,即使是两种方式组合也是一个很好的解决方案。

There's another option, borrowed from Functional programming: expose the extension points as properties of type Func or Action, and have your experiments provide delegates thusly. 从函数式编程中借用了另一个选项:将扩展点公开为Func或Action类型的属性,并让您的实验提供代理。

For example, in your simulation runner, you'd have: 例如,在您的模拟跑步者中,您将拥有:

public class SimulationForm
{
    public Action Init { get; set; }
    public Action<Graphics> Render { get; set; }
    public Action<double> Move { get; set; }

    //...
}

And in your simulation you'd have: 在您的模拟中,您将拥有:

public class Simulation1
{
    private SimulationForm form;

    public void Simulation1()
    {
        form = new SimulationForm();
        form.Init = Init;
        form.Render = Render;
        form.Move = Move;
    }

    private void Init()
    {
        // Do Init code here
    }

    private void Render(Graphics g)
    {
        // Do Rendering code here
    }

    private void Move(double dt)
    {
        // Do Move code here
    }
}

Edit : For a very silly example of exactly this technique that I used in some private-facing code (for a private play-around project), check out my delegate-powered-collection blog post . 编辑 :对于一个非常愚蠢的例子,我在一些面向私人的代码(用于私人游戏项目)中使用了这种技术,请查看我的delegate-powered-collection博客文章

neither. 都不是。

you should consume a class that implements an interface which defines the functions you require. 您应该使用一个实现定义所需函数的接口的类。

为什么界面不适合这个?

Use a virtual method to allow a specialization of the base class - the inner details. 使用虚方法允许基类的特化 - 内部细节。

Conceptually, you can use events to represent the 'output' of a class, apart from return methods. 从概念上讲,除了返回方法之外,您还可以使用事件来表示类的“输出”。

I like LBuskin's summary. 我喜欢LBuskin的总结。 With regards to the fragile base class problem, there is one case where I am thinking protected events may be a contender for best practice: 关于脆弱的基类问题,有一种情况我认为受保护的事件可能是最佳实践的竞争者:

  1. you have events truly intended for internal class use (as opposed to strategies perhaps like the OP's Render), such as Initializing or EnabledChanging, 你有真正用于内部类使用的事件(而不是像OP的Render那样的策略),例如Initializing或EnabledChanging,
  2. you have the potential for a large inheritance hierarchy (deep and/or broad), with multiple levels needing to act on the event ( multiple non-public consumers !) 你有可能有一个大的继承层次结构(深层和/或广泛),多个层次需要对事件采取行动( 多个非公共消费者 !)
  3. you do not want to rely on each derived class remembering to invoke base.OnSomethingHappened(). 你不想依赖每个派生类来记住调用base.OnSomethingHappened()。 (Avoid fragile base class problem.) (避免脆弱的基类问题。)
  4. order of handling the event does not matter (otherwise use a chain of base.OnSomethingHappened() to control the order.) 处理事件的顺序 无关紧要 (否则使用base.OnSomethingHappened()链来控制顺序。)
  5. the event applies to some kind of internal state change and would not be meaningful to an external class (otherwise use a public event ). 该事件适用于某种内部状态更改,对外部类没有意义(否则使用公共事件 )。 For example, a public Initializing event would typically be a cancellable event that occurs before any internal initializing occurs. 例如,公共Initializing事件通常是在发生任何内部初始化之前发生的可取消事件。 A protected Initializing event should not be exposed to the user of the class as a cancellable event because the class may already be partially initialized. 受保护的Initializing事件不应作为可取消事件向类的用户公开,因为该类可能已经部分初始化。

This avoids the need to document virtual protected methods with "derived classes should call base method" and hope that you or others who derive classes always read the documentation and follow it. 这避免了使用“派生类应该调用基本方法”来记录虚拟保护方法的需要,并希望您或其他派生类的人总是阅读文档并遵循它。 It also avoids uncertainty in wondering whether the call to the base method should be called at the beginning of the overridden method, or in the middle or end. 它还避免了不确定性,想知道是否应该在重写方法的开头或中间或结尾调用对base方法的调用。

Note: If you think a base class attaching to its own event is silly, there is nothing stopping you from having a non-virtual method in the base class. 注意:如果您认为附加到其自身事件的基类是愚蠢的,那么没有什么能阻止您在基类中使用非虚方法。

In the OP's case, I would think the Init and possibly Move events might meet the above criteria depending on circumstances. 在OP的情况下,我认为Init和可能的Move事件可能符合上述标准,具体取决于具体情况。 Typically, it is unlikely to ever have more than one Render method, so an abstract method (or empty virtual method, if this class lets you run simulations without drawing anything) method that did not need to invoke any base Render method seems most likely here. 通常情况下,它不太可能有多个Render方法,所以一个抽象方法(或空虚拟方法,如果这个类允许你运行模拟而不绘制任何东西)不需要调用任何基本Render方法的方法似乎很可能在这里。 A strategy (public delegate) for Render would be helpful if external components may know how to draw the class to the graphics target, perhaps with alternate visual styling (ie 2D vs 3D). 如果外部组件可能知道如何将类绘制到图形目标,可能使用替代视觉样式(即2D与3D),则Render的策略(公共委托)将非常有用。 It seems unlikely for a Render method, but perhaps may apply to other functions not mentioned, especially ones that apply an algorithm (trend line averaging method, etc.). 对于Render方法似乎不太可能,但也许可能适用于未提及的其他函数,尤其是应用算法的方法(趋势线平均方法等)。

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

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