也许我很慢,但我不明白为什么你会使用一个非实际动作(如点击)的事件。当你可以调用方法时,为什么要经历创建委托和事件的繁琐?看起来当你创建一个事件时,你正在做的就是为调用者创建一种方法来通过一些复杂的过程来调用一个简单的方法。来电者必须自己举办活动!我不明白。
或许我只是没有理解这个概念。我需要像OnClick
这样的事件以及与控件的交互,但是对于类来说呢?我试图为我的一个类实现事件,比如,当项目的来源发生变化时,但很快意识到没有意义,因为每当我想要执行某个操作而不是创建事件时我就可以调用方法,提出一个事件,并编写一个事件处理程序。另外,我可以重用我的方法,而我不一定能重用我的事件处理程序。
有人请我直截了当。我觉得我在这里错了,我想纠正。我提出的最后一个问题并没有真正得到任何有用的答案。
感谢。
答案 0 :(得分:9)
我一直很喜欢广播电台的比喻。
当广播电台想播放某些内容时,它会将其发送出去。它不需要知道是否有任何人在那里听。您的收音机能够通过无线电台注册(通过拨入表盘),所有无线电台广播(我们的小隐喻中的事件)都由收音机接收,并将其转换为声音。
没有这种注册(或事件)机制。无线电台必须依次联系每个无线电广播并询问是否需要广播,如果您的电台说是,则直接向其发送信号。
您的代码可能遵循非常类似的范例,其中一个类执行操作,但该类可能不知道,或者可能不想知道谁将关心或采取行动。因此,它为任何对象提供了一种方式来注册或取消注册自己以通知操作已发生。
答案 1 :(得分:4)
一般情况下,事件可以是将侦听器/观察者与调用者/提升者分离的好方法。
考虑按钮。当有人点击按钮时,会触发点击事件。按钮单击的监听器是否关心按钮的外观,确实或任何性质的东西?很可能不是。它只关心点击动作已经发生,它现在可以做并做一些事情。
同样的事情可以应用于需要相同隔离/去耦的任何事物。它不必是UI驱动的,它可以只是需要进行的逻辑分离。
答案 2 :(得分:2)
使用事件的优点是可以将处理事件的类与引发它们的类(la the Observer Pattern)分开。虽然这对Model View Controller很有用(引发点击事件的按钮与处理它们的类正交),但是只要你想让它(无论是否在运行时)保持简单,它也很有用。处理从提升它们的类中分离的事件的类(允许您更改或替换它们)。
基本上,重点是保持处理事件的类与引发它们的类分开。不必要的耦合是不好的,因为它使代码维护变得更加困难(因为代码中的一个位置的更改将需要更改与其耦合的任何代码段。)
编辑: 您将在Asp.Net中执行的大多数事件处理可能是处理提供给您的类中的事件。因为这些类使用事件处理,所以它使您可以轻松地与它们进行交互。通常,该事件还将用于允许您与引发事件的对象进行交互。例如,数据绑定控件通常在它们连接到数据库之前引发事件,您可以处理该事件以便使用运行时信息来更改传递给与数据库通信的存储过程的参数,例如,如果查询字符串提供页码参数,并且存储过程具有页码参数。
答案 3 :(得分:1)
可以像消息一样使用事件来通知已发生的事情,并且消费者可以以适当的方式对它们做出反应,因此不同的组件是松散耦合的。你可以使用许多事件,例如审计系统中发生的事情;审计组件可以使用各种事件,并在被触发时写入日志。
答案 4 :(得分:1)
你似乎缺少的是两个:
软件中发生的事情可能需要花费任意时间,这不仅仅是因为等待用户输入(异步i / o,数据库查询等)。如果您启动了异步i / o请求,则需要订阅事件,以便在读取完成时通知您,以便您可以对数据执行某些操作。这个想法在.NET的BackgroundWorker类中得到了推广,它允许你在后台执行繁重的任务(另一个线程),并在完成后在调用线程中接收通知。
多个客户使用的软件中不仅发生了一次。例如,如果你有一个插件架构,你的主代码提供了插件代码的钩子,你可以做类似的事情
foreach (Plugin p in getAvaliablePlugins()) { p.hook1(); }
在你的代码的每个钩子中都会降低灵活性(p无法决定做什么,并且必须公开提供hook1方法)或者你可以
raiseEvent(hook1,this)
所有注册的插件都可以执行他们的代码,因为他们收到了这个事件,让他们按照自己的意愿完成工作。
答案 5 :(得分:1)
我认为你混淆了ASP.NET(错误)使用事件,使用简单的事件处理。
我们将从简单的事件处理开始。事件是实现"Open [for extension]/Closed [for modification]" principle的另一种方式。当你的类公开一个事件时,它允许外部类(可能是那些甚至没有想到的类,更不用说构建的类)让代码由你的类运行。这是一个非常强大的扩展机制,它不需要以任何方式修改您的类。
作为一个例子,考虑一个知道如何接受请求但不知道如何处理文件的Web服务器(我将在这里使用双向事件,其中处理程序可以将数据传递回事件来源。有些人认为这不是犹太人,但这是第一个想到的例子):
class WebServer {
public event EventHandler<RequestReceivedEventArgs> RequestReceived;
void ReceiveRequest() {
// lots of uninteresting network code here
var e = new RequestReceivedEventArgs();
e.Request = ReadRequest();
OnRequestReceived(e);
WriteResponse(e.Response);
}
void OnRequestReceived(RequestReceivedEventArgs e) {
var h = RequestReceived;
if (h != null) h(e);
}
}
不改变该类的源代码 - 也许它在第三方库中 - 我可以添加一个知道如何从磁盘读取文件的类:
class FileRequestProcessor {
void WebServer_RequestReceived(object sender, EventArgs e) {
e.Response = File.ReadAllText(e.Request);
}
}
或者,也许是ASP.NET编译器:
class AspNetRequestProcessor {
void WebServer_RequestReceived(object sender, EventArgs e) {
var p = Compile(e.Request);
e.Response = p.Render();
}
}
或者,也许我只是想知道事件发生了,而根本不会影响它。比如,记录:
class LogRequestProcessor {
void WebServer_RequestReceived(object sender, EventArgs e) {
File.WriteAllText("log.txt", e.Request);
}
}
所有这些类基本上都是在WebServer.OnRequestReceived
中间“注入”代码。
现在,对于丑陋的部分。 ASP.NET有一个令人讨厌的习惯,就是让你编写事件处理程序来处理你自己的事件。因此,您继承的类(System.Web.UI.Page
)有一个名为Load
的事件:
abstract class Page {
public event EventHandler Load;
virtual void OnLoad(EventArgs e) {
var h = this.Load;
if (h != null) h(e);
}
}
并且您希望在加载页面时运行代码。遵循开放/封闭原则,我们可以继承和覆盖:
class MyPage : Page {
override void OnLoad(EventArgs e) {
base.OnLoad(e);
Response.Write("Hello World!");
}
}
或使用事件:
class MyPage : Page {
MyPage() {
this.Load += Page_Load;
}
void Page_Load(EventArgs e) {
Response.Write("Hello World!");
}
}
出于某种原因,Visual Studio和ASP.NET更喜欢事件方法。我想你可以为Load
事件拥有多个处理程序,并且它们都会自动运行 - 但我从未见过有人这样做过。就个人而言,我更喜欢覆盖方法 - 我认为它更清晰一点,你永远不会有“我为什么订阅自己的事件?”的问题。