C#: events or an observer interface? Pros/cons?

对着背影说爱祢 提交于 2019-11-29 20:25:16

Consider an event to be a callback interface where the interface has only one method.

Only hook events you need
With events you only need to implement handlers for events you're interested in handling. In the observer interface pattern, you'd have to implement all methods in the entire interface including implementing method bodies for notification types you don't actually care about handling. In your example, you always have to implement OnFoundDirectory and OnFoundFile, even if you only care about one of these events.

Less maintenance
Another good thing about events is you can add a new one to a particular class so that it will raise it, and you don't have to change every existing observer. Whereas if you want to add a new method to an interface, you have to go around every class that already implements that interface and implement the new method in all of them. With an event though, you only need to alter existing classes that actually want to do something in response to the new event you're adding.

The pattern is built into the language so everybody knows how to use it
Events are idiomatic, in that when you see an event, you know how to use it. With an observer interface, people often implement different ways of registering to receive notifications and hook up the observer.. with events though, once you've learnt how to register and use one (with the += operator), the rest are all the same.

Pros for interfaces
I haven't got many pros for interfaces. I guess they force someone to to implement all methods in the interface. But, you can't really force somebody to implement all those methods correctly, so I don't think there's a lot of value on this.

Syntax
Some people don't like the way you have to declare a delegate type for each event. Also, standard event handlers in the .Net framework follow have these parameters: (object sender, EventArgs args). As sender doesn't specify a particular type, you have to down-cast if you want to use it. This often is fine in practice, if feels not quite right though because you're losing the protection of the static type system. But, if you implement your own events and don't follow the .Net framework convention on this, you can use the correct type so potential down-casting isn't required.

Hmm, events can be used to implement the Observer pattern. In fact, using events can be regarded as another implementation of the observer-pattern imho.

Pros of an interface-solution:

  • If you add methods, existing observers needs to implement those methods. This means that you have less of a chance of forgetting to wire up existing observers to new functionality. You can of course implement them as empty methods which means you have the luxury of still doing nothing in response to certain "events". But you won't so easily forget.
  • If you use explicit implementation, you'll also get compiler errors the other way, if you remove or change existing interfaces, then observers implementing them will stop compiling.

Cons:

  • More thought has to go into planning, since a change in the observer interface might enforce changes all over your solution, which might require different planning. Since a simple event is optional, little or no other code has to change unless that other code should react to the event.

Some further benefits of events.

  • You get proper multicast behaviour for free.
  • If you change the subscribers of an event in response to that event the behaviour is well defined
  • They can be introspected (reflected) easily and consistently
  • Tool chain support for events (simply because they are the idiom in .net)
  • You get the option to use the asynchronous apis it provides

You can achieve all of these (except the tool chain) yourself but it's surprisingly hard. For example: If you use a member variable like a List<> to store the list of observers. If you use foreach to iterate over it then any attempt to add or remove a subscriber within one of the OnFoo() method callbacks will trigger an exception unless you write further code to deal with it cleanly.

  • Events are harder to propagate through chain of objects, for example if you use FACADE pattern or delegate work to other class.
  • You need to be very careful with unsubscribing from events to allow object to be garbage collected.
  • Events are 2x time slower than simple function call, 3x slower if you do null check on every raise, and copy event delegate before null check and invocation to make it thread safe.

  • Also read MSDN about new (in 4.0) IObserver<T> interface.

Consider this example:

using System;

namespace Example
{
    //Observer
    public class SomeFacade
    {
        public void DoSomeWork(IObserver notificationObject)
        {
            Worker worker = new Worker(notificationObject);
            worker.DoWork();
        }
    }
    public class Worker
    {
        private readonly IObserver _notificationObject;
        public Worker(IObserver notificationObject)
        {
            _notificationObject = notificationObject;
        }
        public void DoWork()
        {
            //...
            _notificationObject.Progress(100);
            _notificationObject.Done();
        }
    }
    public interface IObserver
    {
        void Done();
        void Progress(int amount);
    }

    //Events
    public class SomeFacadeWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        private void RaiseDone()
        {
            if (Done != null) Done();
        }
        private void RaiseProgress(int amount)
        {
            if (Progress != null) Progress(amount);
        }

        public void DoSomeWork()
        {
            WorkerWithEvents worker = new WorkerWithEvents();
            worker.Done += RaiseDone;
            worker.Progress += RaiseProgress;
            worker.DoWork();
            //Also we neede to unsubscribe...
            worker.Done -= RaiseDone;
            worker.Progress -= RaiseProgress;
        }
    }
    public class WorkerWithEvents
    {
        public event Action Done;
        public event Action<int> Progress;

        public void DoWork()
        {
            //...
            Progress(100);
            Done();
        }
    }
}

Pros are that events are more 'dot-netty'. If you are designing non-visual components that can be dropped onto a form, you can hook them up using the designer.

Cons are that an event only signifies a single event - you need a separate event for each 'thing' that you want to notify the observer about. This doesn't really have much practical impact except that each observed object would need to hold a reference for every observer for every event, bloating memory in the case where there are lots of observed objects (one of the reasons they made a different way of managing the observer/observable relationship in WPF).

In your case I'd argue it doesn't make much difference. If the observer would typically be interested in all those events, use an observer interface rather than separate events.

Java has language support for anonymous interfaces, so callback interfaces are the thing to use in Java.

C# has support for anonymous delegates - lambdas - and so events are the thing to use in C#.

The best way to decide is this: which one suits the situation better. That might sound like a silly or unhelpful answer, but I don't think you should regard one or the other as the "proper" solution.

We can throw a hundred tips at you. Events are best when the observer is expected to listen for arbitrary events. An interface is best when the observer is expected to listed to all of a given set of events. Events are best when dealing with GUI apps. Interfaces consume less memory (a single pointer for multiple events). Yadda yadda yadda. A bulleted list of pros and cons is something to think about, but not a definitive answer. What you really need to do is try both of them in actual applications and get a good feel for them. Then you can choose the one that suits the situation better. Learn form doing.

If you have to use a single defining question, then ask yourself which better describes your situation: A set of loosely related events any of which may be used or ignored, or a set of closely related events which will all generally need to be handled by one observer. But then, I'm just describing the event model and interface model, so I'm back at square one: which one suits the situation better?

A benefit of interfaces is that they are easier to apply decorators to. The standard example:

subject.RegisterObserver(new LoggingObserver(myRealObserver));

compared to:

subject.AnEvent += (sender, args) => { LogTheEvent(); realEventHandler(sender, args); };

(I'm a big fan of the decorator pattern).

I prefer an event base solution for the following reasons

  • It reduces the cost of entry. It's much easier to say "+= new EventHandler" than to implement a full fledged interface.
  • It reduces maintenance costs. If you add a new event into your class that's all that needs to be done. If you add a new event to an interface you must update every single consumer in your code base. Or define an entirely new interface which over time gets annoying to consumers "Do I implement IRandomEvent2 or IRandomEvent5?"
  • Events allow for handlers to be non-class based (ie a static method somewhere). There is no functional reason to force all event handlers to be an instance member
  • Grouping a bunch of events into an interface is making an assumption about how the events are used (and it's just that, an assumption)
  • Interfaces offer no real advantage over a raw event.

If your objects will need to be serialized in some way that retains references such as with NetDataContractSerializer or perhaps protobuf events will not be able to cross the serialization boundary. Since observer pattern relies on nothing more than just object references, it can work with this type of serialization with no problem if that is what is desired.

Ex. You have a bunch of business objects that link to each other bidirectionally that you need to pass to a web service.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!