上一篇仅仅简单通过代码,以及相关运行示例,对委托有了基本概念。这一篇侧重对委托的更加深入的理解。有问题欢迎评论。本人技术不高,也欢迎指正。
希望看完我能够解释清楚以下问题,而大家能够从中找到自己的答案。
- 什么是委托,如何理解委托 ✔
- 委托的好处,为什么要使用委托
- 委托和事件的区别
- 为什么说委托是事件和回调方法的基础
- 如何理解事件是一种特殊的多播委托?
- 委托是如何使用的?委托的调用和函数的调用相比,其优势具体体现在什么地方?
- 在class的内部也可以申明委托? 此委托和在namespace下的委托有啥区别?
- 设计模式来讲,委托(类)提供了方法(对象)的抽象? 如何理解?
- 如果将委托理解为方法的抽象,那么和抽象类 基类又有什么区别
- 委托和接口又有什么区别, 接口和抽象类又有什么区别?为什么要弄两个概念,接口不够用吗,抽象类有什么独到的优势和特点? 如何理解接口是行为的抽象?
总述
对于委托还有没一点了解的,可以看我之前的一篇文章。
这篇文章,讲一下我最深刻的对于委托的理解:委托的使用, 完全可以理解为将函数作为参数传递。(这篇文章完全不讲事件,不牵扯事件,委托03将仔细讲委托和事件直接的关系)
有C基础的朋友,可以将委托理解为函数指针;有面向对象基础的朋友,则可以将其理解为一种引用类型的数据类型,就和class类一样,是C#中特有的一种数据类型。
如果class是面向对象的一个概念,将现实世界的实体对象化,那么委托(delegate)可以理解为一种将方法本身作为一个实体来操作的机制。在面向对象的世界里,我们通过类来定义对象的属性和行为,而委托则提供了一种将这些行为(即方法)独立出来,使其可以被动态地引用和执行的能力。
委托(Delegate)是一种特殊的类型,它可以持有对方法的引用。你可以把它想象成一个指向函数的指针,但它比指针更安全、更灵活。委托允许你将方法作为参数传递给其他方法,或者从方法中返回方法。
参考视频,十来分钟,可反复观看,讲解的非常好
C#基础教程 delegate 帮你理解委托,知道委托的好处, 不懂委托一定要看下!_哔哩哔哩_bilibili
1. 委托的优势
委托从大方向来讲,主要有两个用途: 模板函数和回调函数 (核心都是把函数作为参数来传递)
模板函数类似于一个模块化的功能,都固定好了,留有一个填空,这个填空会在调用时传递什么,我就填什么,也就是执行调用时传入的函数。
而回调函数有一个逻辑选择,即我调用你还是不调用你,里面的相关逻辑会触发回调。说白了,还是函数作为参数,只是叫法说法不一样罢了。
具体的:回调关系:某个方法可能会被使用,也可能不被调用
一群人给了我一把名片,当我需要帮助的时候,可以在这一把名片这个找到能给我提供帮助的人,然后打电话寻求帮助,打电话的过程其实就是回调的过程。调用那边的某个功能来帮我解决帮个问题。回调函数给我们了一个机会,让我们可以动态的选择将要被回调的方法(动态的选择的过程也就是委托的体现,把函数当作参数来传递)
委托调用其灵魂和核心: 1)函数指针 2)把函数巧妙转换成了实例化对象。委托类型的对象,就是函数指针。既然委托也是一种数据类型,这就意味着在编写函数的时候,可以传入委托这种数据类型的参数,而该参数是灵活可变动的,也就是说,在一个大函数中,传入的参数为委托类型,然后函数内部对该委托类型的参数做一些处理。而这个参数到底指向哪个函数,实例化哪个函数,是灵活可变动的。即:委托就是把函数当作参数来传递和使用!!!
举例(模板函数):
Handle 就是某个处理模块的一整套流程:
比方说,一家公涉及到设计 、生产、 销售 三个流程。设计和销售是不变的,都是由该公司去实现完成,生产过程需要外包委托给其它公司,而Handle函数就是模拟设计生产销售一整套流程。让用户输入数字就是设计, 而通过委托来调用不同的函数就是生产,最后控制台输出可以理解为销售。这样,当公司要执行业务流程的时候,只需要在main函数中直接调用Handle函数,并给出需要委托的生产商(即Handle函数传参到底是哪个),一切都很顺畅的执行。
//在不同的情况可能需要使用不同的处理方法,所以将处理写成委托形式,可以传参调用不同的处理
public void Handle(Cal cal)
{
//设计
double a;
double b;
Console.WriteLine("输入第一个数字:");
a = Convert.ToDouble(Console.ReadLine());
Console.WriteLine("输入第二个数字:");
b = Convert.ToDouble(Console.ReadLine());
//生产委托给别人
double result = cal.Invoke(a, b);
result += 5; //后续处理
Console.WriteLine($"处理的结果为{result}"); //销售
}
比方说想要委托富士康厂商来生产,就在main函数实现这个流程的时候,即调用Handle函数传参的时候传入需要的生产商,即参数即可。(结尾附完整代码,不懂的uu可以自行取用)
static void Main(string[] args)
{
Program prom = new Program();
Calculate calculate = new Calculate();
prom.Handle(calculate.Add); //找委托商 并且让委托商来帮我们做事
}
这么做的优势:Handle函数就固定下来了,形成一个功能模块化的东西,不用去到函数内部,仅通过外部的修改参数就可以实现委托生产商的改变。在大型项目中,Handle函数很有可能是别人先修改,你接触不到,只需要调用即可;Handle也大概率是封装起来的,如果每次生产商变了就到函数内部去修改,其灵活性和可变动行太差了,一点也不利于维护!
2. 委托和直接调用方法本身,其本质区别
直接函数调用:直接调用函数时,函数的调用是固定的,不能在运行时改变。
委托调用:委托可以在运行时动态地改变其指向的方法,这使得委托非常灵活,可以根据运行时的条件来决定调用哪个方法。
直接调用函数是指直接通过方法名来调用方法。这种方式在编译时就确定了方法的调用关系,编译器会将方法调用转换为对方法入口点的直接调用指令。
void Method() { /* ... */ }
Method();
在这种情况下,编译器生成的IL(中间语言)代码会包含一个直接调用指令(call
),直接跳转到Method
方法的入口点。
而使用委托调用函数时,实际上是通过委托实例来间接调用方法。这种方式在运行时才确定具体调用哪个方法。 (关于编译时和运行时下区别可以简单理解:编译时:代码——>exe可执行文件的过程;运行时:点击执行exe文件的过程)
delegate void MyDelegate();
class Program {
static void Method() { /* ... */ }
static void Main() {
MyDelegate d = new MyDelegate(Method);
d(); // 通过委托调用Method
}
}
在这种情况下,编译器生成的IL代码会包含一个对委托实例上Invoke
方法的调用指令(callvirt
),这是一个虚拟调用,因为委托可以引用任何符合委托签名的方法。
- 直接调用函数没有灵活性,因为它在编译时就确定了调用的目标。
- 委托调用提供了高度的灵活性,可以在运行时动态地改变要调用的方法。
- 在运行时,用户的不同操作会使得触发不同的委托函数执行不同的流程!!!
附: 完整代码
namespace ConsoleDel
{
public delegate double Cal(double a, double b); //声明委托
internal class Program
{
static void Main(string[] args)
{
Program prom = new Program();
Calculate calculate = new Calculate();
Cal cal = new Cal(calculate.Del);
prom.Handle(calculate.Add); //找委托商 并且让委托商来帮我们做事
prom.Handle(cal); //换生产商(另一种写法)
}
#region 委托:函数作为参数传递实例
//Handle 就是某个处理模块的一整套流程:
//一家公涉及到设计 生产 销售 三个流程,设计和销售是不变的,都是由该公司去实现完成,生产过程需要外包委托给其它公司
//1 .获取用户归纳兴趣的数字 (让用户输入数字就是设计)
//2. 对获取的数据做相关处理 (通过委托来调用不同的函数就是生产)
//3. 处理后拿到结果,做下一步的规划处理
//4. 销售 (最后控制台输出可以理解为销售)
// 这样,当公司要执行业务流程的时候,只需要在main函数中直接调用Handle函数,并给出需要委托的生产商,一切都很顺畅的执行。
//前后的一些模块同固定好了,就是中间不知道会用哪个方法,所以先写成流程化的东西,要改变的通过参数传递的方式去实现
#endregion
#region 具体实例
public void Handle(Cal cal)
{
//设计
double a;
double b;
Console.WriteLine("输入第一个数字:");
a = Convert.ToDouble(Console.ReadLine());
Console.WriteLine("输入第二个数字:");
b = Convert.ToDouble(Console.ReadLine());
//生产委托给别人
double result = cal.Invoke(a, b);
result += 5; //后续处理
Console.WriteLine($"处理的结果为{result}"); //销售
}
#endregion
}
}
namespace ConsoleDel
{
/// <summary>
/// 计算类,委托的扩展性和复用性
/// </summary>
public class Calculate
{
public void Report()
{
Console.WriteLine("there are five methods!");
}
public double Add(double a, double b)
{
Console.WriteLine($"del a+b = :+ {a + b}");
return a + b;
}
public double Sub(double a, double b)
{
Console.WriteLine($"del a-b = :+ {a - b}");
return a - b;
}
public double Mul(double a, double b)
{
Console.WriteLine($"del a*b = :+ {a * b}");
return a * b;
}
public double Del(double a, double b)
{
Console.WriteLine($"del a/b = :+ {a / b}");
return a / b;
}
}
}
效果如下