委托

委托实际上可以看作对C++函数指针的高级封装,只不过委托提供了编译时的类型安全。CLR的委托本质上是一个类,对委托的声明会被编译器翻译为一个继承自MulticastDelegate的派生类,代码如下:

1
delegate void MyCallback(Int32 v);

编译器实际上为上述语句生成如下代码:

1
2
3
4
5
6
7
class MyCallback:System.MulticastDelegate
{
public MyCallback(Object obj, IntPtr method);
public virtual void Invoke(Int32 v);
public virtual IAsyncResult BeginInvoke(Int32 v, AsyncCallback callback, Object obj);
public virtual void EndInvoke(IAsyncResult result);
}

MulticastDelegate类有三个重要的字段:

  1. _target:对象实例。回调静态方法时为null
  2. _methodPtr:方法指针
  3. _invocationList:委托链,单播委托为null

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A
{
public void Show(Int32 v)
{
Console.WriteLine(v.ToString);
}
}

A a=new A;
MyCallback mcb=new MyCallback(a.Show);
mcb(3);

//输出:3

在上述代码中_target=a,_methodPtr指向Show方法。代码中mcb(3)语句实际上是个语法糖,编译器在底层将它翻译为mcb.Invoke(3),Invoke方法使用_target与_methodPtr共同确定要调用的方法。

委托链

由基类MulticastDelegate的名称很容易看出支持多个委托回调。多播委托与单播委托略有区别,由于需要保存多个回调,_target和_methodPtr显然是不够用的,因此需要使用_invocationList字段。_invocationList实际上是一个委托对象集合,每次使用Delegate.Combine方法添加委托时,方法检查被合并的委托是否为null,如果为null,则直接返回原委托,否则将构建一个新的委托对象。多播委托与单播委托使用方法一样,调用Invoke方法时,依次对每一个委托数组元素调用Invoke,因此如果委托具有返回值,将只返回最后一个执行的回调函数的返回值。Invoke方法的实现大致如下:

1
2
3
4
5
6
7
8
9
10
11
12
public void Invoke(Int32 v)
{
Delegate[] delegateArray = _invocationList as Delagate[]
if(delegateArray != null)
{
//多播委托依次调用每一个回调方法
foreach(MyCallback del in delgateArray)
del.Invoke(v);
}
else
//单播委托直接调用回调方法
}

除了可以添加委托,还可以使用Delegate.Remove方法删除委托,方法查找源委托链中与目标委托签名一致的委托对象,并删除第一个匹配项。如果删除后委托链为空,则返回null。

异步委托

为了便于执行耗时较长的回调,委托类提供了BeginInvoke()方法用于异步执行,其底层实现是将回调交给线程池处理。BeginInvoke接受两个额外的参数:在异步委托完成时执行的AsyncCallback类型的回调和一个用于标识的Object参数,其返回值是一个IAsyncResult类型

1
2
3
4
5
6
7
8
9
10
11
interface IAsyncResult
{
//BeginInvoke的object参数将传递给该属性
public object AsyncState{get};
//内核对象,异步完成时会收到信号
public WaitHandle AsyncWaitHandle{get};
//是否是同步完成的
public bool CompletedSynchronously { get; }
//是否已执行完毕
public bool IsCompleted { get; }
}

BeginInvoke必须用配对的EndInvoke结束异步操作,并返回异步操作的返回值。

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
class Program
{
static Func<int,int> MyAsyncWork = DoWork;
static AsyncCallback MyAsyncCallback = AfterWork;
static void Main()
{
IAsyncResult ar = MyAsyncWork.BeginInvoke(100,MyAsyncCallback,"任意自定义数据");
//主线程结束后将看不到执行结果
Console.Read();
}

static int DoWork(int n)
{
Thread.Sleep(1000);
return n;
}

static void AfterWork(IAsyncResult ar)
{
Console.WriteLine("异步执行完毕");
Console.WriteLine("结果是:" + MyAsyncWork.EndInvoke(ar));
}
}

//AsyncCallback的内部定义
//delegate void AsyncCallback(IAsyncResult);

事件

事件实际上也是基于委托的。定义事件的一般步骤如下:

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
//定义事件参数
public class MailEventArgs: EventArgs
{
private string mFrom;
private string mTo;
private string mContent;

public string From{get{return mFrom;}}
public string To{get{return mTo;}}
public string Content{get{return mContent;}};

public MailEventArgs(string from,string to,string content)
{
mFrom=from; mTo=to; mContent=content;
}

}

//定义事件
public class MailBox
{
public event EventHandler<MailEventArgs> MailEvent;

protected virtual void OnNewMail(Object sender,MailEventArgs e)
{
EventHandler<MailEventArgs> temp=Volatile.Read(ref MailEvent);
if(temp!=null) temp(sender,e);
}

public void NewMail(string from,string to,string content)
{
MailEventArgs e=new MailEventArgs(from,to,content);
OnNewMail(this,e);
}
}

//注册事件处理函数
static void Main()
{
MailBox mb=new MailBox();
mb.MailEvent+=MyMailEvent_Handler;
mb.NewMail("C#","You","HelloWorld");
}

static void MyMailEvent_Handler(Object sender,MailEventArgs e)
{
Console.WriteLine("收到新消息");
Console.WriteLine("From "+e.From+" To "+e.To+NewLine+e.Content);
}

/*对于下面这一事件定义
public event EventHandler<MailEventArgs> MailEvent;
编译器实际上产生以下代码
private EventHandler<MailEventArgs> MailEvent;
public void add_MailEvent(EventHandler<MailEventArgs> value)
{
//注册事件回调时内部调用该方法
}
public void remove_MailEvent(EventHandler<MailEventArgs> value)
{
//移除事件回调时内部调用该方法
}
*/

内存优化的自定义事件

不管是否订阅了事件,每个事件声明都会至少占用4个字节的内存,对于定义了大量事件的类–比如Windows控件,造成的内存浪费将非常可观。为了节省内存空间,CLR提供了EventHandlerList类。

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
//C#
private EventHandlerList Events=new EventHandlerList();
//key可以是任意对象,只需要每种事件不同即可
public event EventHandler Click
{
add
{
Events.AddHandler(key1,value);
}
remove
{
Events.RemoveHandler(key1,value);
}
}

public event EventHandler KeyDown
{
add
{
Events.AddHandler(key2,value);
}
remove
{
Events.RemoveHandler(key2,value);
}
}

protected virtual void OnClick(Object sender,EventArgs e)
{
EventHandler temp=(EventHandler)Events[key];
temp(sender,e);
}

//VB.NET更加简化的Custom关键字
Private Events As New EventHandlerList

Public Custom Event Click As EventHandler
AddHandler(ByVal value As EventHandler)
Events.AddHandler(key1, value)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
Events.RemoveHandler(key1, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
CType(Events(key1), EventHandler).Invoke(sender, e)
End RaiseEvent
End Event

Public Custom Event KeyDown As EventHandler
AddHandler(ByVal value As EventHandler)
Events.AddHandler(key2, value)
End AddHandler
RemoveHandler(ByVal value As EventHandler)
Events.RemoveHandler(key2, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As EventArgs)
CType(Events(key2), EventHandler).Invoke(sender, e)
End RaiseEvent
End Event

EventHandlerList类底层实现基于字典或者哈希表,因此当事件项数量过多时可能会有查找性能问题。

 评论




载入天数...载入时分秒...  |  总访问量为
Powered by Github and MarkdownPad 2

--------------------------- 别拉了,我是有底线的>_< ---------------------------