现在的位置: 首页 > Web开发 > .Net > 正文

VB.net学习笔记(二十三)再识委托

2016年05月29日 .Net ⁄ 共 10611字 ⁄ 字号 暂无评论

一、调用静态方法
1、声明
        委托须使用前声明定义,可以带参数(一个或多个),可以有返回值。

    '位于一个模块或类的声明部分
    Delegate Sub OneArgSub{ByVal msg As String) '带一个参数,且无返回类型

        定义了一个委托的类。后台创建了一个名为OneArgSub的新类,这个类是从System.Delegate类继承而来的。(更准确地说从 Systetn.MuhicastDelegate 继承而来的,而 System.MulticastDelegate 则又是从 System.Delegate 继承而来 的。)
2、实例化
       前面声明新类后,在要使用委托的功能块中就可以实例化委托对象:

        Dim deleg As OneArgSub                      '声明类
        deleg = New OneArgSub(AddressOf DisplayMsg) '实例化1:用New
        deleg = AddressOf DisplayMsg                '实例化2:直接用AddressOf

      实例化就是让B与C建立联系,通过AddressOf(提取函数指针)把C方法(或函数)的地址赋予B,所以执行B就是最终执行C.
3、完善委托的事(C,匹配委托签名的过程)
       最终完美委托的事的方法(或函数)签名必须与第一步的声明一致,而且名称与第二步AddressOf后面的方法(或函数)名一致。

    Private Sub DisplayMsg(ByVal mes As String) '带一个参数,且无返回类型
        TextBox1.Text = mes              'DisplayMsg与AddressOf后的签名一致
    End Sub

4、执行委托
       当B与C建立联系,且完善了C的代码后,就可以调用deleg变量的Invoke方法(可带参数,应与声明一致)。

        deleg.Invoke("帮我打官司")    '执行委托,参数类型与个数应与声明一致

       完整整个委托过程如下:

Public Class frmMain
    '位于一个模块或类的声明部分
    Private Delegate Sub OneArgSub(ByVal msg As String) '带一个参数,且无返回类型
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim deleg As OneArgSub                      '声明类
        deleg = New OneArgSub(AddressOf DisplayMsg) '实例化1:用New
        deleg.Invoke("帮我打官司")                  '执行委托,参数类型与个数应与声明一致
    End Sub
    Private Sub DisplayMsg(ByVal mes As String) '带一个参数,且无返回类型
        TextBox1.Text = mes                    'DisplayMsg与AddressOf后的签名一致
    End Sub
End Class

       委托的工作方式和所有的静态方法一样,这些静态方法是指Module中的Sub和Function过程和类中的共享过程。下面的示例使用委托来调用类中的共享Function方法:

Public Class frmMain
    Private Delegate Function AskYesNoQuestion(ByVal msg As String) As Boolean
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim question As AskYesNoQuestion
        question = New AskYesNoQuestion(AddressOf MessageDisplayer.AskYesNo) '共享方法地址返回
        If question("Do you want to save?") Then '相当于调用类中的共享方法AskYesNo,这里没用invoke
            TextBox1.Text = "Saving......"        
        Else
            TextBox1.Text = "Nothing"
        End If
    End Sub
End Class
Public Class MessageDisplayer
    Shared Function AskYesNo(ByVal mes As String) As Boolean '共享方法,通过类名调用
        Dim answer As MsgBoxResult
        answer = MsgBox(mes, MsgBoxStyle.YesNo Or MsgBoxStyle.Question)
        Return (answer = MsgBoxResult.Yes)    '回答是时,返回真
    End Function
End Class

       Invoke对于system . Delegate类和所有从这个类继承得到的类来说都是默认成员;因此,在调用这个函数时可以省略它。最终,通过委托变量调用过程和调用方法看起来差不多:
       在使用委托的时候,必须注意可选参数。被委托指向的过程能够包含Optional和paramArray参数,并且委托将正确地传递所期待的参数个数。即使目标过程被重载也会这样做,在这种情况下委托可以正确地调用过程的重载版本。不过,委托定义本身不能包含Optional或ParamArray参数。

二、调用实例方法
         对实例化的对象使用委托。因为对于对象的非共享方法,无法从类名访问方法的函数地址,只有通过对象实例化后,这个对象的方法的函数地址(public)才显露出来。才能使用委托。因此AddressOf参数必定跟随的是前面实例化的对象。

Public Class frmMain
    Private Delegate Function AskYesNoQuestion(ByVal answer As Boolean) As Boolean
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        '首先实例化对象
        Dim msgdisp As New MessageDisplayer
        msgdisp.MsgText = "对话类要显示的内容"
        msgdisp.MsgTitle = "对话类的标题 "

        '然后,实例化委托
        Dim question As New AskYesNoQuestion(AddressOf msgdisp.AskYesNo)

        '执行委托
        If question(True) Then
            TextBox1.Text = "回答真真的"
        Else
            TextBox1.Text = "回答是假的"
        End If
    End Sub
End Class
Public Class MessageDisplayer
    Public MsgText As String
    Public MsgTitle As String
    Function AskYesNo(ByVal DefaultAnser As Boolean) As Boolean '共享方法,通过类名调用
        Dim style As MsgBoxStyle

        If DefaultAnser Then
            style = MsgBoxStyle.DefaultButton1
        Else
            style = MsgBoxStyle.DefaultButton2
        End If

        style = style Or MsgBoxStyle.Question Or MsgBoxStyle.YesNo
        Return MsgBox(MsgText, style, MsgTitle) = MsgBoxResult.Yes
    End Function
End Class

三、委托的属性
     所有委托类都从System.Delegate派生,因此它们继承了在这个基类中所定义的全部属性和方法。 
    Target属性
     获取类实例,当前委托将对其调用实例方法。它返回一个到对象的引用,这个对象是委托的目标。如果委托表示实例方法,则为当前委托对其调用实例方法的对象;如果委托表示静态方法,则为 Nothing。
      例:前面实例question.Target.ToString的结果为:Test.MessageDisplayer   (因为项目名为Test,类名为MessageDisplayer)
    如果委托指向一个共享方法,则Target方法返回一个对代表类的System.Type对象的引用。在这种情况下,需要使用反射方法来提取与类本身有关的信息。
    Method属性
      获取委托所表示的方法。它返回System.Reflection.Methodlnfo对象(该对象对正在被调用的方法进行说明),它的特性及其他信息。
      例:前面实例question.Method.ToString的结果为:Boolean AskYesNo(Boolean)  (这正是声明委托的方法)

四、定义多态行为
        委托在调用时有很大灵活性。只要所有被调用的过程应该具有相同的参数签名,即确定调用是哪个。例如下列各种情况:
             (1)使用委托调用静态方法组中的一个方法:这些静态方法可以是模块中的,也可以是类中的。
             (2)使用委托调用同一个对象实例中的不同方法。
             (3)使用委托调用同一个类不同对象的不同方法。
             (4)使用委托调用属于不同类的对象的不同方法。
      例:下面是一个程序执行日志记录。重点是在执行委托事(log函数)前,log是关联到哪个函数,可以看到1时是关联到流中,2时是关联到控制台。简言之,程序中动态的关联将导致不同的执行方式和结果。

Module Module1
    Delegate Sub LogWriter(ByVal Msg As String)  '声明委托类型
    Dim log As LogWriter                         '定义委托变量(即上面能带一参的函数)
    Dim fw As System.IO.StreamWriter             '定义流变量
    Sub main()
        Dim args() As String = Environment.GetCommandLineArgs '获取命令行参数
        If args.Length > 1 Then            '如果命令行包含有一个文件名,打开这个文件 
            fw = New System.IO.StreamWriter(args(1))
            log = New LogWriter(AddressOf fw.WriteLine)      '1、委托到流输出的WriteLine方法
        Else
            log = New LogWriter(AddressOf Console.WriteLine) '2、委托到控制台输出
        End If
        Call DoTheRealJob()                                  '3、子函数中才执行委托函数
        If Not (fw Is Nothing) Then fw.Close()
    End Sub
    Sub DoTheRealJob()
        log("Start of program.")                              '4、这里执行
        log("In the middle of the program.")
        log("End of program.")
    End Sub
End Module

    说明:Environment.GetCommandLineArgs 返回包含当前进程的命令行参数的字符串数组。这是什么意思呢?
                这个其实与C语言最开始用main一样,其中main可带参数,也可不必带参数,带参时:int main (int argc,char *argv[])
                第一个就是参数个数,后面就数组。main函数的参数值是从操作系统命令行上获得的。
                当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。比如:DOS提示符下命令行的一般形式为:
                          C:\>可执行文件名 参数 参数……;
                比如:  copy  D:\1.text   E:\3.text     (把D盘的1.text文件复制到E盘的3.text,复制中带更名)
                后面的两个就是参数,个数2个。

五、委托和回调
--------------
        回调函数
         当程序跑起来时,一般情况下,应用程序(application program)会时常通过API调用库里所预先备好的函数。但是有些库函数(library function)却要求应用先传给它一个函数,好在合适的时候调用,以完成目标任务。这个被传入的、后又被调用的函数就称为回调函数(callback function)。

         打个比方,有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法。可以是打客房电话,也可以是派服务员去敲门,睡得死怕耽误事的,还可以要求往自己头上浇盆水。这里,“叫醒”这个行为是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客决定并告诉旅馆的,也就是回调函数。而旅客告诉旅馆怎么叫醒自己的动作,也就是把回调函数传入库函数的动作,称为登记回调函数(to register a callback function)。
-------------------
       好几种Windows API函数都使用回调机制,例如EmnuWindows和 EnumFonts,EnumWindows枚举系统中所有的顶级窗口,并为每个査找到的窗口回调调用程序。

Declare Function EnumWindows Lib "user32" (ByVal IpEnumFunc As EnumWindows_Callback, ByVal lParam As Integer) As Integer

     委托能够有效地用于接收来自从Windows API的回调通知,使之更安全。对上面回调声明委托:

Delegate Function EnumWindows_Callback(ByVal hWnd As Integer, ByVal lParam As Integer) As Integer

      然后完善委托的事(方法或函数):

    Function EnumWindows_CBK(ByVal hWnd As Integer, ByVal IParam As Integer) As Integer
        Console.WriteLine(hWnd) '显示这个顶级窗口的句柄 
        Return 1                '返回1,继续列举
    End Function

      最后执行委托即可:EnumWindows(AddressOf EnumWindows_CBK, 0)
      完整代码:(为了简化,只显示句柄)

Module Module1
    Declare Function EnumWindows Lib "user32" (ByVal IpEnumFunc As EnumWindows_Callback, ByVal lParam As Integer) As Integer
    Delegate Function EnumWindows_Callback(ByVal hWnd As Integer, ByVal lParam As Integer) As Integer
    Sub main()
        EnumWindows(AddressOf EnumWindows_CBK, 0)
        Console.Read()
    End Sub
    Function EnumWindows_CBK(ByVal hWnd As Integer, ByVal IParam As Integer) As Integer
        Console.WriteLine(hWnd) '显示这个顶级窗口的句柄 
        Return 1                '返回1,继续列举
    End Function
End Module

            例:下面是一个委托中可以随时中止的例子。程序有点复杂,先不要管递归过程。只看委托的使用及中止。

Module Module1
    Delegate Function TraverseDirectoryTree_CBK(ByVal dirName As String) As Boolean
    Sub main()
        TraverseDirectoryTree("E:\", AddressOf DisplayDirectoryName)
    End Sub
    Function DisplayDirectoryName(ByVal path As String) As Boolean
        Console.WriteLine(path)
        If path = "E:\tools\Thunder\Bho" Then '3、一直查到此目录为止
            Return True
        Else
            Return False
        End If
    End Function
    Sub TraverseDirectoryTree(ByVal path As String, ByVal callback As TraverseDirectoryTree_CBK) '1、委托作为参数
        Dim dirName As String
        Static nestLevel As Integer  '嵌套级别
        Static isCanceled As Boolean '取消列举时为True
        nestLevel += 1               '嵌套层次
        For Each dirName In System.IO.Directory.GetDirectories(path)
            isCanceled = callback.Invoke(dirName)     '2、回调程序执行时返回通知
            If isCanceled Then Exit For               '4、通知为真(取消列举)则退出循环
            TraverseDirectoryTree(dirName, callback)  '否则,递归继续列举
        Next
        nestLevel -= 1               '退出这个嵌套层
        If nestLevel = 0 Then   '如果准备返回给用户则取消重置
            isCanceled = False  '否则以下x次调用不能正确工作
        End If
    End Sub
End Module

      说明:1处委托作为参数进入传入,在2处时真正调用委托事项,3处决定委托的状态,4处根据委托的返回值将决定递归是否继续。

六、多路广播委托mulltcast
        .NET Framework支持两种类型的委托:
        1、单播委托
                利用单播委托能够调用一个对象的一个方法。当在单播委托中调用 Invoke()时,委托播调用委派对象播的指定方法。
         2、多播委托
                利用多播委托能够隐式地调用不同对象的一系列方法。多播委托支持一个调用列表,通过列表中的选项来调用哪个对象的哪个方法。当在多播委托中调用Invoke时,委托按顺序调用委派对象的指定方法。
               如果需要对一个对象集合执行相同的操作,或者需要对同一个对象执行一系列操作,或者是上面这两种情况的组合时,多播委托是很有用的。利用多播委托隐式地将需要执行的操作和执行操作所引用的对象形成一个集合。

       多播委托的步骤.:
           (1)定义一个委托类型:多播委托只能够执行那些具有相同签名的方法,
           (2)编写具有相同签名的方法作为委托.
           (3)创建委托对象,并将委托对象与需要通过委托调用的第一个方法相绑定。
           (4)创建另一个委托对象,将其和下一个需要调用的方法相绑定。
           (5)调用System.Delegate类的Combine方法,将上面创建的两个委托连接成一个综合的多播委托。
                        方法Combine返回一个新的委托,该委托的调用列表中包含了所有的委托。
           (6)重复执行上面两个步骤,根据实际需要创建所需的委托,并将它们合成多播委托。
           (7)如果需要从多播委托中删除委托,调用System.Delegate类的Remove方法实现。
                 Remove方法返回一个新的委托,该委托的调用表中不包含已经删除的委托。如果在调用列表中只包含刚刚删除的委托,那么Remove方法返回Nothing。
           (8)当调用多播委托所指定的方法时,只需像前面那样调用Invoke方法,即可按调用列表中的顺序调用方法,返回值为调用列表中最后一个方法的结果。

      例:主窗体不断添加子窗体,通过多播委托同时刷新已经生成的多个子窗体的背景。
       建立一个子窗体类,每次点击产生子窗体,并将其添加到多播委托中,一旦委托执行则多个方法(子窗体颜色)同时刷新,一旦关闭某子窗体则多播列表就移出该委托。
     最终效果:
                         
    子窗体类(通过解决方案中右击项目,添加项->类,来完成) 

Public Class ChildForm
    Inherits System.Windows.Forms.Form
    Private Sub ChildForm_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
        Text = "Created " & DateTime.Now.ToLongTimeString() '子窗体标题
    End Sub
    Public Function Repaint(ByVal theColor As Color) As String
        BackColor = theColor                                '子窗体背景色
        Text = "Updated " & DateTime.Now.ToLongTimeString() '子窗体刷色时间
        Return Me.Text
    End Function
    Protected Sub ChildForm_Cancel(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Closing
        'Tell the main form we are closing, so the main form can 'remove us from its multicast delegate 
        Dim MyOwner As frmMain = CType(Owner, frmMain)
        MyOwner.ChildFormClosing(Me)
    End Sub
End Class

       主窗体代码:

Public Class frmMain
    Delegate Function MyDelegate(ByVal aColor As Color) As String '1、声明委托类
    Private mTheDelegate As MyDelegate                            '2、定义委托变量

    Private Sub btnAddWindow_Click(sender As Object, e As EventArgs) Handles btnAddWindow.Click
        Dim aChildForm As New ChildForm()
        aChildForm.Owner = Me
        aChildForm.DesktopBounds = New Rectangle(800 * Rnd(), 800 * Rnd(), 300 + 200 * Rnd(), 50 + 200 * Rnd())
        aChildForm.Show() '建立子窗体并显示

        Dim newDelegate As MyDelegate = AddressOf aChildForm.Repaint '3、建立新委托
        If mTheDelegate Is Nothing Then                              '多播委托为空
            mTheDelegate = newDelegate
            sbStatus.Text = "Created first child form."
        Else
            mTheDelegate = System.Delegate.Combine(mTheDelegate, newDelegate) '4、不空,则添加
            '显示多播委托列表中的个数
            sbStatus.Text = "Created child form " & mTheDelegate.GetInvocationList().Length & "."
        End If
    End Sub
    Private Sub btnColor_Click(sender As Object, e As EventArgs) Handles btnColor.Click
        If mTheDelegate Is Nothing Then
            MsgBox("多播委托列表为空!")
            Exit Sub
        End If

        Dim dlgColor As New ColorDialog()
        dlgColor.ShowDialog()
        mTheDelegate.Invoke(dlgColor.Color) '6、多播委托执行,全部刷色
        sbStatus.Text = "Updated " & mTheDelegate.GetInvocationList().Length & " child form(s).”
    End Sub
    Public Sub ChildFormClosing(ByVal aChildForm As ChildForm)
        Dim unNeededDelegate As MyDelegate = AddressOf aChildForm.Repaint
        mTheDelegate = System.Delegate.Remove(mTheDelegate, unNeededDelegate) '7、关闭时,移出这个窗体的委托

        If mTheDelegate Is Nothing Then '移出后显示
            sbStatus.Text = "Final child form has been closed."
        Else
            sbStatus.Text = "Child form closed, " & mTheDelegate.GetInvocationList().Length & " form(s) remaining."
        End If
    End Sub
End Class

      说明:重点在主窗体代码中来认识多播委托:
                 1处声明委托类型(背景刷色),2处声明多播委托变量,3处根据新子窗体添加委托,若空,则是第一个委托,否则在4处逐渐新添委托事项。这样就形成多个委托事项。只要一执行(即6处)这多个已经添加的就同时执行(每个子窗体逐个更改背景),当关闭某子窗体,子窗体类,就会调用主窗体中ChildFormClosing方法,在7处完成移动该对应的列表。