C#:事件还是观察者接口? 优点缺点?

发布于 2024-07-13 15:11:18 字数 351 浏览 5 评论 0原文

我有以下内容(简化):

interface IFindFilesObserver
{
    void OnFoundFile(FileInfo fileInfo);
    void OnFoundDirectory(DirectoryInfo directoryInfo);
}

class FindFiles
{
    IFindFilesObserver _observer;

    // ...
}

......我很矛盾。 这基本上是我用 C++ 编写的内容,但 C# 有事件。 我应该更改代码以使用事件,还是应该保留它?

与传统的观察者界面相比,事件的优点或缺点是什么?

I've got the following (simplified):

interface IFindFilesObserver
{
    void OnFoundFile(FileInfo fileInfo);
    void OnFoundDirectory(DirectoryInfo directoryInfo);
}

class FindFiles
{
    IFindFilesObserver _observer;

    // ...
}

...and I'm conflicted. This is basically what I would have written in C++, but C# has events. Should I change the code to use events, or should I leave it alone?

What are the advantages or disadvantages of events over a traditional observer interface?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

可爱咩 2024-07-20 15:11:18

将事件视为回调接口,其中该接口只有一个方法。

仅挂钩您需要的事件
对于事件,您只需要为您感兴趣的事件实现处理程序。 在观察者接口模式中,您必须实现整个接口中的所有方法,包括实现您实际上并不关心处理的通知类型的方法体。 在您的示例中,您始终必须实现 OnFoundDirectory 和 OnFoundFile,即使您只关心这些事件之一。

减少维护
事件的另一个好处是您可以向特定类添加一个新事件,以便它引发它,并且您不必更改每个现有的观察者。 然而,如果您想向接口添加新方法,则必须遍历已经实现该接口的每个类并在所有类中实现新方法。 不过,对于事件,您只需要更改实际想要执行某些操作以响应您添加的新事件的现有类。

该模式内置于语言中,因此每个人都知道如何使用它
事件是惯用的,因为当您看到事件时,您就知道如何使用它。 通过观察者接口,人们经常实现不同的注册方式来接收通知并连接观察者。但是,一旦您学会了如何注册和使用其中一种方式(使用 += 运算符),其余的都是相同的。

界面优点
我在界面方面没有太多专业人士。 我猜他们强迫某人实现接口中的所有方法。 但是,你不能真正强迫某人正确实现所有这些方法,所以我认为这没有太多价值。

语法
有些人不喜欢为每个事件声明委托类型的方式。 此外,.NET 框架中的标准事件处理程序具有以下参数:(对象发送者、EventArgs args)。 由于发送方没有指定特定类型,因此如果您想使用它,则必须进行向下转换。 这在实践中通常很好,但感觉不太正确,因为你失去了静态类型系统的保护。 但是,如果您实现自己的事件并且不遵循 .NET 框架约定,则可以使用正确的类型,因此不需要潜在的向下转换。

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

一桥轻雨一伞开 2024-07-20 15:11:18

嗯,事件可以用来实现观察者模式。 事实上,使用事件可以被视为观察者模式的另一种实现恕我直言。

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.

以歌曲疗慰 2024-07-20 15:11:18
  • 事件很难通过对象链传播,例如,如果您使用 FACADE 模式或将工作委托给其他类。
  • 您需要非常小心地取消订阅事件以允许对象被垃圾收集。
  • 事件比简单函数调用慢 2 倍,如果每次引发时都进行空检查,并在空检查和调用之前复制事件委托以使其线程安全,则慢 3 倍。

  • 另外阅读 MSDN 了解新的(4.0 中)IObserver 接口。

考虑以下示例:

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();
        }
    }
}
  • 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();
        }
    }
}
故事还在继续 2024-07-20 15:11:18

接口解决方案的优点:

  • 如果添加方法,现有观察者需要实现这些方法。 这意味着您不太可能忘记将现有观察者连接到新功能。 您当然可以将它们实现为空方法,这意味着您可以奢侈地仍然不执行任何操作来响应某些“事件”。 但你不会那么容易忘记。
  • 如果您使用显式实现,您还会以另一种方式得到编译器错误,如果您删除或更改现有接口,那么实现它们的观察者将停止编译。

缺点:

  • 规划时需要更多思考,因为观察者界面的更改可能会强制整个解决方案发生更改,这可能需要不同的规划。 由于简单事件是可选的,因此很少或不需要更改其他代码,除非其他代码应对该事件做出反应。

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.
王权女流氓 2024-07-20 15:11:18

活动的一些进一步好处。

  • 您可以免费获得适当的多播行为。
  • 如果您更改事件的订阅者以响应该事件,则行为已明确定义
  • 它们可以轻松且一致地内省(反映)
  • 事件的工具链支持(仅仅因为它们是 .net 中的惯用语)
  • 您可以选择使用它提供的异步 API

您可以自己实现所有这些(工具链除外),但这非常困难。 例如:
如果您使用像 List<> 这样的成员变量 存储观察者列表。
如果您使用 foreach 对其进行迭代,那么在 OnFoo() 方法回调之一中添加或删除订阅者的任何尝试都将触发异常,除非您编写进一步的代码来干净地处理它。

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.

深海里的那抹蓝 2024-07-20 15:11:18

最好的决定方法是:哪一个更适合具体情况。 这听起来可能是一个愚蠢或无益的答案,但我认为您不应该将其中一个视为“正确”的解决方案。

我们可以向您提供一百个提示。 当观察者期望监听任意事件时,事件是最好的。 当观察者希望列出所有给定的事件集时,界面是最好的。 处理 GUI 应用程序时,事件是最好的选择。 接口消耗更少的内存(多个事件的单个指针)。 亚达亚达亚达。 优点和缺点的项目符号列表值得考虑,但不是明确的答案。 您真正需要做的是在实际应用中尝试它们并获得良好的感受。 然后你可以选择更适合情况的一种。 学习形式实践。

如果您必须使用单个定义问题,那么问问自己哪个更能描述您的情况:一组松散相关的事件,其中任何一个都可以使用或忽略,或者一组密切相关的事件,通常都需要由一名观察员。 但是,我只是描述了事件模型和接口模型,所以我又回到了第一个问题:哪一个更适合这种情况?

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?

爱人如己 2024-07-20 15:11:18

优点是事件更加“点网状”。 如果您正在设计可以拖放到表单上的非可视组件,则可以使用设计器将它们连接起来。

缺点是一个事件仅表示一个事件 - 您需要为每个要通知观察者的“事物”一个单独的事件。 这实际上并没有太大的实际影响,只是每个被观察的对象都需要为每个事件的每个观察者保存一个引用,在有大量被观察的对象的情况下会导致内存膨胀(这是他们采用不同方式的原因之一)管理 WPF 中的观察者/可观察关系)。

就你而言,我认为这没有多大区别。 如果观察者通常对所有这些事件感兴趣,请使用观察者界面而不是单独的事件。

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.

烟雨扶苏 2024-07-20 15:11:18

我更喜欢基于事件的解决方案,原因如下:

  • 它降低了进入成本。 说“+= new EventHandler”比实现完整的接口要容易得多。
  • 它降低了维护成本。 如果您将一个新事件添加到您的类中,那么这就是需要完成的全部工作。 如果您向接口添加新事件,则必须更新代码库中的每个使用者。 或者定义一个全新的接口,随着时间的推移,它会让消费者感到厌烦“我要实现 IRandomEvent2 还是 IRandomEvent5?”
  • 事件允许处理程序是非基于类的(即某处的静态方法)。 没有功能上的理由强制所有事件处理程序成为实例成员
  • 将一堆事件分组到一个接口中是对事件的使用方式做出假设(这只是一个假设)
  • 与原始接口相比,接口没有提供真正的优势事件。

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.
酒废 2024-07-20 15:11:18

Java 具有对匿名接口的语言支持,因此回调接口是 Java 中使用的东西。

C# 支持匿名委托 - lambda - 因此事件是 C# 中使用的东西。

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

十年不长 2024-07-20 15:11:18

接口的一个好处是它们更容易应用装饰器。 标准示例:

subject.RegisterObserver(new LoggingObserver(myRealObserver));

相比:(

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

我是装饰器模式的忠实粉丝)。

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

橘寄 2024-07-20 15:11:18

如果您的对象需要以某种保留引用的方式进行序列化,例如 NetDataContractSerializerprotobuf 事件将无法跨越序列化边界。 由于观察者模式仅依赖于对象引用,因此如果需要的话,它可以毫无问题地处理这种类型的序列化。

前任。 您有一堆相互双向链接的业务对象,您需要将它们传递给 Web 服务。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文