与买桂花同载酒 🌙

委托深入

再看委托

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System;

namespace ConsoleApp1
{
delegate int Calculator(int a, int b);

class MathOperation
{
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
}

class Zhao
{
public Zhao(Calculator a)
{
operation = a;
}
public Calculator operation;
}
internal class Program
{

static void Main(string[] args)
{
MathOperation math = new MathOperation();
Zhao zhao = new Zhao(math.Add);
//zhao.operation = new Calculator(math.Add);
int result1 = zhao.operation(10, 5);
Console.WriteLine("Addition: " + result1);
zhao.operation = math.Subtract;
int result2 = zhao.operation(10, 5);
Console.WriteLine("Subtraction: " + result2);
Console.ReadLine();
}
}
}

多播委托与返回值陷阱

其实 C# 里所有委托本质都是多播委托MulticastDelegate),可以用 += 把多个方法串起来一次性调用:

1
2
3
Calculator multi = math.Add;
multi += math.Subtract; // 现在同时挂了两个方法
multi(10, 5); // Add 和 Subtract 都会被调用

但这里藏着一个经典陷阱:当委托有返回值时,multi(10, 5) 只会返回最后一个方法的结果,前面方法(Add 返回的 15)被直接丢弃:

1
int r = multi(10, 5);  // r == 5(Subtract 的结果),Add 的 15 没了

想拿到每一次调用的返回值,得用 GetInvocationList() 把多播委托拆开逐个调用:

1
2
3
4
5
foreach (Calculator item in multi.GetInvocationList())
{
Console.WriteLine(item(10, 5));
}
// 输出:15 和 5

这也解释了为什么事件的标准签名都是 void——事件是「一对多通知」,本就不该指望一个统一的返回值。后面 Action 是 voidEventHandler 也是 void,都不是巧合。

委托和Func

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
namespace ConsoleApp1
{
public delegate int Calculator(int a, int b);
class MathOperation
{
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
}

class Zhao
{
public Calculator operation;
public Func<int, int, int> operationFunc;
public Zhao(Calculator calculator)
{
operation = calculator;
//operationFunc = calculator.Invoke;
//operationFunc = new Func<int, int, int>(calculator);
operationFunc = (a,b)=> calculator(a,b);
}
}
internal class Program
{

static void Main(string[] args)
{
var math = new MathOperation();
var xiaozhao = new Zhao(math.Add);
Console.WriteLine($"10 + 5 = {xiaozhao.operationFunc(10, 5)}");
xiaozhao.operation = math.Subtract;
Console.WriteLine($"10 - 5 = {xiaozhao.operationFunc(10, 5)}");

Console.ReadLine();
}
}
}

委托和Action

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
namespace ConsoleApp1
{
public delegate void Calculator(int a, int b);
class MathOperation
{
public void Add(int a, int b)
{
Console.WriteLine($"10 + 5 = {a+b}");
}
public void Subtract(int a, int b)
{
Console.WriteLine($"10 - 5 = {a - b}");
}
}

class Zhao
{
public Calculator operation;
public Func<int, int, int> operationFunc;
public Action<int, int> operationAction;
public Zhao(Calculator calculator)
{
operation = calculator;
//operationAction = calculator.Invoke;
operationAction = new Action<int, int>(calculator);
//operationAction = (a, b) => calculator(a, b);
}
}
internal class Program
{

static void Main(string[] args)
{
var math = new MathOperation();
var xiaozhao = new Zhao(math.Add);
xiaozhao.operationAction(10, 5);
//Console.WriteLine($"10 + 5 = {xiaozhao.operationFunc(10, 5)}");
xiaozhao.operationAction += math.Subtract;
//Console.WriteLine($"10 - 5 = {xiaozhao.operationFunc(10, 5)}");
xiaozhao.operationAction(10, 5);

Console.ReadLine();
}
}
}

委托和事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace ConsoleApp1
{
public delegate int Calculator(int a, int b);
class MathOperation
{
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
}

class Zhao
{
public Calculator operation;
public Func<int, int, int> operationFunc;
public Action<int, int> operationAction;
public event Calculator operationEvent;
public Zhao(Calculator calculator)
{
operationEvent = calculator;
}
public int OnoperationEvent(int a, int b)
{
return operationEvent(a, b);
}
}
internal class Program
{

static void Main(string[] args)
{
var math = new MathOperation();
var xiaozhao = new Zhao(math.Add);
Console.WriteLine($"10 + 5 = {xiaozhao.OnoperationEvent(10, 5)}");
xiaozhao.operationEvent -= math.Add;
xiaozhao.operationEvent += math.Subtract;
Console.WriteLine($"10 - 5 = {xiaozhao.OnoperationEvent(10, 5)}");

Console.ReadLine();
}
}
}

上面这段用到了 event 关键字,必须搞清楚它和普通委托字段的区别。Zhao 类里其实同时有两种写法:

1
2
public Calculator operation;            // 普通委托字段
public event Calculator operationEvent; // 事件

两者在类内部用法完全一样(赋值、调用都行),但对外部代码的限制天差地别:

操作 普通委托字段 event
外部 += / -= 订阅、退订
外部 = 直接覆盖 ✅(能把别人订阅全冲掉) ❌ 编译报错
外部直接 invoke() 触发 ❌ 编译报错

也就是说,event 给委托套了一层封装:外部只能订阅/退订,既不能清空别人的订阅,也不能替你触发事件。要是把上面的 event 换成普通字段,外部随随便便一句 xiaozhao.operationEvent = null; 就能把所有订阅者干掉——多人协作里这是灾难。

还有个细节:触发事件的标准写法是空条件调用 ?.Invoke,避免没有订阅者时抛 NullReferenceException

1
operationEvent?.Invoke(a, b);   // 没人订阅就什么都不做,绝不报错

本节 OnoperationEvent 里直接写 operationEvent(a, b) 没炸,是因为构造函数里保证了至少有一个订阅者。换成更通用的场景就该用 ?.Invoke——后面「集大成者」里 OnBeforeCalculate?.Invoke(this, args) 就是标准姿势。

集大成者

先让大家提条件(订阅拦截),外部传入具体的操作(加法/减法),最后引擎统一检查条件,通过了才真正执行操作。

这在软件工程中是一个极其经典的架构模式,叫做 拦截器模式(Interceptor Pattern) 或者 事件预处理模式(Pre-Event Pattern)。像 WPF 里的 PreviewMouseDown 事件,或者 ASP.NET 里的中间件(Middleware),底层用的全是你说的这种思想!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using System;

namespace ConsoleApp1
{
class MathOperation
{
public int Add(int a, int b)
{
return a + b;
}
public int Subtract(int a, int b)
{
return a - b;
}
}
public class CalculationEventArgs : EventArgs
{
public int A { get; set; }
public int B { get; set; }
public Func<int, int, int> Operation { get; set; }
public bool IsCancelled { get; set; }=false;
public string CancelReason { get; set; }=string.Empty;
}

class Zhao
{
public event EventHandler<CalculationEventArgs> OnBeforeCalculate;
public void Execute(int a, int b, Func<int, int, int> operation)
{
var args = new CalculationEventArgs { A = a, B = b, Operation = operation };
OnBeforeCalculate?.Invoke(this, args);
if (!args.IsCancelled)
{
int result = args.Operation(args.A, args.B);
Console.WriteLine($"Result: {result} 成功");
}
else
{
Console.WriteLine($"失败 Operation cancelled: {args.CancelReason}");
}
}
}
internal class Program
{

static void Main(string[] args)
{
var math = new MathOperation();
var xiaozhao = new Zhao();
EventHandler<CalculationEventArgs> rule1 = (sender,e) =>
{
if (e.A < 0 || e.B < 0)
{
e.IsCancelled = true;
e.CancelReason = "参数不能为负数";
}
};
EventHandler<CalculationEventArgs> rule2 = (sender, e) =>
{
if (e.Operation == math.Subtract && e.A < e.B)
{
e.IsCancelled = true;
e.CancelReason = "减法运算中,参数A不能小于参数B";
}
};
xiaozhao.OnBeforeCalculate += rule1;
xiaozhao.OnBeforeCalculate += rule2;
Console.WriteLine("测试1");
xiaozhao.Execute(5, 3, math.Add);
Console.WriteLine("测试2");
xiaozhao.Execute(-5, 3, math.Add);
Console.WriteLine("测试3");
xiaozhao.Execute(3, 5, math.Subtract);

Console.ReadLine();
}
}
}