Visual Basic 初級講座 [改訂版]
VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

第34回 ラムダ式の使用

2021/5/16

この記事が対象とする製品・バージョン

VB2019 Visual Basic 2019 対象です。
VB2017 Visual Basic 2017 対象です。
VB2015 Visual Basic 2015 対象です。
VB2013 Visual Basic 2013 対象です。
VB2012 Visual Basic 2012 対象です。
VB2010 Visual Basic 2010 対象です。
VB2008 Visual Basic 2008 対象ですが、VB2008では使えない機能も扱っています。
VB2005 Visual Basic 2005 × 対象外です。ラムダ式はVB2008から導入された機能です。
VB.NET 2003 Visual Basic.NET 2003 × 対象外です。
VB.NET 2002 Visual Basic.NET (2002) × 対象外です。
VB6対応 Visual Basic 6.0 × 対象外です。

 

目次

 

1.ラムダ式

1-1.名前のない関数

ラムダ式とは、名前のない関数(FunctionおよびSub)のことです。

次の例はたし算を行うラムダ式の使用例です。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'ラムダ式を変数 add に代入
Dim add = Function(x As Integer, y As Integer) As Integer
              Return x + y
          End Function

Dim result1 As Integer = add(2, 3) 'result1 は 5 になります。
Dim result2 As Integer = add(4, 3) 'result2 は 7 になります。

この例ではラムダ式内のプログラムは Return の1行だけですが、通常の関数と同様に複数行記述することもできますし、If や Try や 他の関数の呼び出しなども記述できます。

次の例は複数行のラムダ式の例です。この例では、Function ではなく、 Sub を使っています。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

'ラムダ式を変数 log に代入
Dim log = Sub(message As String)
              Dim appPath As String = AppDomain.CurrentDomain.BaseDirectory
              Dim logFileName As String = appPath & "log.txt"
              Debug.WriteLine(logFileName & " にログを出力します。")

              Dim logMessage As String = Now.ToString("yyyy\/MM\/dd HH\:mm\:ss ") & message & Environment.NewLine

              IO.File.AppendAllText(logFileName, logMessage)
          End Sub


log("ラムダ式のテスト")
log("ログファイルの場所は 出力ウィンドウの デバッグ で確認できます。")

Debug.WriteLineで出力される場所

 

ラムダ式には名前がないので、変数に代入して、変数から呼び出します。

通常の関数(FunctionおよびSub)は変数には代入できないので、変数に代入できるという点はラムダ式の大きな特色です。

メモ メモ  - ラムダを英語で書くと Lambda

ラムダの英語のスペルは Lambda です。途中で発音しない b があるので、わかりにくいです。

基となっているのはギリシア語の小文字アルファベットの λ (ラムダ)です。1930年代にアロンゾ・チャーチという数学者は、考案した数学理論を表記するときにオリジナルの記号を使っていたようなのですが、その記号は印刷しにくかったので、印刷するときに形が似たギリシア語の λ (ラムダ) を採用してしまったらしいです。このあたり少し経緯がはっきりしないようですが、印刷会社(?)が勝手に λ (ラムダ) にしてしまったという話もあるようです。ともかく、今日ではこの記号 ラムダ がこの数学理論を象徴する名前となっているようです。私はこの数学理論についてはまったくわかりませんが、プログラムにおけるラムダ式を実現するにあたりこの理論が下敷きとなっているらしいです。

参考:ラムダ計算 - Wikipedia

 

1-2.1行形式のラムダ式

ラムダ式の内容が1行だけの場合、特別な短い形式で記述することができます。

Sub の場合は、 Sub と内容を同じ行に書いて、End Sub を省略します。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim sample = Sub() Debug.WriteLine("1行だけのラムダ式")

sample()

この後ろに End Sub を記述すると構文エラーです。

 

Function の場合は、内容が1行しかないということは、その1行は Return 文であるはずです。

そのため、Function と内容を同じ行に書いて、Return と End Function を省略します。

さらに戻り値の型を定義する As xxxxx も省略します。VBは戻り値の型を省略しても、内容から型を推論できます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim sample = Function(x As Integer, y As Integer) x + y

Dim result = sample(2, 3) 'result は Integer と推論され、値は 5 になります。

この1行形式のラムダ式は、Return が省略されているため、慣れないうちは何を意味しているのかわかりにくいかもしれません。

これに対して最初に紹介したSub ~ End Sub, Function ~ End Function で記述するラムダ式を複数行形式のラムダ式と呼びます。

 

2.ラムダ式の型

2-1.Action と Func

VBはラムダ式の型を推論できるので、明示的に型を指定する必要はありません。あえて明示したい場合は、Subの場合、Action(読み方:Action=アクション)、Functionの場合、Func(読み方:Func=ファンク)を使用します。

フレームワークや他の人が作ったプログラムでラムダ式を指定できる場所の型には、Action や Func が使われる場合が多いので、 プログラム中のヒントで Action や Func を見かけたら、それは、その位置にラムダ式を記述できるという意味です。

たとえば、次のようなインテリセンスを見かけることがあります。

Whereメソッドの引数に Func(Of String, Boolean) 型のものを指定できるという意味であり、要するにラムダ式を引数に使えるという意味になります。どのようなラムダ式を指定すればよいのでしょうか。

この章ではAction や Func と ラムダ式の関係を説明します。その後で、実際に上記のようなヒントから、ラムダ式を使うプログラム方法も説明します。

 

引数のない Sub

引数のない Sub のラムダ式は次のようにActionを使って型を明示的に記述できます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Dim s1 As Action = Sub() Debug.WriteLine("TEST")

 

引数が1つある Sub

引数の型は Actionの型パラメーターになります。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Dim s2 As Action(Of String) = Sub(x As String) Debug.WriteLine("TEST")

この例では、Sub(x As String) の As String は省略できます。Action(Of String)と型が明示されているのでVBが型を推論できるからです。

 

引数が2つある Sub

引数の数が増えると型パラメーターの数も増えます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Dim s3 As Action(Of String, Integer) = Sub(x As String, y As Integer) Debug.WriteLine("TEST")

この例でも、型推論ができるので右辺の As String と As Integer も省略可能です。

 

引数のない Function

Functionの場合、Funcを使って型を明示できます。戻り値の型が型パラメーターになります。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Dim f1 As Func(Of Integer) = Function() 1

 

引数が1つある Function

引数がある場合は、型パラメーターになります。戻り値の型は常に最後の型パラメーターです。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Dim f2 As Func(Of String, Integer) = Function(x As String) 1

Sub の場合と同様に、右辺の As String も省略可能です。

 

引数が2つある Function

引数が複数ある場合は、型パラメーターの数が増えます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Dim f3 As Func(Of String, Date, Integer) = Function(x As String, d As Date) 1

この例でも右辺の As String と As Date は型推論にまかせて省略することができます。

 

 

 

2-2.Predicate

あまり見かけませんが、Action と Func 以外のラムダ式もあり得ます。比較的多いのは Predicate(読み方:Predicate=プレディケイト) です。これは引数が1つで、戻り値が Boolean であるFunc とです。


Dim p1 As Predicate(Of Integer) = Function(x As Integer) True

つまり、Predicate(Of T) とは Func(Of T, Boolean) と同じラムダ式を表します。

歴史的には Predicateの方が早くから存在し、その後により汎用的であるActionとFuncが登場したという流れです。そのため、新しい機能でPredicateを見かけることはまずないです。

 

3.条件を指定するラムダ式

3-1.単語のリスト

Whereメソッドを例にラムダ式の使いどころを説明します。はじめなのでかなり丁寧に説明します。

配列やコレクションには Where(読み方:Where = ホエア)という拡張メソッドがあり、条件を指定して当てはまるものだけを抽出することができます。 この条件を指定するのにラムダ式を使用できます。

次の例は、英語の文章をスペースで区切ってListの要素にします。

VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = "Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku"
Dim names As New List(Of String)(content.Split(" "))

この時点で names の内容は Himiko, also, known, as, Shingi, Wao, was, a, shamaness-queen, of, Yamatai-koku, in, Wakoku の 13項目になります。

ここから、先頭が大文字の単語だけを抽出したい場合に Where メソッドを使うと楽です。

 

3-2.ヒントから必要なラムダ式を読み解く

Whereメソッドの引数を見ると predicate As Func(Of String, Boolean) となっています。

このヒントから次のことが読み取れます。

 

つまり、ここで指定できるラムダ式は複数行形式で書けば次のようなものです。下記の処理内容は適当です。

Function (xxx As String) As Boolean
    Return True
End Function

1行形式のラムダ式では、戻り値の型と Return と End Function を省略するので次のようになります。さらに、この状況だとVBは引数の型も推論できるので、下記では引数の型である As String も省略しました。


Function (xxx) True

 

3-3.ラムダ式に書くべき内容

それで、ラムダ式の内容としてどのようなプログラムを書くべきでしょうか?

そもそもこのラムダ式はどこから呼び出されるのでしょうか?

ここまでのプログラムで names は 13個の単語からなる List になっていました。

Whereメソッドは、この単語ごとに 引数で指定されたラムダ式を呼び出します。つまり、この場合だと指定したラムダ式は13回呼び出されます。それぞれの呼び出し時にWhereメソッドはラムダ式の引数に単語を1つ渡します。ラムダ式が True を返した場合、その単語は条件に合致するものと判断します。

つまり、ここでラムダ式に記述すべき内容とは引数の先頭が大文字である場合 True を返すプログラムです。

文字が大文字であるか判断するには Charクラスの共有メソッド IsUpper が使用できます。

文字列から先頭の文字を取り出すには 変数に (0) を付けます。

結果的に次にようになります。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Function(word As String) As Boolean
    Return Char.IsUpper(word(0))
End Function

1行形式のラムダ式だと 戻り値の型と Return と End Function を省略できるので、次のように書けます。引数の型も推論できるので省略しています。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019


Function(word) Char.IsUpper(word(0))

 

3-4.完成版

このラムダ式を組み込んで、最後に結果を表示する処理も記述すると次のようになります。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = "Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku"
Dim names As New List(Of String)(content.Split(" "))

'先頭が大文字のものだけ抽出するラムダ式を適用(まだ抽出処理は行われない)
Dim capitals = names.Where(Function(word) Char.IsUpper(word(0)))

'抽出処理を実行して結果を表示
For Each word In capitals
    Debug.WriteLine(word)
Next

Debug.WriteLineで出力される場所

 

3-5.ラムダ式が呼び出されているか調べる

ラムダ式がどのように呼び出されているか調べるロジックを追加してみましょう。

こうなると単純に Return だけするわけではなくなるので、1行形式のラムダ式では記述できず、複数行形式のラムダ式を使用することになります。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = ("Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku")
Dim names As New List(Of String)(content.Split(" "))

'先頭が大文字のものだけ抽出するラムダ式を適用(まだ抽出処理は行われない)
Dim capitals = names.Where(Function(word)
                               Debug.WriteLine("引数 word = " & word)
                               If Char.IsUpper(word(0)) Then
                                   Debug.WriteLine("  先頭は大文字です。")
                                   Return True
                               Else
                                   Debug.WriteLine("  先頭は大文字ではありません。")
                                   Return False
                               End If
                           End Function)

'抽出処理を実行して結果を表示
Debug.WriteLine("結果の抽出を開始します。")
For Each word In capitals
    Debug.WriteLine("  " & word & " は抽出されました。")
Next

結果、出力ウィンドウのデバッグには次のように出力されます。

結果の抽出を開始します。
引数 word = Himiko
  先頭は大文字です。
  Himiko は抽出されました。
引数 word = also
  先頭は大文字ではありません。
引数 word = known
  先頭は大文字ではありません。
引数 word = as
  先頭は大文字ではありません。
引数 word = Shingi
  先頭は大文字です。
  Shingi は抽出されました。
…以下省略…

これを見ると、ちゃんと単語ごとにラムダ式が呼び出されていることがわかります。そして、True を返したときだけ、結果として生き残っています。

もう1つ重要なポイントが隠れています。抽出処理は「結果の抽出を開始します」という出力以降に実施されているという点です。

Whereメソッドを呼び出しただけでは、ラムダ式は全く呼び出されていないということです。これはラムダ式に関連した重要な特徴の1つで、「遅延評価」や「遅延実行」と呼ばれる場合もあります。Whereメソッドはラムダ式を実行せずに式として保持しておき、本当にそれを実行する必要が生じたときにようやくラムダ式が呼び出されるという仕掛けです。この例では For Eachで列挙処理をしているので、列挙するときには、抽出が完了している必要があるため、これを合図にラムダ式が呼び出されるように仕込まれています。

 

なお、ラムダ式内でもブレークポイントを設定することができるので、Visual Studioのデバッグ機能を使ってラムダ式がどのように実行されているか調べることもできます。

 

4.処理を指定するラムダ式

先頭が大文字の単語だけ抽出する例をもう1度見てみます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = "Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku"
Dim names As New List(Of String)(content.Split(" "))

'先頭が大文字のものだけ抽出するラムダ式を適用(まだ抽出処理は行われない)
Dim capitals = names.Where(Function(word) Char.IsUpper(word(0)))

'抽出処理を実行して結果を表示
For Each word In capitals
    Debug.WriteLine(word)
Next

Debug.WriteLineで出力される場所

最後に結果を表示するために For Each でループを記述している部分は、ListクラスのForEachメソッドを使って書き換えることができます。

Listクラスの ForEach メソッドは引数にラムダ式を指定すると、コレクションの各要素に対して、そのラムダ式を呼び出してくれる機能です。ここでもラムダ式が登場します。

これを使うと、ループの構文を使用しないで、コレクションの要素1つずつに処理を適用するプログラムが可能になります。

まずは、変数capitals を ToList メソッドで List(Of String)型に変換してから、ForEachメソッドの呼び出しのヒントを確認します。

引数は action As Action(Of String)となっており、 このヒントから次のことが読み取れます。

つまり、引数に要素を渡すので、処理したい内容をラムダ式で記述すればよいというシンプルなものです。

Debug.WriteLine を実行したいだけなので1行形式のラムダ式で次のようにシンプルに書けます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = "Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku"
Dim names As New List(Of String)(content.Split(" "))

'先頭が大文字のものだけ抽出するラムダ式を適用(まだ抽出処理は行われない)
Dim capitals = names.Where(Function(word) Char.IsUpper(word(0)))

'抽出処理を実行して結果を表示
capitals.ToList.ForEach(Sub(word) Debug.WriteLine(word))

これでも機能しますが、もっと短く書くこともできます。

たとえば、次のようにできます。

VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = "Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku"
content.Split(" ").Where(Function(word) Char.IsUpper(word(0))).ToList.ForEach(Sub(word) Debug.WriteLine(word))

この書き方は Split メソッドの戻り値から直接 Whereメソッドを呼び出して、またその戻り値から直接 ToListメソッドを呼び出して…というようにメソッドを次々とつないでいく書き方が特徴です。このような記述方法は「メソッドチェーン」と呼ばれることもあります。

この例の場合だと、どんな処理をやっているのかわかりにくくなりますから、 適当なところで改行して少し見やすくすると良いと思います。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Dim content = "Himiko also known as Shingi Wao was a shamaness-queen of Yamatai-koku in Wakoku"
content.Split(" ").
    Where(Function(word) Char.IsUpper(word(0))).
    ToList.
    ForEach(Sub(word) Debug.WriteLine(word))

たまにプログラムの行数の少なさにこだわる人がいますが、行数が増えても見やすさを優先すべきです。プログラムが見やすければ間違いを発見しやすいですし、機能の変更や修正もやりやすいです。

とはいえ、どんなプログラムが見やすいかは人それぞれで、最後に紹介したようにメソッドチェーンを改行で整形するプログラムが見やすいと感じる人もいれば、ラムダ式を使わないで For Each ~ Next で書いた方が行数は多くなるけど処理がわかりやすいと感じる人もいます。

 

 

5.イベントを処理するラムダ式

ラムダ式を使ってイベントの処理を記述することもできます。

画面を作るアプリケーションでは Click イベントや Loadイベントなどのイベントが発生したタイミングで、イベントハンドラーとしてプログラムしておいたメソッドを実行することがよくありますが、このメソッドをラムダ式で記述することができます。

※「イベント」は画面を作るアプリケーション以外でも使用できますが、ClickイベントやLoadイベントは簡単に扱えるわかりやすいイベントなのでイベントの代表として取り上げています。

たとえば、通常 イベントとメソッドを結びつけるには Handles キーワードを使用します。

次の例はWindowsフォームアプリケーションで、ButtonのClickイベントを Button1_Clickメソッドに結びつけています。

実際に試してみる場合は、Windowsフォームアプリケーションプロジェクトを新規作成しButtonを1つ配置してダブルクリックしてみてください。自動的に Handles キーワードを含んだ空のメソッドが生成されます。

VB2002 VB2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    MsgBox("Hello!")
End Sub

この状態だとClickイベントと Button1_Clickメソッドが結びついているので、ボタンをクリックするとButton1_Clickメソッドが実行されます。メソッドの名前は重要ではなく変更することが可能です。

AddHandlerステートメント (読み方:AddHandler=アドハンドラー)を使って同じようにイベントとメソッドを結びつけることもできます。

VB2002 VB2003 VB2005 VB2008 VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    AddHandler Button1.Click, AddressOf Button1_Click

End Sub

Private Sub Button1_Click(sender As Object, e As EventArgs)
    MsgBox("Hello!")
End Sub

 

入力中に表示されるインテリセンスには、ここにラムダ式が指定できるとはっきり書いてあります。(環境によってはこのように書かれていないかもしれません)

メモ メモ  - デリゲート

インテリセンスには もう1つ、「デリゲート」というものも指定できるように書かれています。デリゲートとはメソッドの定義を型として定義する技術です。ラムダ式の登場によってデリゲートを意識することはまずなくなりました。ラムダ式やイベントなどの仕組みはこの内部ではこのデリゲートという技術によって支えられています。次回もう少し取り上げる予定です。

 

ラムダ式で書き換えると次のようになります。

VB2010 VB2012 VB2013 VB2015 VB2017 VB2019

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

    AddHandler Button1.Click, Sub (eventSender, eventE)
                                  MsgBox("Hello!")
                              End Sub  

End Sub

型推論できるため、ラムダ式の引数に型を明示する必要はありません。