目录
一,什么是委托?
1 .1官方关于委托的概述
(1) 委托是一种引用类型,表示对具有特定参数列表和返回类型的方法的引用。
(2) 在实例化委托时,你可以将其实例与任何具有兼容签名和返回类型的方法相关联。 委托可以链接在一起,一次性调用多个方法
(3) 你可以通过委托实例调用方法。
(4) 委托用于将方法作为参数传递给其他方法,可用于定义回调方法
(5) 可将任何可访问类或结构中与委托类型匹配的任何方法分配给委托。该方法可以是静态方法,也可以是实例方法。 此灵活性意味着你可以通过编程方式来更改方法调用,还可以向现有类中插入新代码。
(6) 委托类型派生自 .NET 中的 Delegate 类。 委托类型是密封的,它们不能派生自 Delegate,也不能从其派生出自定义类
1.2 通俗解释
从字面意思上说,“委托”就是一件事,我们不亲自去做,而是把这件事交给其他人帮忙做;但从编程语言的角度来说,编程语法机制也是一种C#语言对现实世界逻辑的抽象。
委托可以看做出C++中指针的升级版,它的 本质是一个类。其中定义了方式的类型,使得可以将方法作为另一个方法的参数进行传递。这种写法可以避免程序中存在大量的If else/switch语句,同时提高了程序的扩展性。
二,怎么使用委托?
2.1 委托的定义(声明)
对于委托的定义主要有以下几种:
//1.无参数无返回值在类外部委托
public delegate void NoReturnNoParaOutClass();
class CustomDelegate
{
//2.无参数无返回值在类内部委托
public delegate void NoReturnNoPara();
//3.有参数无返回值委托
public delegate void NoReturnWithPara(int x, int y);
//4.无参数有返回值委托
public delegate int NoParaWithReturn();
//5.有参数有返回值委托
public delegate int WithParaWithReturn(int x, int y);
}
由于委托是继承自特殊类MulticastDelegate,因此在使用时需要实例化:通过New来实例化,且要求传递一个和这个委托的参数和返回值完全匹配的方法。
2.2 委托的实例化
实例化方式主要有三种:
- 采用New关键字
- 使用语法糖(直接指向方法,简化New)
- 指向类型一致的lambad表达式。
public delegate int WithParaWithReturn(int x, int y);
//实例化方式1
WithParaWithReturn withParaWithReturn = new WithParaWithReturn(method);
//实例化方式2
WithParaWithReturn withParaWithReturn1 = method; //采用语法糖
//实例化方式3
WithParaWithReturn withParaWithReturn2 = (x,y)=> { return 0; };
//有返回值有参数的方法
public static int method(int a,int b)
{
return a + b;
}
2.3 委托的调用
-
直接调用委托的实例对象,如:withParaWithReturn1(3,2);
-
使用Invoke(),如:withParaWithReturn1.Invoke(3,2);
-
如果希望异步调用,则用BeginInvoke调用。(即另开一个线程执行)
-
EndInvoke等待BeginInvoke执行完成后再执行。
2.4 委托的使用实例
//【1】申明委托
public class DelegateTest
{
// 1.无参数无返回值委托
public delegate void NoReturnNoPara();
// 2.有参数无返回值委托
public delegate void NoReturnWithPara(int x, int y);
// 3.无参数有返回值的委托
public delegate int WithReturnNoPara();
// 4.带参数带返回值的委托
public delegate int WithReturnWithPara(out int x, out int y);
}
class Program
{
//【2】定义委托相关的方法
private static void NoReturnNoParaMethod()
{
Console.WriteLine("无参数,无返回值的方法");
}
private static void NoReturnWithParaMethod(int s, int t)
{
Console.WriteLine("有参数,无返回值的方法");
}
static void Main(string[] args)
{
//【3】实例化委托
//使用new 实例
DelegateTest.NoReturnNoPara noReturnNoPara= new DelegateTest.NoReturnNoPara(NoReturnNoParaMethod);
//使用赋值的方式实例
DelegateTest.NoReturnWithPara noReturnWithPara = NoReturnWithParaMethod;
//使用匿名委托实例
DelegateTest.WithReturnNoPara withReturnNoPara = delegate()
{
Console.WriteLine("无参数,有返回值的方法");
return default(int);
};
//使用lambda 匿名方法实例
DelegateTest.WithReturnWithPara WithReturnWithPara = (out int x, out int y) =>
{
x = 1;
y = 2;
Console.WriteLine("有参数,有返回值的方法");
return x + y;
};
//【4】调用委托
//使用委托变量调用
noReturnNoPara();
//使用invoke调用
//【Invoke】执行方法,如果委托定义没有参数,则invoke也没有参数,委托没有返回值,则invoke也没有返回值
noReturnNoPara.Invoke();
int result= withReturnNoPara.Invoke();//调用有返回值,无参数的委托
int x1, y1;
int result2 = WithReturnWithPara.Invoke(out x1,out y1);//调用有返回值,有参数的委托
//使用BeginInvoke
//【BeginInvoke】开启一个线程去执行委托,NetCore不支持,NetFamework支持 NetCore有更好的多线程功能来支持实现类似功能
noReturnWithPara.BeginInvoke(1,2,null,null);
//【EndInvoke等待BeginInvoke方法执行完成后再执行EndInvoke后面的代码】
//noReturnWithPara.EndInvoke();
Console.ReadLine();
}
}
三,委托实现嵌套中间件
3.1 委托+Lambad表达式=简单嵌套实现
- (1)声明一个委托,并定义一个普通类CustomClass,用于实现核心的业务逻辑。
public delegate void ShowDelegate();
class CustomClass
{
public void Method()
{
Console.WriteLine("核心业务逻辑运行");
}
}
- 主程序中创建多个委托实例,并通过Lambad表达式迭代调用。
static void Main(string[] args)
{
ShowDelegate showMthod1 = new ShowDelegate(() =>
{
Console.WriteLine("showMthod1执行前");
new CustomClass().Method();
Console.WriteLine("showMthod1执行后");
});
ShowDelegate showMthod2 = new ShowDelegate(() =>
{
Console.WriteLine("showMthod2执行前");
showMthod1.Invoke();
Console.WriteLine("showMthod2执行后");
});
ShowDelegate showMthod3 = new ShowDelegate(() =>
{
Console.WriteLine("showMthod3执行前");
showMthod2.Invoke();
Console.WriteLine("showMthod3执行后");
});
showMthod3.Invoke();
}
最后效果:
3.2 委托+特性+反射=花式嵌套实现
- (1)声明委托并定义抽象特性以及子类特性(子类用于实现嵌套业务)
public delegate void ShowDelegate();
//定义抽象特性
public abstract class AbstractMethodAttribute:Attribute
{
public abstract ShowDelegate Do(ShowDelegate action);
}
//定义子类特性实现Log业务
public class DelegateLogAttribute:AbstractMethodAttribute
{
//LOG业务嵌套
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
Console.WriteLine("在执行LOG之前");
action.Invoke();
Console.WriteLine("在执行LOG之后");
});
return actionResult;
}
}
//定义子类特性实现Error业务
public class DelegateErrorAttribute : AbstractMethodAttribute
{
//Error业务嵌套
public override ShowDelegate Do(ShowDelegate action)
{
ShowDelegate actionResult = new ShowDelegate(() =>
{
Console.WriteLine("在执行ERROR之前");
action.Invoke();
Console.WriteLine("在执行ERROR之后");
});
return actionResult;
}
}
- (2)定义一个类用于实现核心业务逻辑,并标记特性
public class CustomClass
{
[DelegateLog]
[DelegateError]
public void Method()
{
Console.WriteLine("花式嵌套业务核心");
}
}
- (3)主程序通过反射调用特性
static void Main(string[] args)
{
CustomClass customClass = new CustomClass();
Type type = customClass.GetType();
MethodInfo methodInfo = type.GetMethod("Method");
//给委托赋值,初始委托方法
ShowDelegate showMethod = new ShowDelegate(() =>
{
customClass.Method();
});
//判断是否定义特性对每个特性进行执行
//继承自父类的特性都算
if (methodInfo.IsDefined(typeof(AbstractMethodAttribute), true))
{
//Reverse越靠近方法越先执行
foreach (AbstractMethodAttribute attribute in methodInfo.GetCustomAttributes().Reverse())
{
//把初始方法传入,返回封装好的委托再作为下一个参数传入
showMethod = attribute.Do(showMethod);
}
}
//执行委托
showMethod.Invoke();
}
最后效果:
🧡🧡🧡 这种写的好处在哪?🧡🧡🧡
如果我们增加一个最新的业务逻辑,比如在LOG业务之前进行CHECK检查,只需要再写一个CHECK特性继承自抽象特性,然后在实现核心业务逻辑类里进行标记即可。
最终效果:
在执行CHECK之前
在执行LOG之前
在执行ERROR之前
花式嵌套业务核心
在执行ERROR之后
在执行LOG之后
在执行CHECK之后
四,泛型委托
4.1 自定义泛型委托
//【1】定义了具有两个泛型参数类型的委托
public delegate void CustomDelegate<T, V>(T t, V v);
public class TestCustomDelegate
{
//【2】申明委托变量,这时需指明数据类型
CustomDelegate<string, string> customDelegate;
//【3】编写与委托对应的方法
public void TestMethod(string a,string b)
{
Console.WriteLine($"{a}拼接{b}");
}
public void Result()
{
//【4】实例化
customDelegate = TestMethod;
customDelegate = new CustomDelegate<string, string>(TestMethod);
customDelegate = delegate (string a1, string b1) { Console.WriteLine("delegate匿名方法也可以"); };
customDelegate = (string a2, string b2) => { Console.WriteLine("使用lambda匿名方法"); };
//【5】执行委托
customDelegate("test1","test2");
customDelegate.Invoke("test1", "test2");
}
}
由于单独定义委托和事件,比较繁琐,而且比较冗余,因此C#2.0提供了Action 和Func两个泛型委托,不用单独申明,拿来就可以用。
4.2 框架内置委托(泛型委托Action/Func)
(1)Action
- Action 表示无参,无返回值的委托
- Action<int,string> 表示有参,无返回值的泛型委托,最多可入参16个
- 使用Action 就可以囊括所有无返回值委托,可以说Action是对无返回值委托的进一步包装
Action本身就代表无返回,无参数,因此将void 和 ()一省略,只剩下Action action #Action 为我们编程提供了方便,哪里需要就在哪里直接定义使用,不需要单独去定义
public class ActionTest
{
public Action action;
public void SendMsg()
{
Console.WriteLine("消息完成发送");
}
public void Test()
{
//实例化方式有四种,如下
action = SendMsg;
action = new Action(SendMsg);
action = delegate(){ Console.WriteLine("delegate 匿名方法"); };
action = () => { Console.WriteLine("lambda 匿名方法"); };
//调用方式
action();
action.Invoke();
}
}
(2) Func
- Func 表示有返回值的委托(必须有返回值)
- Func可以无参数,也可以有参数,最多16个参数,最后一个表示返回值且只有一个
public class FuncTest
{
public Func<int,int> func;
public int DoubleNumber(int number)
{
Console.WriteLine("计算完成");
return number * 2;
}
public void Test()
{
//实例化有以下4种方式
func = DoubleNumber;
func = new Func<int, int>(DoubleNumber);
func = delegate (int a)
{
Console.WriteLine("delegate 匿名方法");
return a * 2 ;
};
func = (int b) =>
{
Console.WriteLine("lambda 匿名方法");
return b * 2;
};
//调用
int result = func(10);
int result2 = func.Invoke(10);
}
}
五,多播委托和事件
- 委托都是继承自MulticastDelegate(多播委托),定义的所有的委托都是多播委托
- 可以通过+=把多个方法添加到这个委托中,形成一个方法的执行链,执行委托的时候,按照添加方法的顺序,依次去执行方法
- 可以通过-=移除方法,是从后往前,逐个匹配,如果匹配不到,就不做任何操作,如果匹配到,就把当前这个移除,且停止去继续往后匹配
- action.BeginInvoke();会开启一个新的线程 去执行委托,注册有多个方法的委托,不能使用BeginInvoke
- 注册有多个方法的委托想要开启新线程去执行委托,可以通过action.GetInvocationList()获取到所有的委托,然后循环,每个方法执行的时候可以BeginInvoke
现在有一个需求:猫叫之后引发一系列的动作(小孩哭,大人醒,打开灯)。
一般方式:封装一个方法,调用一系列动作。
public void Miao()
{
Console.WriteLine("{0} Miao", this.GetType().Name);
new Baby().Cry(); //小孩哭了
new Men().awake();//大人醒了
new light().Open(); //灯打开
}
整个代码职责不单一,依赖于其他的类太多,代码不稳定,任何一个类的修改,都有可能会影响到这只猫
基于面向对象的观察者模式:引发的动作注册到方法列表中去
(1)定义一个接口,所有引发的动作类都继承该接口。
public interface IObject
{
void Invoke();
}
public class Men:IObject
{
public void Invoke()
{
Console.WriteLine("大人醒来");
}
}
public class Baby : IObject
{
public void Invoke()
{
Console.WriteLine("小孩哭了");
}
}
public class light : IObject
{
public void Invoke()
{
Console.WriteLine("灯被打开");
}
}
(2)定义猫cat类,通过list列表获取引发的动作。
public class cat
{
public List<IObject> objectList = new List<IObject>();
public void MiaoObsever()
{
Console.WriteLine("喵咪叫了“喵~");
if (objectList.Count>0)
{
foreach (var item in objectList)
{
item.Invoke();
}
}
}
}
(3)主程序调用
static void Main(string[] args)
{
cat cat = new cat();
cat.objectList.Add(new Baby());// 小孩哭了
cat.objectList.Add(new Men()); // 大人醒来
cat.objectList.Add(new light()); // 灯被打开
cat.MiaoObsever();//执行
}
可以看的,猫只是执行方法列表,方法列表的注册交给第三方,不在猫的内部。
基于委托的观察者模式:引发的动作注册到多播委托中去
public class cat
{
public Action MiaoDelegateHandler = null;
public void MiaoDelegate()
{
Console.WriteLine("喵咪叫了“喵~");
MiaoDelegateHandler?.Invoke(); //?. 如果不为null ,就执行后面的动作
}
}
//程序调用
static void Main(string[] args)
{
cat cat = new cat();
cat.MiaoDelegateHandler += new Baby().Cry; // 小孩哭了
cat.MiaoDelegateHandler += new Men().awake; // 大人醒来
cat.MiaoDelegateHandler += new light().Open; // 灯被打开
cat.MiaoDelegate();//执行
}
可以看的,猫cat类只是执行委托的方法链,方法链注册交个第三方,不在猫内部
基于事件的观察者模式: 引发的动作注册到事件中去
和基于委托的观察者模式类似,只需要在 public Action MiaoDelegateHandler = null;中加入event关键字。
🧡🧡🧡 既然多播委托和事件写法区别只在于少一个event关键字,事件的作用是什么?🧡🧡🧡
在上面基于多播委托的观察者模式实例中,我们可以在方法内部通过cat的委托实例对象直接调用内部的委托MiaoEventHandler。
也就是说,在外部程序中,不用通过猫叫就可以触发一系列动作,违背了设计需求。(要求是只能是通过猫叫触发!)
因此通过对event关键字将委托声明为事件,事件只能在当前类被访问,使得子类和类外部均不能执行类中的事件方法。
六,WinForm中按钮点击事件解析
以WinForm中最常见的Button按钮绑定的Click事件为例,我们在页面上添加登录按钮双击生成了一个方法,运行起来,点击按钮,触发这个方法,这个过程是怎么完成的?
- 首先,按钮是一个Button类,继承自Control类,在Control类中有一个Click事件。
EventHandler(object? sender, EventArgs e)
- MyWinForm构造函数函数中有一个InitializeComponent方法,在InitializeComponent方法中初始化Button按钮实例,Button的实例中的Click事件+=一个动作btnLogin_Click方法。
- 然后点击按钮,触发事件,并执行事件绑定的方法,也就是btnLogin_Click方法。
添加按钮,注册事件
namespace MyWinform
{
partial class MyWinForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.btnLogin = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// btnLogin
//
this.btnLogin.Location = new System.Drawing.Point(78, 59);
this.btnLogin.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.btnLogin.Name = "btnLogin";
this.btnLogin.Size = new System.Drawing.Size(73, 25);
this.btnLogin.TabIndex = 0;
this.btnLogin.Text = "登录";
this.btnLogin.UseVisualStyleBackColor = true;
this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click);
//
// MyWinForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(312, 213);
this.Controls.Add(this.btnLogin);
this.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
this.Name = "MyWinForm";
this.Text = "MyWinForm";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button btnLogin;
}
}
添加执行逻辑
using System;
using System.Windows.Forms;
namespace MyWinform
{
public partial class MyWinForm : Form
{
public MyWinForm()
{
InitializeComponent();
}
private void btnLogin_Click(object sender, EventArgs e)
{
MessageBox.Show("触发了点击事件");
}
}
}
运行效果:
6.1 制定事件的标准流程
-
发布者发布事件
-
订阅者订阅事件
-
触发事件
(1)定义发布者
// 发布者:对外发布事件;触发事件;
public class Publisher
{
//发布事件
public event EventHandler Publish;
//发布者触发事件
public void EventAction()
{
Console.WriteLine("触发事件");
Publish?.Invoke(null,null);
}
}
(2)定义订阅者
// 订阅者:对发布者发布的事情关注
public class Observer1
{
// 订阅者1的行为
public void Action1(object sender, EventArgs e)
{
Console.WriteLine("订阅者1的行为");
}
}
// 订阅者:对发布者发布的事情关注
public class Observer2
{
// 订阅者2的行为
public void Action2(object sender, EventArgs e)
{
Console.WriteLine("订阅者2的行为");
}
}
(3)发布订阅触发事件
//初始化发布者
Publisher publisher = new Publisher();
//初始化订阅者1
Observer1 observer1 = new Observer1();
//初始化订阅者2
Observer2 observer2 = new Observer2();
//订阅者订阅事件
publisher.Publish += observer1.Action1;
publisher.Publish += observer2.Action2;
//触发事件
publisher.EventAction();
运行结果:
触发事件
订阅者1的行为
订阅者2的行为