委托初学者指南






4.65/5 (84投票s)
2006年3月29日
4分钟阅读

525723

6867
一篇关于 .NET 中委托的文章。
引言
在本文中,我简要介绍了委托。这可以作为委托的良好“入门指南”。
背景
在此,我假设您对函数指针有基本了解。因为在介绍部分,我将委托与函数指针进行比较。有关函数指针的基本概念,请参阅此链接。
简单委托
委托非常类似于 C/C++ 的“函数指针”或“代表函数地址的typedef”。在 C/C++ 中,函数的地址只是一个内存地址。C/C++ 函数指针只存储函数的地址。此地址不包含任何额外信息,例如函数期望的参数数量、它接受的参数类型等。事实上,所有这些都使函数指针类型不安全。传统上,通过其地址调用函数取决于语言是否支持函数指针。而函数指针本质上是危险的。
委托为传统函数指针的概念增加了安全性。.NET 框架通过提供一种称为委托的类型安全机制,带来了额外的好处。它们遵循信任但验证的模型,并由编译器自动验证签名。然而,与函数指针不同,委托是面向对象的、类型安全的且受保护的。简而言之,委托是一种数据结构,它引用静态方法或对象实例及其实例方法。当委托引用实例方法时,它不仅存储了对方法入口点的引用,还存储了对调用方法的对象实例的引用。
在 VB.NET 中,我们使用“Delegate
”关键字定义委托。例如:
'Defining the delegate
Public Delegate Sub GreetingDelegate(ByVal MsgString As String)
这并不声明委托,而是定义它。通过这一行,我们告诉编译器创建一个名为“GreetingDelegate
”的新类,该类继承自“System.Delegate
”(编译器会自动为您完成)。这一行等同于在 C/C++ 中编写以下代码:
typedef void (*GreetingDelegate) (char *);
此委托只能调用一个接受 String
参数且不返回任何内容的函数。委托只关心您在其中封装的函数的签名。因此,我们可以使用此委托安全地调用封装在此委托中的函数(具有适当的签名)。
在这里,我在 VB.NET 中编写了两个不同的函数:
Public Sub GoodMoring(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
关键字完成,如下所示:
'Instantiating the delegate
Dim MyGreeting As GreetingDelegate
'Here we assign the address of the function we wish to encapsulate
'to the delegate
Console.WriteLine("Adding 'GoodMoring' Reference To A Delegate...")
MyGreeting = AddressOf GoodMoring
一旦我们将地址分配给委托,我们就可以使用 Invoke
方法来调用委托中封装的函数。
'Invoking the delegate
Console.WriteLine("Invoking Delegate...")
MyGreeting.Invoke("Mallinath")
因为委托只关心您在其内部封装的函数的签名,所以我们也可以让它引用另一个函数。例如:
'Assigning the address of the another function to the same delegate
Console.WriteLine()
Console.WriteLine("Making Existing Delegate To Point To Another Fuction...")
Console.WriteLine("Replacing With Goodnight Reference...")
MyGreeting = New GreetingDelegate(AddressOf Goodnight)
'Another way of invoking the delegate.
Console.WriteLine("Invoking Delegate...")
MyGreeting("Mallinath")
惊讶?我忘了在那里放 Invoke()
?不!实际上,您不需要使用 Invoke()
,您可以直接将委托用作方法的代理。
如果您尝试将不匹配委托声明的方法或函数的地址分配给它,您将收到编译器错误。请尝试将以下代码放入下载的源代码中。
'your existing code goes over here
'add these following lines
Public Sub BadGreets()
Console.WriteLine("Wishing You Bad Greetings !")
End Sub
Sub Main()
'your existing code goes over here
'add these following lines and try
MyGreeting = AddressOf BadGreets
MyGreeting.Invoke("Mallinath")
End Sub
多播委托
说到多播委托,它们是可以指向同一签名的多个函数的委托。System.Delegate
类型维护一个链表,用于保存要由委托调用的函数的引用。这个链表称为调用列表。委托类提供了一个名为 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" & _
" References 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
函数将调用所有其引用存储在该委托对象的调用列表中的函数。
最后,在总结多播委托时,我可以说,它与常规委托不同之处在于它可能包含不止一个方法的引用。多播委托中的方法在调用多播委托时同步执行,并且按照添加到列表中的顺序执行。如果被调用的方法之一引发异常,则委托停止,并将异常传播给委托调用者。
本文中使用的所有上述代码都已放入示例中。您可以下载并运行它。