ラムダ式 (Visual Basic)

"ラムダ式" は、デリゲートが有効な場所であればどこでも使用できる、名前のない関数またはサブルーチンです。 ラムダ式は関数またはサブルーチンにすることができ、単一行または複数行にすることができます。 値を現在のスコープからラムダ式に渡すことができます。

Note

RemoveHandler ステートメントは例外です。 RemoveHandler のデリゲート パラメーターにラムダ式を渡すことはできません。

標準の関数またはサブルーチンを作成する場合と同様に、Function または Sub キーワードを使用してラムダ式を作成します。 ただし、ラムダ式はステートメントに含まれます。

次の例は、引数をインクリメントして値を返すラムダ式です。 この例は、関数の単一行および複数行のラムダ式の構文を示しています。

Dim increment1 = Function(x) x + 1
Dim increment2 = Function(x)
                     Return x + 2
                 End Function

' Write the value 2.
Console.WriteLine(increment1(1))

' Write the value 4.
Console.WriteLine(increment2(2))

次の例は、コンソールに値を書き込むラムダ式です。 この例は、サブルーチンの単一行および複数行のラムダ式の構文を示しています。

Dim writeline1 = Sub(x) Console.WriteLine(x)
Dim writeline2 = Sub(x)
                     Console.WriteLine(x)
                 End Sub

' Write "Hello".
writeline1("Hello")

' Write "World"
writeline2("World")

上記の例では、ラムダ式が変数名に割り当てられていることに注意してください。 変数を参照するときは常にラムダ式を呼び出します。 次の例に示すように、ラムダ式の宣言と呼び出しを同時に行うこともできます。

Console.WriteLine((Function(num As Integer) num + 1)(5))

ラムダ式は関数呼び出しの値として返すことも (このトピックで後述する「コンテキスト」の例を参照)、次の例に示すように、デリゲート型を受け取るパラメーターに引数として渡すこともできます。

Module Module2

    Sub Main()
        ' The following line will print Success, because 4 is even.
        testResult(4, Function(num) num Mod 2 = 0)
        ' The following line will print Failure, because 5 is not > 10.
        testResult(5, Function(num) num > 10)
    End Sub

    ' Sub testResult takes two arguments, an integer value and a 
    ' delegate function that takes an integer as input and returns
    ' a boolean. 
    ' If the function returns True for the integer argument, Success
    ' is displayed.
    ' If the function returns False for the integer argument, Failure
    ' is displayed.
    Sub testResult(ByVal value As Integer, ByVal fun As Func(Of Integer, Boolean))
        If fun(value) Then
            Console.WriteLine("Success")
        Else
            Console.WriteLine("Failure")
        End If
    End Sub

End Module

ラムダ式の構文

ラムダ式の構文は、標準の関数またはサブルーチンの構文に似ています。 相違点は、次のとおりです。

  • ラムダ式には名前はありません。

  • ラムダ式には、OverloadsOverrides などの修飾子を含めることはできません。

  • 単一行のラムダ関数では、As 句を戻り値の型を指定しません。 代わりに、ラムダ式の本体が評価される値から型が推論されます。 たとえば、ラムダ式の本体が cust.City = "London" の場合、戻り値の型は Boolean になります。

  • 複数行のラムダ関数では、As 句を使用して戻り値の型を指定することも、戻り値の型が推論されるように As 句を省略することもできます。 複数行のラムダ関数で As 句を省略すると、戻り値の型として、複数行のラムダ関数のすべての Return ステートメントから最も優先度の高い型が推論されます。 "最も優先度の高い型" は、他のすべての型から拡大変換できる一意の型です。 この一意の型を特定できない場合、最も優先度の高い型は、配列内の他のすべての型から縮小変換できる一意の型になります。 これらの一意の型をどちらも特定できない場合は、 Objectが最も優先度の高い型になります。 この場合、Option StrictOn に設定されていると、コンパイラ エラーが発生します。

    たとえば、Return ステートメントに指定された式に、IntegerLongDouble の各型の値が含まれている場合、結果の配列は Double 型になります。 IntegerLong はどちらも Double に拡大変換され、Double のみになります。 そのため、 Double が最も優先度の高い型になります。 詳細については、「 Widening and Narrowing Conversions」を参照してください。

  • 単一行の関数の本体は、ステートメントではなく、値を返す式である必要があります。 単一行の関数の Return ステートメントはありません。 単一行の関数によって返される値は、関数の本体に含まれる式の値です。

  • 単一行のサブルーチンの本体は、単一行のステートメントである必要があります。

  • 単一行の関数とサブルーチンには、End Function または End Sub ステートメントは含まれません。

  • As キーワードを使用して、ラムダ式のパラメーターのデータ型を指定できます。また、パラメーターのデータ型を推論することもできます。 すべてのパラメーターにデータ型が指定されているか、すべて推論される必要があります。

  • Optional および Paramarray パラメーターは使用できません。

  • ジェネリック パラメーターは使用できません。

非同期ラムダ

Async キーワードと Await Operator キーワードを使用すると、非同期処理を組み込んだラムダ式およびステートメントを簡単に作成できます。 たとえば、次に示す Windows フォーム例には、非同期メソッド ExampleMethodAsyncを呼び出して待機するイベント ハンドラーが含まれています。

Public Class Form1

    Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' ExampleMethodAsync returns a Task.
        Await ExampleMethodAsync()
        TextBox1.Text = vbCrLf & "Control returned to button1_Click."
    End Sub

    Async Function ExampleMethodAsync() As Task
        ' The following line simulates a task-returning asynchronous process.
        Await Task.Delay(1000)
    End Function

End Class

AddHandler ステートメントで非同期ラムダを使用して、同じイベント ハンドラーを追加できます。 次の例に示すように、このハンドラーを追加するには、ラムダ パラメーター リストの前に Async 修飾子を追加します。

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        AddHandler Button1.Click,
            Async Sub(sender1, e1)
                ' ExampleMethodAsync returns a Task.
                Await ExampleMethodAsync()
                TextBox1.Text = vbCrLf & "Control returned to Button1_ Click."
            End Sub
    End Sub

    Async Function ExampleMethodAsync() As Task
        ' The following line simulates a task-returning asynchronous process.
        Await Task.Delay(1000)
    End Function

End Class

非同期メソッドの作成および使用方法の詳細については、「Asynchronous programming with Async and Await (Async および Await を使用した非同期プログラミング)」をご覧ください。

コンテキスト

ラムダ式は、その式が定義されているスコープとコンテキストを共有します。 包含スコープで記述されたコードと同じアクセス権を持ちます。 これには、包含スコープ内のメンバー変数、関数とサブルーチン、Me、パラメーターとローカル変数へのアクセスが含まれます。

包含スコープ内のローカル変数とパラメーターへのアクセスは、そのスコープの有効期間を超えて拡張できます。 ラムダ式を参照するデリゲートがガベージ コレクションで使用できない間は、元の環境の変数へのアクセスが保持されます。 次の例では、変数 target は、makeTheGame に対してローカルです。これは、ラムダ式 playTheGame が定義されているメソッドです。 MaintakeAGuess に割り当てられた、返されたラムダ式は、引き続きローカル変数 target にアクセスできます。

Module Module6

    Sub Main()
        ' Variable takeAGuess is a Boolean function. It stores the target
        ' number that is set in makeTheGame.
        Dim takeAGuess As gameDelegate = makeTheGame()

        ' Set up the loop to play the game.
        Dim guess As Integer
        Dim gameOver = False
        While Not gameOver
            guess = CInt(InputBox("Enter a number between 1 and 10 (0 to quit)", "Guessing Game", "0"))
            ' A guess of 0 means you want to give up.
            If guess = 0 Then
                gameOver = True
            Else
                ' Tests your guess and announces whether you are correct. Method takeAGuess
                ' is called multiple times with different guesses. The target value is not 
                ' accessible from Main and is not passed in.
                gameOver = takeAGuess(guess)
                Console.WriteLine("Guess of " & guess & " is " & gameOver)
            End If
        End While

    End Sub

    Delegate Function gameDelegate(ByVal aGuess As Integer) As Boolean

    Public Function makeTheGame() As gameDelegate

        ' Generate the target number, between 1 and 10. Notice that 
        ' target is a local variable. After you return from makeTheGame,
        ' it is not directly accessible.
        Randomize()
        Dim target As Integer = CInt(Int(10 * Rnd() + 1))

        ' Print the answer if you want to be sure the game is not cheating
        ' by changing the target at each guess.
        Console.WriteLine("(Peeking at the answer) The target is " & target)

        ' The game is returned as a lambda expression. The lambda expression
        ' carries with it the environment in which it was created. This 
        ' environment includes the target number. Note that only the current
        ' guess is a parameter to the returned lambda expression, not the target. 

        ' Does the guess equal the target?
        Dim playTheGame = Function(guess As Integer) guess = target

        Return playTheGame

    End Function

End Module

次の例は、入れ子になったラムダ式の幅広いアクセス権を示しています。 返されたラムダ式は、Main から aDel として実行されると、次の要素にアクセスします。

  • それが定義されているクラスのフィールド: aField

  • それが定義されているクラスのプロパティ: aProp

  • それが定義されている functionWithNestedLambda メソッドのパラメーター: level1

  • functionWithNestedLambda のローカル変数: localVar

  • それが入れ子になっているラムダ式のパラメーター: level2

Module Module3

    Sub Main()
        ' Create an instance of the class, with 1 as the value of 
        ' the property.
        Dim lambdaScopeDemoInstance =
            New LambdaScopeDemoClass With {.Prop = 1}

        ' Variable aDel will be bound to the nested lambda expression  
        ' returned by the call to functionWithNestedLambda.
        ' The value 2 is sent in for parameter level1.
        Dim aDel As aDelegate =
            lambdaScopeDemoInstance.functionWithNestedLambda(2)

        ' Now the returned lambda expression is called, with 4 as the 
        ' value of parameter level3.
        Console.WriteLine("First value returned by aDel:   " & aDel(4))

        ' Change a few values to verify that the lambda expression has 
        ' access to the variables, not just their original values.
        lambdaScopeDemoInstance.aField = 20
        lambdaScopeDemoInstance.Prop = 30
        Console.WriteLine("Second value returned by aDel: " & aDel(40))
    End Sub

    Delegate Function aDelegate(
        ByVal delParameter As Integer) As Integer

    Public Class LambdaScopeDemoClass
        Public aField As Integer = 6
        Dim aProp As Integer

        Property Prop() As Integer
            Get
                Return aProp
            End Get
            Set(ByVal value As Integer)
                aProp = value
            End Set
        End Property

        Public Function functionWithNestedLambda(
            ByVal level1 As Integer) As aDelegate

            Dim localVar As Integer = 5

            ' When the nested lambda expression is executed the first 
            ' time, as aDel from Main, the variables have these values:
            ' level1 = 2
            ' level2 = 3, after aLambda is called in the Return statement
            ' level3 = 4, after aDel is called in Main
            ' localVar = 5
            ' aField = 6
            ' aProp = 1
            ' The second time it is executed, two values have changed:
            ' aField = 20
            ' aProp = 30
            ' level3 = 40
            Dim aLambda = Function(level2 As Integer) _
                              Function(level3 As Integer) _
                                  level1 + level2 + level3 + localVar +
                                    aField + aProp

            ' The function returns the nested lambda, with 3 as the 
            ' value of parameter level2.
            Return aLambda(3)
        End Function

    End Class
End Module

デリゲート型への変換

ラムダ式は、互換性のあるデリゲート型に暗黙的に変換できます。 互換性に関する一般的な要件については、「厳密でないデリゲート変換」をご覧ください。 たとえば、次のコード例は、Func(Of Integer, Boolean) または一致するデリゲート シグネチャに暗黙的に変換するラムダ式を示しています。

' Explicitly specify a delegate type.
Delegate Function MultipleOfTen(ByVal num As Integer) As Boolean

' This function matches the delegate type.
Function IsMultipleOfTen(ByVal num As Integer) As Boolean
    Return num Mod 10 = 0
End Function

' This method takes an input parameter of the delegate type. 
' The checkDelegate parameter could also be of 
' type Func(Of Integer, Boolean).
Sub CheckForMultipleOfTen(ByVal values As Integer(),
                          ByRef checkDelegate As MultipleOfTen)
    For Each value In values
        If checkDelegate(value) Then
            Console.WriteLine(value & " is a multiple of ten.")
        Else
            Console.WriteLine(value & " is not a multiple of ten.")
        End If
    Next
End Sub

' This method shows both an explicitly defined delegate and a
' lambda expression passed to the same input parameter.
Sub CheckValues()
    Dim values = {5, 10, 11, 20, 40, 30, 100, 3}
    CheckForMultipleOfTen(values, AddressOf IsMultipleOfTen)
    CheckForMultipleOfTen(values, Function(num) num Mod 10 = 0)
End Sub

次のコード例は、Sub(Of Double, String, Double) または一致するデリゲート シグネチャに暗黙的に変換するラムダ式を示しています。

Module Module1
    Delegate Sub StoreCalculation(ByVal value As Double,
                                  ByVal calcType As String,
                                  ByVal result As Double)

    Sub Main()
        ' Create a DataTable to store the data.
        Dim valuesTable = New DataTable("Calculations")
        valuesTable.Columns.Add("Value", GetType(Double))
        valuesTable.Columns.Add("Calculation", GetType(String))
        valuesTable.Columns.Add("Result", GetType(Double))

        ' Define a lambda subroutine to write to the DataTable.
        Dim writeToValuesTable = Sub(value As Double, calcType As String, result As Double)
                                     Dim row = valuesTable.NewRow()
                                     row(0) = value
                                     row(1) = calcType
                                     row(2) = result
                                     valuesTable.Rows.Add(row)
                                 End Sub

        ' Define the source values.
        Dim s = {1, 2, 3, 4, 5, 6, 7, 8, 9}

        ' Perform the calculations.
        Array.ForEach(s, Sub(c) CalculateSquare(c, writeToValuesTable))
        Array.ForEach(s, Sub(c) CalculateSquareRoot(c, writeToValuesTable))

        ' Display the data.
        Console.WriteLine("Value" & vbTab & "Calculation" & vbTab & "Result")
        For Each row As DataRow In valuesTable.Rows
            Console.WriteLine(row(0).ToString() & vbTab &
                              row(1).ToString() & vbTab &
                              row(2).ToString())
        Next

    End Sub


    Sub CalculateSquare(ByVal number As Double, ByVal writeTo As StoreCalculation)
        writeTo(number, "Square     ", number ^ 2)
    End Sub

    Sub CalculateSquareRoot(ByVal number As Double, ByVal writeTo As StoreCalculation)
        writeTo(number, "Square Root", Math.Sqrt(number))
    End Sub
End Module

ラムダ式をデリゲートに割り当てるか、引数としてプロシージャに渡す場合、パラメーター名を指定できますが、データ型を省略することで、デリゲートから型を取得できます。

使用例

  • 次の例では、null 許容値型引数に値が割り当てられている場合は True を返し、値が Nothing の場合は False を返すラムダ式を定義します。

    Dim notNothing =
      Function(num? As Integer) num IsNot Nothing
    Dim arg As Integer = 14
    Console.WriteLine("Does the argument have an assigned value?")
    Console.WriteLine(notNothing(arg))
    
  • 次の例では、配列の最後の要素のインデックスを返すラムダ式を定義します。

    Dim numbers() = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    Dim lastIndex =
      Function(intArray() As Integer) intArray.Length - 1
    For i = 0 To lastIndex(numbers)
        numbers(i) += 1
    Next
    

関連項目