在本文中,将简要介绍委托(Delegate)的概念。这可以作为.NET编程中委托使用的入门指南。
在开始之前,假设已经具备了函数指针的基础知识。因为在介绍部分,将委托与函数指针进行比较。如果对函数指针还不太了解,可以参考。
委托非常类似于C/C++中的“函数指针”或“typedef”,它代表了函数的地址。在C/C++中,函数的地址仅仅是一个内存地址。函数指针只持有函数的地址,而不包含任何额外的信息,比如函数期望的参数数量、参数类型等。实际上,这使得函数指针类型不安全。传统上,调用函数指针依赖于语言对函数指针的支持,而函数指针本身是危险的。
委托为传统的函数指针概念增加了安全性。.NET框架增加了一种类型安全的机制,称为委托。它们遵循“信任但验证”的模型,编译器自动验证签名。与函数指针不同,委托是面向对象的、类型安全的、安全的。简而言之,委托是一种数据结构,它引用静态方法或对象实例及其实例方法。当委托引用实例方法时,它不仅存储方法入口点的引用,还存储用于调用该方法的对象实例的引用。
在VB.NET中,使用“Delegate”关键字定义委托。例如:
Public Delegate Sub GreetingDelegate(ByVal MsgString As String)
这行代码并没有声明委托,而是定义了委托。通过这行代码,告诉编译器创建一个名为“GreetingDelegate”的新类,该类继承自“System.Delegate”(编译器会为自动完成)。这行代码等同于在C/C++中编写以下代码:
typedef void (*GreetingDelegate) (char *);
这个委托只能调用一个接受“String”参数且不返回任何内容的方法。委托只关心封装在其中的方法的签名。因此,可以安全地使用这个委托来调用具有适当签名的函数。
在这里,写了两个不同的VB.NET函数:
Public Sub GoodMorning(ByVal YourName As String)
Console.WriteLine("Good Morning " + YourName + "!")
End Sub
Public Sub GoodNight(ByVal YourName As String)
Console.WriteLine("Good Night " + YourName + "!")
End Sub
现在,在Sub Main中,可以创建委托的实例,并将函数的地址分配给委托。这是使用AddressOf关键字完成的:
Dim MyGreeting As GreetingDelegate
Console.WriteLine("Adding 'GoodMorning' Reference To A Delegate...")
MyGreeting = AddressOf GoodMorning
一旦给委托分配了地址,可以使用Invoke方法来调用封装在委托中的函数。
Console.WriteLine("Invoking Delegate...")
MyGreeting.Invoke("Mallinath")
因为委托只关心封装在其中的方法的签名,所以可以让它引用另一个方法。例如:
Console.WriteLine("Making Existing Delegate To Point To Another Function...")
Console.WriteLine("Replacing With Goodnight Reference...")
MyGreeting = New GreetingDelegate(AddressOf Goodnight)
另一种调用委托的方式。
Console.WriteLine("Invoking Delegate...")
MyGreeting("Mallinath")
惊讶吗?忘记放Invoke()了吗?不!实际上,不需要使用Invoke(),可以直接使用委托作为方法的代理。
如果尝试分配一个与委托声明不匹配的方法或函数的地址,将得到一个编译器错误。尝试以下代码,将其放入下载的源代码中:
Public Sub BadGreets()
Console.WriteLine("Wishing You Bad Greetings !")
End Sub
Sub Main()
Dim MyGreeting As GreetingDelegate
MyGreeting = AddressOf BadGreets
MyGreeting.Invoke("Mallinath")
End Sub
接下来是多播委托,这些委托能够指向具有相同签名的多个函数。System.Delegate类型维护一个链接列表,用于保存委托要调用的函数的引用。这个链接列表被称为调用列表(Invocation List)。委托类提供了一个共享成员函数Combine(),它将指定函数的引用添加到调用列表中。
为了演示多播委托,在MulticastDelegate示例程序中添加了一个新的函数'GoodEvening',以及之前的两个函数。为之前的每个函数创建了单独的委托。
Public Delegate Sub GreetingDelegate(ByVal MsgString As String)
Public Sub GoodMorning(ByVal YourName As String)
Console.WriteLine("Good Morning " + YourName + "!")
End Sub
Public Sub Goodnight(ByVal YourName As String)
Console.WriteLine("Good Night " + YourName + "!")
End Sub
Public Sub GoodEvening(ByVal YourName As String)
Console.WriteLine("Good Evening " + YourName + "!")
End Sub
在Sub Main中,实例化它们如下:
Dim MorningGreets As GreetingDelegate
Dim EveningGreets As GreetingDelegate
MorningGreets = AddressOf GoodMorning
EveningGreets = New GreetingDelegate(AddressOf GoodEvening)
为了说明多播委托,将两个委托合并为一个委托,使用Combine()方法:
Console.WriteLine("Adding 'MorningGreets' And 'EveningGreets' References To A Delegate...")
Dim AllGreets As GreetingDelegate = Delegate.Combine(MorningGreets, EveningGreets)
Combine()返回一个新委托,合并了指定委托的函数引用的调用列表。在这里,可以向现有委托添加另一个函数引用:
Console.WriteLine("Adding Another Reference To Existing Delegate...")
AllGreets = Delegate.Combine(AllGreets, New GreetingDelegate(AddressOf Goodnight))
正如写的,委托定义行告诉编译器创建一个继承自System.Delegate类型的新类:
Dim NightGreets As GreetingDelegate = AddressOf Goodnight
这等同于:
Dim NightGreets As GreetingDelegate
NightGreets = New GreetingDelegate(AddressOf Goodnight)
所以,整个事情可以总结为:
AllGreets = Delegate.Combine(AllGreets, New GreetingDelegate(AddressOf Goodnight))
就像函数一样,要向委托的调用列表添加函数引用,委托类提供了一个函数来从委托调用列表中移除函数引用。
例如:
Console.WriteLine("Removing 'GoodEvening' Reference...")
AllGreets = Delegate.Remove(AllGreets, EveningGreets)
对多播委托的Invoke函数调用将调用存储在该委托对象调用列表中的所有函数。