简体   繁体   中英

Virtual Methods or Events in C#

I'm currently writing a little C# library to simplify implementing little physical simulations/experiments.

The main component is a SimulationForm that runs a timer-loop internally and hides boilerplate-code from the user. The experiment itself will just be defined through three methods:

  1. Init() (Initialize everything)
  2. Render(Graphics g) (Render current simulation state)
  3. Move(double dt) (Move experiment for dt seconds)

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

protected virtual void Init() {} 
...

or

2) Events

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.

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. 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.

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 .

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. 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,
  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(). (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.)
  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. 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.

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.

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. 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. 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). 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.).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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