Done is better than perfect

0%

委托与事件

委托

什么是委托?委托与事件有什么关系呢?先来看一下委托的定义,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace DotnetExample
{
//定义一个委托
public delegate void VoidCallback();

// Console必须要一个Main函数才能编译通过
class Program
{
static void Main(string[] args)
{
}
}
}
这样的就定义了一个返回值和参数都为空的委托,这个委托究竟是什么呢,先别急,我来看哈,这个委托转换成CIL是什么样的,CIL代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.class public auto ansi sealed DotnetExample.VoidCallback extends [System.Runtime]System.MulticastDelegate
{
.method public hidebysig specialname rtspecialname instance default void .ctor([System.Runtime]System.Object 'object', native int 'method') runtime managed
{
} // End of method System.Void DotnetExample.VoidCallback::.ctor(System.Object,System.IntPtr)
.method public hidebysig newslot virtual instance default void Invoke() runtime managed
{
} // End of method System.Void DotnetExample.VoidCallback::Invoke()
.method public hidebysig newslot virtual instance default [System.Runtime]System.IAsyncResult BeginInvoke(class [System.Runtime]System.AsyncCallback callback, [System.Runtime]System.Object 'object') runtime managed
{
} // End of method System.IAsyncResult DotnetExample.VoidCallback::BeginInvoke(System.AsyncCallback,System.Object)
.method public hidebysig newslot virtual instance default void EndInvoke(class [System.Runtime]System.IAsyncResult result) runtime managed
{
} // End of method System.Void DotnetExample.VoidCallback::EndInvoke(System.IAsyncResult)
} // End of class DotnetExample.VoidCallback
现在我们明白了原来委托就是一个继承至System.MulticastDelegate的类,delegate关键字就是一个语法糖而已。MulticastDelegate类又继承至Delegate。看看Delegate都有哪些内容,如下图: Delegate函数

其中重要的几个函数:

  • 构造函数 .ctor(Type, String), .ctor(Object String),当更一个委托赋值(=)时,会根据方法类型选择调用哪个构造函数,静态的函数调用第一种形式的构造函数,如果是成员函数的则会调用第二种形式的构造函数
  • Combine函数, 当使用+=的时候则会调动Combine将一个委托追加到调用链的后面。
  • CreateDelegate静态函数,更具各种类型创建委托
  • GetInvocationList函数,获取委托链上的说有委托
  • Remove函数,当使用-=的时候回调用Remove函数,它跟Combine函数是一对的,一个负责添加委托,一个负责移除委托。

上面定义了一个VoidCallback委托,下面我们来使用一下这个委托,代码如下:

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
namespace DotnetExample
{
class Program
{
static void Main(string[] args)
{
new DelegateExample().Init();
}
}

public delegate void VoidCallback();

class DelegateExample
{
public VoidCallback voidCallback;

public void Init()
{
voidCallback = TestCallback;
voidCallback += TestStaticCallback;

voidCallback.Invoke();

voidCallback -= TestCallback;
voidCallback -= TestStaticCallback;
}

void TestCallback()
{
Console.WriteLine("TestCallback is called.");
}

static void TestStaticCallback()
{
Console.WriteLine("TestStaticCallback is called.");
}
}
}

上面这些函数都是Delegate类里面的功能函数,其中“=”运算换转换成构造函数,“+=”会转换成Combine函数,“-=”会被转换成Remove函数。那么Invoke函数是哪里来的呢?在上面我们定义了一个委托,编译后给我们生成了一个类,这个类里面包含了3个函数其中一个就是Invoke,还是两个是BeginInvoke和EndInvoke函数,这几个函数有什么区别呢?Invoke是同步函数在主线程执行而BeginInvoke和EndInvoke是用于异步调用的一对函数在其他线程运行。BeginInvoke和EndInvoke如何使用,可以参考https://www.cnblogs.com/canger/p/5938591.html

事件

我们还是通过CIL来了解事件吧!先定义个事件看看转换后的CIL代码是什么样的,事件定义代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace DotnetExample
{
public delegate void VoidCallback();

class Program
{
public static event VoidCallback eventFields;

static void Main(string[] args)
{
}
}
}
删除了无关代码后,IL代码如下:
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
.class private auto ansi beforefieldinit DotnetExample.Program extends [System.Runtime]System.Object
{
// eventFields这个字段被转换成里private的访问权限
.field private static class DotnetExample.VoidCallback eventFields

// 生成了一个add_xxxx(字段名)的函数
.method public hidebysig specialname static default void add_eventFields(class DotnetExample.VoidCallback 'value') cil managed
{
// Method begins at Relative Virtual Address (RVA) 0x2050
// Code size 39 (0x27)
.maxstack 3
.locals init(class DotnetExample.VoidCallback V_0, class DotnetExample.VoidCallback V_1, class DotnetExample.VoidCallback V_2)
IL_0000: ldsfld class DotnetExample.VoidCallback DotnetExample.Program::eventFields
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldarg.0
IL_000a: call [System.Runtime]System.Delegate class [System.Runtime]System.Delegate::Combine([System.Runtime]System.Delegate, [System.Runtime]System.Delegate)
IL_000f: castclass class DotnetExample.VoidCallback
IL_0014: stloc.2
IL_0015: ldsflda class DotnetExample.VoidCallback DotnetExample.Program::eventFields
IL_001a: ldloc.2
IL_001b: ldloc.1
IL_001c: call !!0 class [System.Threading]System.Threading.Interlocked::CompareExchange<class DotnetExample.VoidCallback>(!!0&, !!0, !!0)
IL_0021: stloc.0
IL_0022: ldloc.0
IL_0023: ldloc.1
IL_0024: bne.un.s IL_0006
IL_0026: ret
} // End of method System.Void DotnetExample.Program::add_eventFields(DotnetExample.VoidCallback)

// 生成了一个remove_xxxx(字段名)的函数
.method public hidebysig specialname static default void remove_eventFields(class DotnetExample.VoidCallback 'value') cil managed
{
// Method begins at Relative Virtual Address (RVA) 0x2084
// Code size 39 (0x27)
.maxstack 3
.locals init(class DotnetExample.VoidCallback V_0, class DotnetExample.VoidCallback V_1, class DotnetExample.VoidCallback V_2)
IL_0000: ldsfld class DotnetExample.VoidCallback DotnetExample.Program::eventFields
IL_0005: stloc.0
IL_0006: ldloc.0
IL_0007: stloc.1
IL_0008: ldloc.1
IL_0009: ldarg.0
IL_000a: call [System.Runtime]System.Delegate class [System.Runtime]System.Delegate::Remove([System.Runtime]System.Delegate, [System.Runtime]System.Delegate)
IL_000f: castclass class DotnetExample.VoidCallback
IL_0014: stloc.2
IL_0015: ldsflda class DotnetExample.VoidCallback DotnetExample.Program::eventFields
IL_001a: ldloc.2
IL_001b: ldloc.1
IL_001c: call !!0 class [System.Threading]System.Threading.Interlocked::CompareExchange<class DotnetExample.VoidCallback>(!!0&, !!0, !!0)
IL_0021: stloc.0
IL_0022: ldloc.0
IL_0023: ldloc.1
IL_0024: bne.un.s IL_0006
IL_0026: ret
} // End of method System.Void DotnetExample.Program::remove_eventFields(DotnetExample.VoidCallback)

// eventFields事件定义了+= addon 和-= removeon两个函数
.event class DotnetExample.VoidCallback eventFields
{
.addon default void DotnetExample.Program::add_eventFields (class DotnetExample.VoidCallback 'value')
.removeon default void DotnetExample.Program::remove_eventFields (class DotnetExample.VoidCallback 'value')
} // End of property DotnetExample.VoidCallback DotnetExample.Program::eventFields
} // End of class DotnetExample.Program
根据IL代码发现,我们定义的public的事件字段eventFields被转换成了private的访问权限,并且eventFields也就是只是委托类型的对象,还生成了两个public的函数add_xxxx和remove_xxxx函数,add_xxxx函数里直接调用了委托的Combine函数,remove_xxxx函数了调用了委托的Remove函数,以及一个event类型,里面包含了两个函数分别对应+=和-=两个运算符。

委托与事件的区别

根据上两节的分析,总结委托与事件的区别。

相同点

  • 事件本质也是委托

不同点

  • 事件访问权限都是private的,要触发事件调用只能在类的内部。
  • 在外部,事件只能通过+=或-=进行回调函数的添加或删除