学习 C#(第 9 天):理解 C# 中的事件(深入探讨)






4.31/5 (67投票s)
理解 C# 中的事件(深入探讨)
目录
引言
事件是 C# 编程环境中的核心且重要的概念之一,坦白说,如果没有适当的解释和示例,有时很难理解它们。
因此,我考虑写这篇文章来让学习者和初学者更容易理解。
图片来源:http://d.wildapricot.net/images/newsblog/bigstock-events-19983578.jpg?sfvrsn=0
路线图
让我们再次回顾一下我们的路线图,
- 深入 OOP(第一天):多态与继承(早期绑定/编译时多态)
- 深入OOP(第2天):多态性和继承(继承)
- 深入OOP(第3天):多态性和继承(动态绑定/运行时多态)
- 深入 OOP (第四天):多态与继承 (关于 C# 中的抽象类)
- 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
- 学习 C#(第 6 天):理解 C# 中的枚举(实用方法)
- 学习 C# (第 7 天):C# 中的属性 (实用方法)
- 学习 C# (第 8 天):C# 中的索引器 (实用方法)
- 学习 C#(第 9 天):理解 C# 中的事件(深入探讨)
- 学习C#(第10天):C#中的委托(一种实用方法)
- 学习C#(第11天):C#中的事件(一种实用方法)
我们的主题
用非常简单的语言来说,事件是动作或发生的事情,例如单击、按键、鼠标移动或系统生成的通知。应用程序可以在事件发生时对其作出响应。事件是对象发送的消息,用于指示事件的发生。事件是进程间通信的有效手段。它们对对象很有用,因为它们提供了状态变化的信号,这对于对象的客户端可能很有价值。
如果上面的句子很难理解,我们简单地把它理解为,如果用户单击窗体上的按钮,就会触发一个事件;如果用户在文本框中输入内容,就会触发按键事件,依此类推。
来自MSDN的解释:
“C# 中的事件是一种类向该类的客户端提供通知的方式,告知它们当对象发生某些有趣的事情时。最常见的事件用途是在图形用户界面中;通常,表示界面中控件的类具有在用户对控件执行某些操作(例如,单击按钮)时进行通知的事件。
但是,事件不必仅用于图形界面。事件为对象提供了一种通用的方式来发出可能对对象客户端有用的状态更改信号。事件是创建可在大量不同程序中重用的类的基本构建块。
事件使用委托进行声明。如果您还没有学习委托教程,您应该先学习。回想一下,委托对象封装了一个方法,以便可以匿名调用它。事件是一种类允许客户端为其提供方法委托的方式,当事件发生时应该调用这些方法。当事件发生时,将调用由其客户端提供给它的委托。”
下图是解释事件和事件处理的通用表示。
在 C# 中,委托与事件一起用于实现事件处理。.NET Framework 事件模型使用委托将通知与称为事件处理程序的方法绑定。当生成事件时,委托会调用关联的事件处理程序。
.NET Windows 应用程序按钮单击事件调查 (案例研究)
只需打开您的 Visual Studio 并创建一个 Windows 应用程序。您将在 Windows 应用程序项目中找到一个名为Form1.cs的窗体。在该窗体上添加一个简单的按钮,只需拖放即可。在该按钮的属性面板中,将单击事件与代码隐藏中的事件绑定,并在单击时显示一些文本。您可以浏览附加的源代码以进行理解。
现在,当您运行应用程序时,您的窗体将显示一个按钮,单击该按钮,看看会发生什么,
正如我们所见,用户单击了窗体上的按钮,因此触发了按钮单击事件,该事件由委托处理,委托进而调用button_click
方法(事件处理程序方法),该方法显示了我们的消息框。
在上面的示例中,事件声明、委托声明、事件处理程序声明都由 .NET 完成,我们只需编写代码来处理我们的事件,在这种情况下是显示消息框的代码。
幕后
如果我们调查Form1.Designer.cs并按照下图所示的步骤进行,我们可以轻松找到 `event` 关键字和 `delegate` 关键字,从而找到它们的定义。
由于我们还没有看到定义语法,它可能看起来很陌生,但我们很快就会讲到,但现在,请按照图中的步骤进行。
步骤 1:从解决方案资源管理器中打开Form1.Designer.Cs
步骤 2:在Form1.Designer.Cs中查找Click
事件和EventHandler
委托。
步骤 3:双击this.button1.Click
并按 F12 查看 Click 事件定义,同样双击System.EventHandler
并按 F12 查看 EventHandler Delegate 定义。
事件的定义,
委托的定义,
发布-订阅模型
事件在类中声明和引发,并使用同一类中的委托与事件处理程序关联。事件是类的一部分,并且同一个类用于发布其事件。然而,其他类可以接受这些事件,换句话说,可以订阅这些事件。事件使用发布者和订阅者模型。
发布者是包含事件和委托定义的类。事件与委托的关联也在发布者类中指定。发布者类的对象调用事件,该事件会通知其他对象。
订阅者是想要接受事件并为事件提供处理程序的类。发布者类的委托调用订阅者类的方法。订阅者类中的此方法是事件处理程序。发布者和订阅者模型实现可以由同一个类定义。
委托在 C# 中扮演着非常重要的角色,它是可以直接放入类命名空间的实体之一。此委托的特性使其对所有其他类都可用。委托基于面向对象模式工作,并通过封装方法和对象来尝试遵循封装。C# 中的委托定义了一个类,并使用System.Delegate
命名空间。委托非常适合匿名方法调用。我将在我接下来的文章中更详细地讨论事件和委托。
下图显示了发布者和订阅者对象使用的机制。
亲自动手:创建自定义事件
让我们动手构建我们自己的事件处理示例。在下面的示例中,我们将看到如何定义我们的自定义事件,如何引发它,以及如何通过我们自己的自定义事件处理程序来处理它。
在我们简单的示例中,我们将构建一个银行控制台应用程序,当客户进行交易时,将引发TransactionMade
事件,并向他发送通知。
现在让我们进行一些严肃的编码。
首先,我们定义我们的 Account 类。
我们可以添加一个构造函数来初始化我们的变量 int BalanceAmount
,它将保存我们类中的账户余额。
public int BalanceAmount;
public Account(int amount)
{
BalanceAmount = amount;
}
然后我们定义我们的事件和我们的委托。
发布者类(Account 类)中的事件定义包括委托的声明以及基于委托的事件的声明。以下代码定义了一个名为TransactionHandler
的委托和一个名为TransactionMade
的事件,当引发该事件时,它会调用TransactionHandler
委托。
public delegate void TransactionHandler(object sender,TransactionEventArgs e);
public event TransactionHandler TransactionMade;
正如您所见,我们的委托名称是TransactionHandler
,其签名包含void
返回类型和两个object
和TransactionEventArgs
类型的参数。如果您想在某处实例化此委托,作为构造函数参数传入的函数应具有与此委托相同的签名。
当引发事件时,我们会将一些数据传递给派生自EventArgs
的类中的订阅者。例如,在我们的示例中,我们想提供交易金额和交易类型。因此,我们定义一个TransactionEventArgs
类,它将继承EventArgs
以将数据传递给订阅者类。我们声明了两个私有变量,一个int _transactionAmount
用于传递交易金额信息,另一个是string _transactionType
用于将交易类型(借记/贷记)信息传递给订阅者类。
这是类定义
public class TransactionEventArgs : EventArgs
{
public int TranactionAmount { get; set; }
public string TranactionType { get; set; }
public TransactionEventArgs(int amt, string type)
{
TranactionAmount = amt;
TranactionType = type;
}
}
现在,所有内容都在我们的Account
类中。现在我们将定义我们的通知方法,这些方法将在贷记或借记交易时被调用并引发我们的事件。
在Debit
方法中,将扣除余额并引发事件以通知订阅者余额已更改;同样,在 Credit 方法中,将存入余额并发送通知给订阅者类。
借记方法
public void Debit(int debitAmount)
{
if (debitAmount < BalanceAmount)
{
BalanceAmount = BalanceAmount - debitAmount;
TransactionEventArgs e = new TransactionEventArgs(debitAmount,"Debited");
OnTransactionMade(e); // Debit transaction made
}
}
贷记方法
public void Credit(int creditAmount)
{
BalanceAmount = BalanceAmount + creditAmount;
TransactionEventArgs e = new TransactionEventArgs(creditAmount,"Credited");
OnTransactionMade(e); // Credit transaction made
}
如您在上述方法中所见,我们创建了TransactionEventArgs
的实例,并将该实例传递给了OnTransactionMade()
方法,并调用了它。您可能在想OnTransactionMade()
在这里做什么, well,这就是我们引发事件的方法。所以这是它的定义
protected virtual void OnTransactionMade(TransactionEventArgs e)
{
if (TransactionMade != null)
{
TransactionMade(this, e); // Raise the event
}
}
以下是我们Account
类的完整代码
namespace EventExample
{
public delegate void TransactionHandler(object sender,TransactionEventArgs e); // Delegate Definition
class Account
{
public event TransactionHandler TransactionMade; // Event Definition
public int BalanceAmount;
public Account(int amount)
{
this.BalanceAmount = amount;
}
public void Debit(int debitAmount)
{
if (debitAmount < BalanceAmount)
{
BalanceAmount = BalanceAmount - debitAmount;
TransactionEventArgs e = new TransactionEventArgs(debitAmount,"Debited");
OnTransactionMade(e); // Debit transaction made
}
}
public void Credit(int creditAmount)
{
BalanceAmount = BalanceAmount + creditAmount;
TransactionEventArgs e = new TransactionEventArgs(creditAmount,"Credited");
OnTransactionMade(e); // Credit transaction made
}
protected virtual void OnTransactionMade(TransactionEventArgs e)
{
if (TransactionMade != null)
{
TransactionMade(this, e); // Raise the event
}
}
}
通过调用我们的事件TransactionMade(this, e);
来引发事件。
此事件将由我们的事件处理程序处理。
现在让我们定义我们的Subscriber
类,它将对事件作出反应并相应地处理它,通过其自己的方法。
首先创建一个名为TestMyEvent
的类,并定义一个名为SendNotification
的方法,其返回类型和参数应与我们在发布者类中之前声明的Delegate
匹配。基本上,此方法将响应事件(余额金额的变化)并通知用户(通过在控制台写入此内容)。
以下是定义
private static void SendNotification(object sender, TransactionEventArgs e)
{
Console.WriteLine("Your Account is {0} for Rs.{1} ", e.TranactionType, e.TranactionAmount);
}
现在在订阅者类(TestMyEvent
)的Main()
方法中,创建实例并传入一些初始余额。然后将事件处理程序的引用传递给事件,并将要在该事件响应时调用的方法传递给事件处理程序。
private static void Main()
{
Account MyAccount = new Account(10000);
MyAccount.TransactionMade += new TransactionHandler(SendNotification);
MyAccount.Credit(500);
Console.WriteLine("Your Current Balance is : " + MyAccount.BalanceAmount);
Console.ReadLine();
}
因此,以下是我们订阅者类(TestMyEvent
)的完整定义
class TestMyEvent
{
private static void SendNotification(object sender, TransactionEventArgs e)
{
Console.WriteLine("Your Account is {0} for Rs.{1} ", e.TranactionType, e.TranactionAmount);
}
private static void Main()
{
Account MyAccount = new Account(10000);
MyAccount.TransactionMade += new TransactionHandler(SendNotification);
MyAccount.Credit(500);
Console.WriteLine("Your Current Balance is : " + MyAccount.BalanceAmount);
Console.ReadLine();
}
}
我们的输出将是
代码解释
在我们的示例中,我们的Account
类是发布者,当进行借记或贷记时,它会通知账户余额的变化,正如您从代码中看到的,我们在Debit
/Credit
方法中引发了我们的事件,如果事件被引发,那么必须有人对其作出反应,所以TestMyEvent
类是订阅者类,在该类中进行了借记/贷记交易,因此账户余额发生变化,因此它从Publisher
类接收通知并对其作出反应,并通过调用其事件处理程序方法SendNotification
来处理事件。所以这里很清楚,事件可以在一个类(Publisher
)中定义,而多个订阅者类可以根据自己的需要对其作出反应。
结论
我们调查了 .NET 事件并构建了自己的自定义事件,并看到了事件是如何被引发和处理的。所以,如果我说事件封装了委托,而委托封装了方法,那也不会错。因此,订阅者类不需要知道幕后发生了什么,它只需要发布者类发出事件已被引发的通知,并必须相应地作出响应。
我希望您在阅读这篇文章后感到满意,最重要的是,您在阅读和编码时感到愉快。
我的其他系列文章
MVC: https://codeproject.org.cn/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu
RESTful WebAPIs: https://codeproject.org.cn/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application
祝您编码愉快!