委托和事件的 6 个重要用法






4.84/5 (204投票s)
委托和事件的 6 个重要用法
委托和事件的 6 个重要用法
引言 方法和函数的抽象问题 如何声明委托? 使用委托解决抽象指针问题 多播委托 多播委托的简单演示 多播委托的问题——暴露无遗 事件——委托的封装 实现事件 委托和事件的区别 用于异步方法调用的委托 总结委托的用途 委托和C++指针的区别 源代码 |
如果您不想阅读我完整的文章,可以观看以下有关C#中事件和委托的视频。
引言
在本文中,我们将首先尝试理解委托解决了什么问题,然后创建一个简单的委托并尝试解决该问题。接下来,我们将尝试理解多播委托的概念以及事件如何帮助封装委托。最后,我们将理解事件和委托之间的区别,并了解如何异步调用委托。
当我们完成所有基础知识后,将总结委托的六个重要用途。
这是一本为所有.NET朋友准备的小型电子书,涵盖了WCF、WPF、WWF、Ajax、Core .NET、SQL等主题,您可以从这里下载。
方法和函数的抽象问题
在我们继续讨论委托之前,让我们先尝试理解委托解决了什么问题。下面是一个名为“ClsMaths”的简单类,其中包含一个简单的“Add”函数。这个“ClsMaths”类由一个简单的UI客户端使用。现在,假设随着时间的推移,您在“ClsMaths”类中添加了减法功能,您的客户端也需要相应地进行更改以适应新功能。
换句话说,在类中添加新功能会导致UI客户端重新编译。
x`
简而言之,问题在于函数名与UI客户端之间存在紧密耦合。那么如何解决这个问题呢?与其引用UI/客户端中的实际方法,不如引用一个指向这些方法的抽象指针,这样我们就可以将函数与UI解耦。
之后,对“ClsMath”类的任何更改都不会影响UI,因为更改将由抽象指针解耦。这个抽象指针可以使用委托来定义。委托定义了指向函数/方法的简单抽象指针。
好的,既然我们已经理解了紧密耦合问题以及解决方案,让我们尝试理解如何定义一个简单的委托。
如何声明委托?
实现委托是一个四步过程:声明、创建、指向和调用。
第一步是声明具有相同返回类型和输入参数的委托。例如,上面的“add”函数有两个整数输入参数和一个整数输出参数。
private int Add(int i,int y) { return i + y; }
因此,对于上面的方法,委托需要以下面的方式定义。请注意代码中附加的“delegate”关键字。
// Declare delegate public delegate int PointetoAddFunction(int i,int y);
委托的返回类型和输入类型需要兼容,如果不兼容,将显示如下错误,如以下图片所示。
下一步(第二步)是创建委托引用。
// Create delegate reference PointetoAddFunction myptr = null;
第三步是将委托引用指向方法,目前我们需要将委托引用指向add函数。
// Point the reference to the add method myptr = this.Add;
最后,我们需要使用委托的“Invoke”函数来调用委托函数。
// Invoke the delegate myptr.Invoke(20, 10)
下图总结了上述四个步骤如何映射到代码片段。
使用委托解决抽象指针问题
为了解耦算法更改,我们可以通过一个抽象委托公开所有以下算术函数。
因此,第一步是添加一个通用的委托,它产生一个输出并接受两个输入,如下面的代码片段所示。
public class clsMaths { public delegate int PointerMaths(int i, int y); }
下一步是公开一个函数,该函数接受操作并将一个附加的委托公开给UI,如下面的代码片段所示。
public class clsMaths { public delegate int PointerMaths(int i, int y); public PointerMaths getPointer(int intoperation) { PointerMaths objpointer = null; if (intoperation == 1) { objpointer = Add; } else if (intoperation == 2) { objpointer = Sub; } else if (intoperation == 3) { objpointer = Multi; } else if (intoperation == 4) { objpointer = Div; } return objpointer; } }
下面是完整的代码片段。所有算法函数,即“Add”、“Sub”等,都被设为私有,只公开一个通用的抽象委托指针,可用于调用这些算法函数。
public class clsMaths { public delegate int PointerMaths(int i, int y); public PointerMaths getPointer(int intoperation) { PointerMaths objpointer = null; if (intoperation == 1) { objpointer = Add; } else if (intoperation == 2) { objpointer = Sub; } else if (intoperation == 3) { objpointer = Multi; } else if (intoperation == 4) { objpointer = Div; } return objpointer; } private int Add(int i, int y) { return i + y; } private int Sub(int i, int y) { return i - y; } private int Multi(int i, int y) { return i * y; } private int Div(int i, int y) { return i / y; } }
因此,在客户端,调用变得通用,没有任何与实际方法名称(如“Add”、“Sub”等)的耦合。
int intResult = objMath.getPointer(intOPeration).Invoke(intNumber1,intNumber2);
多播委托
在我们之前的例子中,我们看到了如何创建指向函数或方法的委托指针。我们还可以创建指向多个函数和方法的委托。如果我们调用这样的委托,它将依次调用与该委托相关的所有方法和函数。
下面的代码片段将两个方法,即method1和method2,附加到委托“delegateptr”。为了添加多个方法和函数,我们需要使用“+=”符号。现在,如果我们调用委托,它将首先调用“Method1”,然后调用“Method2”。调用发生的顺序与附加的顺序相同。
// Associate method1 delegateptr += Method1; // Associate Method2 delegateptr += Method2; // Invoke the Method1 and Method2 sequentially delegateptr.Invoke();
那么我们如何在实际项目中 M使用多播委托呢?很多时候我们想创建发布者/订阅者模型。例如,在一个应用程序中,可以有各种错误日志记录例程,并且一旦应用程序发生错误,您就会希望将错误广播到相应的组件。
多播委托的简单演示
为了更好地理解多播委托,让我们做以下演示。在这个演示中,我们有“Form1”、“Form2”和“Form3”。“Form1”有一个多播委托,它将事件传播给“Form2”和“Form3”。
在“Form1”的窗体级别(此窗体将事件传播给form2和form3),我们将首先定义一个简单的委托以及该委托的引用,如下面的代码片段所示。此委托将负责向其他窗体广播事件。
// Create a simple delegate public delegate void CallEveryOne(); // Create a reference to the delegate public CallEveryOne ptrcall=null; // Create objects of both forms public Form2 obj= new Form2(); public Form3 obj1= new Form3();
在窗体加载时,我们以多播方式(+=)调用窗体并附加存在于两个窗体中的“CallMe”方法。
private void Form1_Load(object sender, EventArgs e) { // Show both the forms obj.Show(); obj1.Show(); // Attach the form methods where you will make call back ptrcall += obj.CallMe; ptrcall += obj1.CallMe; }
最后,我们可以调用并向两个窗体广播方法调用。
private void button1_Click(object sender, EventArgs e) { // Invoke the delegate ptrcall.Invoke(); }
多播委托的问题——暴露无遗
上述代码的第一个问题是,订阅者(form2和form3)无权表明他们对事件感兴趣或不感兴趣。这一切都由“form1”决定。
我们可以走另一条路,即:将委托传递给订阅者,让他们在希望订阅“form1”发送的广播时附加他们的方法。但这会导致另一组问题,即封装违反。
如果我们向订阅者公开委托,他们可以调用委托,添加自己的函数等。换句话说,委托对订阅者来说是完全暴露的。
事件——委托的封装
事件有助于解决委托的封装问题。事件位于委托之上,提供封装,以便目标源只能收听而不能完全控制委托对象。
下图显示了事物的样子:
• 方法和函数通过委托进行抽象/封装
• 通过使用多播委托,委托得到了进一步扩展,以提供广播模型。
• 多播委托通过事件进一步封装。
实现事件
因此,让我们以使用多播委托的相同示例,并尝试使用事件来实现它。事件在内部使用委托,因为事件 over 委托提供了更高级别的封装。
因此,在发布者(“Form1”)中的第一步是定义委托和该委托的事件。下面的代码片段就是如此,请注意“event”关键字。
我们定义了一个委托“CallEveryOne”,并为该委托指定了一个名为“EventCallEveryOne”的事件对象。
public delegate void CallEveryone(); public event CallEveryone EventCallEveryOne;
从发布者,即“Form1”,创建“Form2”和“Form3”对象,并附加当前的“Form1”对象,以便“Form2”和“Form3”可以收听事件。对象附加后,引发事件。
Form2 obj = new Form2(); obj.obj = this; Form3 obj1 = new Form3(); obj1.obj = this; obj.Show(); obj1.Show(); EventCallEveryOne();
在订阅者端,即(Form2和Form3),将方法附加到事件监听器。
obj.EventCallEveryOne += Callme;
这段代码将显示与我们从多播委托示例中获得的结果相同的结果。
委托和事件的区别
那么,除了事件的糖衣语法之外,委托和事件之间到底有什么区别?如前所述,主要区别在于事件比委托提供了一个更高级别的封装。
因此,当我们传递委托时,它是裸露的,并且目标/订阅者可以修改委托。当我们使用事件时,目标只能收听它。
用于异步方法调用的委托
委托的另一个用途是异步方法调用。您可以异步调用委托指向的方法和函数。
异步调用意味着客户端调用委托,并且立即将控制权返回给进一步执行。委托与主调用者并行运行。当委托完成其工作时,它会调用调用者,告知函数/子例程已完成执行。
要异步调用委托,我们需要调用“begininvoke”方法。在“begininvoke”方法中,我们需要指定当前的回调方法“CallbackMethod”。
delegateptr.BeginInvoke(new AsyncCallback(CallbackMethod), delegateptr);
下面的代码片段是“CallbackMethod”。一旦委托完成其任务,就会调用此方法。
static void CallbackMethod(IAsyncResult result) { int returnValue = flusher.EndInvoke(result); }
总结委托的用途
委托有 6 个重要用途:
1. 抽象和封装方法(匿名调用)
这是委托最重要的用途;它帮助我们定义一个可以指向方法和函数的抽象指针。同一个抽象委托稍后可以用来指向该类型的方法和函数。在上一节中,我们展示了一个简单的数学类示例。后来添加新的算法函数不会影响UI代码。
2. 回调机制很多时候我们希望提供一个回调机制。委托可以传递给目的地,目的地可以使用相同的委托指针进行回调。
3. 异步处理通过使用“BeginInvoke”和“EndInvoke”,我们可以异步调用委托。我们在上一节中详细解释了这一点。
4. 多播——顺序处理有时我们希望按顺序调用某些方法,这可以通过使用多播委托来完成。这已经在上面显示的多播示例中进行了说明。
5. 事件——发布者订阅模型我们可以使用事件来创建纯粹的发布者/订阅者模型。
委托和C++指针的区别
C++指针不是类型安全的,换句话说,它可以指向任何类型的方法。另一方面,委托是类型安全的。指向整数返回类型的委托不能指向字符串返回类型。
源代码
您可以从这里下载本教程的源代码。
进一步阅读,请观看下面的面试准备视频和分步视频系列。