C# 中的委托 -尝试深入了解 -第三部分
关于委托的更多内容。认识事件
背景
这是四篇文章中的第三篇,我将继续讨论委托并介绍事件。
由于这些文章在逻辑和上下文上是相互关联的,我建议按顺序阅读它们。
在我的第一篇文章中,我讨论了委托以及 C# 编译器如何处理它们。我们已经知道委托是一种特定类型的对象。它由编译器创建,它包含对一个或多个函数的引用;当它调用 Invoke
方法时,它实际上调用了被引用的函数。
这是指向 C# 中的委托 - 深入探究 - 第一部分的链接。
第二篇文章解释了委托如何使用多线程。委托可以使用 Invoke
方法同步调用被引用的函数,或者使用 BeginInvoke
/EndInvoke
方法进行异步调用。
这是指向 C# 中的委托 - 深入探究 - 第二部分的链接。
更多关于委托:另一种方法
在发布了前两篇文章后,我收到了一些积极的反馈。许多程序员喜欢这种解释编程术语的方法,不仅使用定义和代码示例,还创造现实生活中的情境并尝试将其翻译成编程。当你将现实生活与编程建立牢固的联系时,它有助于你更好地编程。你的程序变得生动;你可以看到在哪里以及如何使用你以前从未使用过的编程技巧。
以委托为例。
我们在日常生活中使用委托而不自知。时不时地我们会通过电话订餐、在线购物、参加在线课程;通过电子邮件与朋友聊天等……看看
- 通过电话订餐:它是一个委托,代表餐厅为你准备食物。
- 在线购物:网站是一个委托,代表卖家。
- 在线上课:网站是一个委托,代表一所大学。
- 通过电子邮件与朋友聊天:电子邮件是一个委托,代表你的朋友。
没想过吧?但这是真的。委托不仅是代表某人在会议或聚会上的一个人,也是任何可以代表任何事物的东西。
手机、网站等的概念,基本上就是编程世界的委托声明。你全局声明电话委托、网站委托或电子邮件委托。它们可以被所有人访问,但它们不是真实的事物。
要使它们成为现实,你必须实例化它们。在现实世界中,你不能只是通过电话打电话,你必须使用一个特定的电话号码。
所以当你拿到一个电话号码时,你就创建了一个电话委托的实例。
这个号码属于某个东西(例如餐厅)。在编程世界中,这就是一个方法或函数。所以当你实际拨打号码时,你就调用了委托。
我们可以传递委托。你可以将餐厅的电话号码传递给朋友。现在你的朋友就可以使用这个委托了。
但这里还有更多。
通过电话,你可以订购的不仅仅是食物,还可以与人交谈、订票、预约等等……
所以,一个委托——一部电话——可以代表许多不同的事物。这取决于电话号码和你通过电话传递的消息。
我们不需要在公寓里准备四部不同的电话来处理四件不同的事情。一部电话就足够了。
你明白了吗?
同一个委托可以用于许多不同的函数/方法。只需要遵守签名即可。
这如何应用于编程? 想象一下你有一个过程,它将错误消息记录到文件中。它返回 void
并接受一个 string
参数:错误消息。
另一个过程将错误消息记录到数据库。它返回 void
并接受一个 string
参数:错误消息。
另一个过程将错误消息通过电子邮件发送给管理员。它返回 void
并接受一个 string
参数:错误消息。
你可以声明一个 public
委托,代表以上任何一个过程。它必须具有过程的签名:它返回 void
并接受一个 string
参数。
在程序中,你创建一个委托实例,指向一个更合适的过程。当你使用委托的实例时,它会调用相应过程并按你想要的方式记录错误消息。
事件
通过这种方法,我想仔细看看事件。毫无疑问,C# 程序员经常使用事件。我无法想象任何生产程序没有事件。这意味着每个人都知道如何使用事件,这给我带来了挑战,需要为这个主题带来一些新东西。尽管如此,我还是会尝试从另一个角度来解释。
什么是事件?
根据谷歌词典,事件是指发生的事情,尤其是重要的事情。
如果我们想选出对我们来说真正重要的事情,我们会为它设置一个事件。
为了让这种联系更接近编程,我们假设我们不知道事件何时发生。
示例:一个家庭在等待婴儿。确切的分娩日期自然是未知的。
他们希望亲朋好友去医院探望准妈妈。于是他们宣布了这件事。他们在 Facebook 上发布消息,请求潜在的参与者告知他们是否会来。
如果我是一个朋友,想去,我会给他们打电话,留下我的电话号码,并请他们在事情发生时通知我。
当事件发生时,他们会打电话通知我,然后我去医院探望家人。
想更深入地了解吗?那么跟我来。
当家人宣布这件事时,他们实际上已经放置了一个委托。它还没有被实例化。
如果它已经被实例化,它将被用于通知所有参与者。
第一个呼叫者创建了这个委托的一个实例。
必须创建一个特殊的函数,并将该函数的引用放入委托中。
当事件发生时,他们会运行事件的 Invoke
方法(如果事件不为 null
:表示有参与者)。每个参与者都会通过运行为事件创建的函数得到通知。
在编程术语中,家人发布了一个事件,参与者订阅了该事件。
重要披露
事件是一个委托。 我们对委托了解的一切都可以应用于事件。通过发布事件,你可以从先前声明的委托或由环境预先构建的委托中选择一个合适的委托类型。
事件修改了委托
同时,事件修改了这个委托,所以我们可以说 event
关键字是委托的修饰符。
让我们考虑一些差异。
- 即使事件是委托,委托声明也不能取代事件声明。委托必须先声明才能成为事件。
- 事件声明不指定函数签名,它使用预定义的类型。
- 参与者不能订阅委托。你必须声明(发布)事件才能启用订阅。
- 事件属于其类。你不能在类外部调用事件(不像委托——你可以从任何地方调用它)。
- 如果你定义了一个接口,你不能将委托声明为接口的一部分,但你可以为事件这样做。
- .NET 对你打算用作事件的委托应用了一些限制。它要求函数签名是
void() (object sender, EventArgs e)
。
代码
MyFamilyEventHandler
是我们准备用于未来事件源的委托。
// declare delegate for future event
public delegate void MyFamilyEventHandler(object sender, EventArgs e);
Family
类声明了一个事件 OnBabyBorn
。BabyBorn
方法引发事件。它检查事件是否不为 null
,换句话说,它检查事件是否有订阅者。
该事件的类型为 MyFamilyEventHandler
。
它继承的基类是 MulticastDelegate
(见图 1)。这证明了事件本质上就是委托。

这是 Family
类
/// <summary>
/// Family that waits for a baby
/// </summary>
public class Family
{
///<summary>
/// declare event of MyFamilyEventHandler
/// type
/// </summary>
public event MyFamilyEventHandler OnBabyBorn;
public string Name { get; set; }
///<summary>
/// constructor
/// </summary>
///<param name="name">
public Family(string name)
{
Name = name;
}
///<summary>
/// raise OnBabyBorn event;
/// </summary>
public void BabyBorn()
{
if (null != OnBabyBorn)
OnBabyBorn(Name, new EventArgs());
}
}
现在我创建了 Friend
类。Friend
类有一个 public
属性 Family
。
当该属性被赋值时(在 Set 过程中),friend
订阅了 family
事件 OnBabyBorn
。订阅是通过以下代码完成的:
family.OnBabyBorn += new MyFamilyEventHandler(family_OnBabyBorn);
如你所知,当编译器遇到这段代码时,它会创建一个事件的实例(意味着一个委托),并将函数 family_OnBabyBorn
的引用分配给委托。从现在开始,当事件被引发时(意味着调用了基础委托 invoke
),该函数就会被调用并执行。
请记住,委托驻留在 family
类中。所以朋友通过委托向 family
提供了他的方法信息,当事件发生时,该委托将被调用。
///<summary>
/// Family friend
/// </summary>
public class Friend
{
private Family family;
public Family Family
{
get
{
return family;
}
set
{
family = value;
//subscribe for the family OnBabyBorn event:
family.OnBabyBorn += new MyFamilyEventHandler
(family_OnBabyBorn);
}
}
public string Name { get; set; }
///<summary>
/// constructor
/// </summary>
///<param name="name">
public Friend(string name)
{
Name = name;
}
///<summary>
/// run when OnBabyBorn event in
/// family occurs
/// </summary>
///<param name="sender">
///<param name="e">
void family_OnBabyBorn(object sender, EventArgs e)
{
Console.WriteLine("{0}, go visit {1} family", Name, sender.ToString() );
}
}
最后是 Program
类。
它创建了 Family
对象和两个 Friend
对象。
它将 family
分配给 friends
。它运行 family BabyBorn
过程,该过程触发 OnBabyBorn
事件。
两个 friends
都收到了事件发生的通知,并且 friend
s 的预先指定函数被调用。
class Program
{
static void Main(string[] args)
{
//create new family:
Family family = new Family("Adams");
//create a friend:
Friend Ed = new Friend("Ed");
//assign the family to the friend:
Ed.Family = family;
//create another friend:
Friend Alex = new Friend("Alex");
//assign the family to the friend:
Alex.Family = family;
//baby is born
family.BabyBorn();
Console.Read();
}
}
输出如下

注释
通过将编程主题(委托和事件)与现实世界的主题联系起来,我试图告诉你,在你的代码中,你应该使用类似的逻辑。编程中的委托和事件的含义与现实世界中的含义相同。我特意创建了一个非常简单的事件使用示例。其目的是展示事件在编程中的常识。
希望这有助于你更好地理解委托和事件,并在你的代码中使用它们。
我正在撰写的下一篇文章将向你介绍委托的现代化世界。
待续...