C# 中的委托、多播委托和事件 - 笔记






4.90/5 (63投票s)
在本文中,我们将尝试理解 C# 中的委托、多播委托和事件。
引言
简要回答
委托是指向函数的指针
那么间接调用的麻烦之处在哪里呢?
不,根本不要这么想
使用委托实现回调
多播委托
事件:- 封装的委托
总结
引言
我明白有很多关于委托和事件的文章,而且我相信在接下来的时间里还会有超过一千篇文章。但仍然有许多开发人员感到困惑:
- 什么是委托,在哪里使用它?
- 什么是多播委托?
- 何时使用委托 VS 事件
因此,在本文中,我们将尝试回答上述三个问题。
简要回答
下面是一个简单的图表,解释了委托、多播委托和事件是如何关联的。
- 委托是指向函数的指针,用于回调。
- 多播委托有助于调用多个回调。
- 事件封装委托并实现发布者-订阅者模型。
- 事件和多播是委托的类型。因此,委托是事件和多播的基础。
在文章的其余部分,我们将尝试更详细地理解上述陈述。
委托是指向函数的指针
“委托是指向函数的指针”,您可以通过委托调用所指向的函数。
创建委托是一个三步过程:-
- 声明委托:- 在这一步,您将使用 delegate 关键字,但需要确保此委托的签名必须与方法相同。例如,在下面的代码中,“SomeMethod()”返回 void 且不接受任何输入参数。因此,委托“SomeMethodPtr()”是相应定义的。
- 创建委托对象:- 定义委托后,您需要创建对象来使用它。这在代码中显示为步骤 2。
- 调用委托:- 要调用委托,您需要调用“Invoke”方法来调用“SomeMethod”。
delegate void SomeMethodPtr(); // 1. Declare delegate
static void Main(string[] args)
{
SomeMethodPtr ptrObject = SomeMethod; // 2. Create object of delegate
ptrObject.Invoke(); // 3 . invoke the delegate
}
static void SomeMethod()
{
// some code
}
那么间接调用的麻烦之处在哪里呢?
好的,既然我们已经理解了委托是指向函数的指针,那么它有什么了不起的呢?当我们可以直接调用方法时,通过间接调用方法有什么好处,可以让我们生活得更好?
想想看,与通过委托间接调用相比,以下代码是不是更简单?
static void Main(string[] args)
{
SomeMethod()
}
static void SomeMethod()
{
// some code
}
要理解的第一个重要之处是,当您调用不同库中的代码时,委托的重要性就体现出来了。例如,下面是一个简单的文件搜索库,用于搜索目录中的文件。
请注意,此刻实际上没有文件搜索的代码,请想象 for 循环是一个搜索,文件名是以字符串“str”传入的。
public class SearchFile
{
public void Search()
{
// File search is happening
for(int i=0;i<100;i++)
{
string str = "File " + i;
}
}
}
现在,上述组件在控制台应用程序中被消耗,如下所示。现在,我们希望在文件搜索完成后立即通知 UI 文件名。
static void Main(string[] args)
{
SearchFile f1 = new SearchFile();
f1.Search();
}
换句话说,我们需要从文件搜索组件返回一个回调到控制台应用程序。这正是委托的用途——回调、回调、回调。
不,根本不要这么想
作为开发人员,您可能会想,为什么不能直接在搜索方法本身中写入“Console.Write”,如下所示?但是,这样做会使您的“SearchFile”与 UI 技术紧密耦合。如果我们想在“Winform”或“Wpf”应用程序中使用以下代码呢?以下代码将崩溃。
public class SearchFile
{
public void Search()
{
// File search is happening
for(int i=0;i<100;i++)
{
string str = "File " + i;
console.writeline(str);
}
}
}
使用委托实现回调
现在,为了实现委托,第一件事是文件搜索组件必须通过公开一个委托来提供必要的机制。下面是“SearchFile”类,通过它我们公开了一个名为“wheretocall”的委托。请查看以下代码中的步骤 1 和步骤 2。此委托“wheretocall”目前是空的,它表示任何期望我回调的客户端请在此处传递您的方法指针。
在 for 循环内部,委托通过委托传递当前文件名的数据。
public class SearchFile
{
public delegate void WheretoCall(string status); // Step 1
public WheretoCall wheretocall = null; // Step 2
public void Search()
{
// File search is happening
for (int i = 0; i < 100; i++)
{
string str = "File " + i;
wheretocall(str); // Step 3
}
}
}
现在,消耗上述类的客户端需要将方法引用传递给委托指针。请参见下面的代码中的步骤 1。在此处,我们传递了我们期望“SearchFile”类回调并发送信息的方法。
static void Main(string[] args)
{
SearchFile f1 = new SearchFile();
f1.wheretocall = CallHere; // Step 1
f1.Search();
}
static void CallHere(string message)
{
Console.WriteLine(message);
}
多播委托
现在设想一种情况,我们希望“SearchFile”类一次性向三个方法发送通知(广播)… 我们也可以称之为发布者-订阅者架构。其中“SearchFile”是发布者,所有其他方法都是订阅者,附加到发布者。
static void CallHereToWriteToFile(string message)
{
System.IO.File.WriteAllText(@"c:\somtext.txt", message);
}
static void CallHereForConsole(string message)
{
Console.WriteLine(message);
}
static void CallHereToWriteInternally(string message)
{
messages.Add(message);
}
为了实现这一点,我们无需更改“SearchFile”类中的任何一行代码。在客户端,我们只需使用“+=”符号将方法分配给“wheretocall”委托。
如果您想取消订阅,可以使用“-=”符号。
static void Main(string[] args)
{
SearchFile f1 = new SearchFile();
f1.wheretocall += CallHereForConsole;
f1.wheretocall += CallHereToWriteInternally;
f1.wheretocall += CallHereToWriteToFile;
f1.Search();
}
现在,“SearchFile”类调用委托时,所有附加到委托的三种方法都会被调用。
事件:- 封装的委托
多播委托创建的发布者-订阅者架构存在一个严重问题,即订阅者可以修改委托。现在,在真正的发布者-订阅者架构或广播架构中,订阅者只能订阅和取消订阅。换句话说,他们只能收听,不能修改委托。
例如,在下面的代码中,您可以看到订阅者代码将发布者委托引用设置为空,并且他还可以调用委托。换句话说,客户端可以修改委托。如果您在现实世界中进行可视化,电视就工作在发布者-订阅者架构上。作为最终用户,我们可以订阅或取消订阅电视节目,但如果我们不喜欢某个节目,我们无权掌掴电视主持人。
SearchFile f1 = new SearchFile();
f1.wheretocall += CallHereForConsole;
f1.wheretocall.invoke();// The client can invoke.
f1.wheretocall = null; // The delegate can be modified.
f1.wheretocall += CallHereToWriteInternally;
f1.Search();
这时事件就派上用场了。事件封装委托。在发布者端,我们需要使用“event”关键字创建委托的实例。
public class SearchFile
{
public delegate void WheretoCall(string status);
public event WheretoCall wheretocall = null;
// Remaining code removed for clarity.
}
如果我们这样做,客户端就不能再修改委托了。您可以看到下面在订阅者端突出显示的错误,您只能使用“+=”或“-=”,但不能使用“=”。
如果我们把下面的错误翻译成简单的英语,那就是您只能订阅(+=)或取消订阅(-=)委托,而不能修改它们。因此,通过使用事件,我们实现了正确的发布者-订阅者模型。
总结
因此,如果我们总结一下:委托是回调,多播委托用于多个回调,而事件执行多播回调,但客户端对委托没有控制权。在事件的情况下,客户端只能订阅委托。
下面是一个简单的 YouTube 视频 C# 中的事件和委托,值得一看。
进一步阅读,请观看下面的面试准备视频和分步视频系列。