VB のたまご

作成日: 2015/12/03, 更新日: 2015/12/03


デリゲートの特徴を1つ1つ噛み砕いて、苦手意識を克服しよう

デリゲート、それはよく分からないもの。

  •  デリゲート、日本語の意味は「委譲」、つまり代理人。
  • 「デリゲート」で考えてみると、ちょっと聞きなれない言葉で、よく分からないものというイメージで身構えてしまいますが、 「代理人」で考えてみると、実は、今の世の中には、代理人であふれかえっていることが分かります。
  •  印刷代行、運転代行、決済代行、家事代行、育児代行。このように、いろいろなサービスがあります。 要するに、本人の代わりにやってあげるよ。というお仕事ですね。

スポンサーリンク


デリゲート、それはメイドさん。

  •  例えば、欧米の場合、執事やメイド等の使用人を雇って、生活面をサポートしてもらうところが多いです。 今日は、そんな使用人の仕事内容について、勉強していきます。

使用人のお仕事、仕事代行。

  •  使用人は、食事の準備や掃除、洗濯を代わりにやってくれます。中には秘書のように、スケジュール管理もやってくれるところもあります。 しかし、宿題面倒くさいから代わりにやって。仕事行きたくないから代わりに行ってやってきて。というお願いは聞いてくれません。 何でもかんでも、代わりにやってくれるわけではないのです。
  •  VB.NET の Delegate も代行のお仕事をしてくれます。あー、メソッド呼び出すの面倒くさいな、誰か代わりに呼んでくれないかな。 というお願いを、Delegate は引き受けてくれます。
  • Dim i1 As Integer = 0
    
    ' 足し算
    i1 = Me.Plus(3, 2)               ' あー、呼び出すのだりぃ。
    Console.WriteLine("3+2={0}", i1)
    
    ' 引き算
    i1 = Me.Minus(3, 2)               ' あー、呼び出すのかったりぃ。
    Console.WriteLine("3-2={0}", i1)
    
    Function Plus(i1 As Integer, i2 As Integer) As Integer
        Return i1 + i2
    End Function
    
    Function Minus(i1 As Integer, i2 As Integer) As Integer
        Return i1 - i2
    End Function
    
    ' 出力結果
    3+2=5
    3-2=1
    

  •  このような大変ハードな処理内容も、Delegate に掛かればちょちょいのちょいです。

  • Dim dlgt As CalcDelegate = New CalcDelegate(AddressOf Me.Plus) ' このようにも書ける
    
    ' 足し算メソッドの代わりとなる
    dlgt = AddressOf Me.Plus                 ' 代行呼び出し、お願いしますね!>あいよー!
    Console.WriteLine("3+2={0}", dlgt(3, 2)) ' あらよっとー!
    
    ' 引き算メソッドの代わりとなる
    dlgt = AddressOf Me.Minus                ' 代行呼び出し、お願いしますね!>あいよー!
    Console.WriteLine("3-2={0}", dlgt(3, 2)) ' あらよっとー!
    
    Delegate Function CalcDelegate(i1 As Integer, i2 As Integer) As Integer
    
    Function Plus(i1 As Integer, i2 As Integer) As Integer
        Return i1 + i2
    End Function
    
    Function Minus(i1 As Integer, i2 As Integer) As Integer
        Return i1 - i2
    End Function
    
    ' 出力結果
    3+2=5
    3-2=1
    

  •  ほらね。なぜか日本の江戸っ子口調だけど、しっかり仕事をこなすでしょ。
  • これだけ見ると、わざわざデリゲート経由で呼び出す方が手間なだけじゃん。 としか思えないのですが、後々、LINQ やラムダ式を扱えるようになるためには、必要な考え方なので、 今は、そういうことができるのか、へぇ。程度に思ってください。

  •  Delegate はメソッド呼び出しを代行するので、〇〇なメソッドを代行しますよ、という定義をまずしなければいけません。 定義方法はメソッドを定義するみたいに書きます。処理内容は、代行の依頼者側に依存するのでありません。 ここでは、Integer 型変数の引数を2つ受け取り、Integer 型の戻り値を返すメソッドなら何でも代行しますよー!と定義しています。

  •  もう1つサンプルを見てみます。 「変数に、メソッドをセットする」という特徴があるので、今度は、メソッドにメソッドを渡して、そのメソッド内でメソッド実行するサンプルです。 ちょっと長いです。
  • Public Class Form1
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    
            Dim i1 As Integer = 0
    
            ' 足し算
            i1 = Calc.Calculate(3, 2, AddressOf Me.Plus)
            Console.WriteLine("3+2={0}", i1)
    
            ' 引き算
            i1 = Calc.Calculate(3, 2, AddressOf Me.Minus)
            Console.WriteLine("3-2={0}", i1)
    
        End Sub
    
        Function Plus(i1 As Integer, i2 As Integer) As Integer
            Return i1 + i2
        End Function
    
        Function Minus(i1 As Integer, i2 As Integer) As Integer
            Return i1 - i2
        End Function
    
    End Class
    
    Public Class Calc
    
        Public Delegate Function CalcDelegate(i1 As Integer, i2 As Integer) As Integer
    
        Public Shared Function Calculate(i1 As Integer, i2 As Integer, method As CalcDelegate) As Integer
            Return method(i1, i2)
        End Function
    
    End Class
    
    ' 出力結果
    3+2=5
    3-2=1
    

  •  いったん、Delegate 変数を用意して、それにメソッドをセットして、その変数を引数として渡そうかとも考えました。 しかし、見づらくなるかなとも思い、ここでは直接 Delegate 変数をその場で用意しつつ、メソッドに渡すような書き方をしていいます。

  •  「メソッドは呼び出したその場所で実行する」という概念が崩れたでしょうか。 Delegate は、変数にメソッドをセットする、そしてあっちこっちに持ち運びできる、好きな場所で実行できる、という面白い特徴があります。

スポンサーリンク


使用人のお仕事、多重代行。

  •  前節で述べた通り、使用人は、とにかく、いろいろやることがあります。 家事が終わると、庭の手入れや子供たちの送迎、食材の購入、クリーニング等、次から次へと、段取り良くテキパキこなします。
  •  VB.NET の Delegate もいろいろやります。頼まれた仕事はテキパキこなします。
  • ' 1メソッド1デリゲートで用意して、デリゲートにまとめて登録
    Dim i1 As Integer = 0
    Dim plusMethod As CalcDelegate = AddressOf Me.Plus
    Dim minusMethod As CalcDelegate = AddressOf Me.Minus
    Dim multiMethod As CalcDelegate = CType([Delegate].Combine(New CalcDelegate() {plusMethod, minusMethod}), CalcDelegate)
    
    ' まとめたデリゲートを1つ1つ実行して、戻り値を受け取る
    Dim methodList() As [Delegate] = multiMethod.GetInvocationList()
    For Each method As CalcDelegate In methodList
        i1 = method(3, 2)
        Console.WriteLine(i1)
    Next
    
    ' 出力結果
    5
    1
    

  •  Plus メソッドと Minus メソッドの定義は省略しています。 デリゲートの複数登録や、1つ1つ抜き出す処理があんまりいい感じじゃないですね・・・。 戻り値ありの場合は、普通にデリゲート実行すると、最後に登録したデリゲート結果しか返してくれないので、このような書き方にしました。 戻り値無しのメソッドであれば、もう少し簡単です。
  • Dim d1 As MethodInvoker = AddressOf Me.EatBreakfast
    Dim d2 As MethodInvoker = AddressOf Me.EatLunch
    Dim d3 As MethodInvoker = AddressOf Me.EatDinner
    Dim d4 As MethodInvoker = CType([Delegate].Combine(New MethodInvoker() {d1, d2, d3}), MethodInvoker)
    
    ' 複数実行
    d4()
            
    Sub EatBreakfast()
        Console.WriteLine("Eat! Breakfast.")
    End Sub
    
    Sub EatLunch()
        Console.WriteLine("Eat! Lunch.")
    End Sub
    
    Sub EatDinner()
        Console.WriteLine("Eat! Dinner.")
    End Sub
    
    ' 出力結果
    Eat! Breakfast.
    Eat! Lunch.
    Eat! Dinner.
    

  •  MethodInvoker というデリゲート型は、.NET Framework に組み込まれているデリゲートです。 引数無しで、戻り値無しのメソッドに対応しています。戻り値無しの場合は、複数呼び出しが楽ですね。

使用人のお仕事、非同期実行。

  •  使用人は、ご主人様とは別の人間なので、ご主人様が仕事している時に、洗濯、掃除等他の仕事をすることができます。 人間の世界で考えると、人々が非同期で活動しているのって、ごくごく当たり前なことですよね。 例えば、会社だと、営業が各自で外周りしたり、上司が得意先とそれぞれの会議室で会議したり、とかですね。 使用人にしても、1人で掃除や買い物するのではなく、2人いれば、掃除担当と買い物担当で分けてやれば、それぞれ同時にこなせますね。
  •  VB.NET の Delegate も非同期実行ができます。
  • Imports System.Threading.Thread
    
    Dim method As New MethodInvoker(AddressOf Me.DoWork)
    method()
    Console.WriteLine("全て終了")
            
    Sub DoWork()
    
        Console.WriteLine("開始")
        Sleep(3000)
        Console.WriteLine("終了")
    
    End Sub
    
    ' 出力結果
    開始
    終了
    全て終了
    

  •  最初は比較のため、同期処理を実行しました。1行ずつ命令が実行されていることが分かります。 次は非同期処理での実行です。

  • Imports System.Threading.Thread
    
    Dim method As New MethodInvoker(AddressOf Me.DoWork)
    method.BeginInvoke(Nothing, Nothing)
    Console.WriteLine("全て終了")
            
    Sub DoWork()
    
        Console.WriteLine("開始")
        Sleep(3000)
        Console.WriteLine("終了")
    
    End Sub
    
    ' 出力結果
    全て終了
    開始
    終了
    

  •  どうでしょうか。先に「全て終了」が出力されました。別々に動いているということですね。 プログラムが非同期で動く。なんかカッコイイ。このような表向きの憧れを、私も持っています。今でもね。 しかし、非同期の裏の顔も、私は知っています。

  •  非同期処理は、じゃじゃ馬です。扱うのが難しいです。 いろいろ試してみたけどうまくできないから、もう処理が遅いけど同期処理でいいや。なんてざらにあります。 別々に動かす。という認識を、「勝手に動く」ことを考慮しながら扱う、という認識で捉えることが、うまく制御するためのポイントかと思います。 まぁ、今回は非同期処理の触りの部分なので、重く考えずに、非同期カッコイイ!とだけ分かればOKです。

  •  補足です。非同期処理が終わった後で、戻り値を受け取りたい時の処理を載せておきます。
  • Imports System.Threading.Thread
    
    Public Class Form1
    
        ' クラス変数にして、各メソッドからアクセスできるようにしている
        Private method As MethodDelegate = Nothing
    
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            method = AddressOf Me.Plus
            method.BeginInvoke(3, 2, New AsyncCallback(AddressOf Me.CallBack), "すべて終了")
            Console.WriteLine("開始")
        End Sub
    
        Delegate Function MethodDelegate(i1 As Integer, i2 As Integer) As Integer
    
        Function Plus(i1 As Integer, i2 As Integer) As Integer
            Sleep(1000)
            Return i1 + i2
        End Function
    
        Sub CallBack(ar As IAsyncResult)
            Dim result As Integer = method.EndInvoke(ar)
            Dim msg As String = ar.AsyncState.ToString()
            Console.WriteLine(result)
            Console.WriteLine(msg)
        End Sub
    
    End Class
    
    ' 出力結果
    開始
    5
    すべて終了
    

  •  BeginInvoke メソッドの引数は、①代行するメソッドの各引数、②非同期処理が終わった後で、自動実行したいメソッド(値を受け取りなんやらかんやらする後処理メソッド)、 ③②のメソッド内で扱いたいデータ( Object 型なので何でもセットできる)となっています。②のような役割をするメソッドのことを、コールバック関数と言います。

まとめ。

  •  使用人のお仕事がいかに大変か、お分かりになられたでしょうか。 ではなくて、ここまで Delegate について勉強してきましたが、いかがでしたでしょうか。

  •  近年では、VB2005 で登場して、VB2008 で強化された、ジェネリックデリゲートがたくさん登場しています。 本記事内では毎回独自のデリゲートを定義していましたが、ジェネリックの機能により、あらゆるパターンのデリゲートを作成することができるようになりました。 独自のデリゲートを準備することなくデリゲートを使えるので、大変便利になりました。 ジェネリックについてはこちらを参照ください。

  •  Delegate を知ることは、イベントの仕組みを知ることにつながります。ここまで頑張って読まれた方であれば、イベントの仕組みも楽勝です!それでは。

  •  最後までこの記事を読んでいただき、ありがとうございました。

  • スポンサーリンク