LINQはうまく使えば非常に便利なのですが、残念ながらVBで書くと非常にゴチャゴチャします。C# なら => で済む所が、Function やら Sub やら長ったらしく、しかも使い分ける必要があるのが悩みどころです。どういう感じになるか、ひとつやってみます。
お題:小田急線の列車種別・線別の停車駅をクエリして、コンソールに出力します。「正解」はコチラです。
Step1: 駅名を保持するクラスを用意します。列車種別と線名にFlags属性が付いているところに注意してください。
Private Class Station
<Flags()>
Friend Enum TrainKind
各停 = 1
区間準急 = 2
準急 = 4
多摩急行 = 8
急行 = 16
快速急行 = 32
特急 = 64
End Enum
<Flags()>
Friend Enum Line
小田原線 = 1
多摩線 = 2
江ノ島線 = 4
End Enum
Property 駅名 As String
Property 線 As Line
Property 停車 As TrainKind
End Class
Step2:上で作ったクラスに初期値を入れておきます。 Sub main の冒頭でやっておけば十分です。(全駅を書くと大変なので、一部だけにします。)
Dim colStations As New List(Of Station)
With colStations
.Add(New Station With {.駅名 = "代々木上原", .線 = Station.Line.小田原線, .停車 = Station.TrainKind.多摩急行 Or Station.TrainKind.快速急行 Or Station.TrainKind.急行 Or Station.TrainKind.準急 Or Station.TrainKind.区間準急 Or Station.TrainKind.各停})
.Add(New Station With {.駅名 = "豪徳寺", .線 = Station.Line.小田原線, .停車 = Station.TrainKind.区間準急 Or Station.TrainKind.各停})
.Add(New Station With {.駅名 = "登戸", .線 = Station.Line.小田原線, .停車 = Station.TrainKind.多摩急行 Or Station.TrainKind.急行 Or Station.TrainKind.準急 Or Station.TrainKind.区間準急 Or Station.TrainKind.各停})
.Add(New Station With {.駅名 = "向ヶ丘遊園", .線 = Station.Line.小田原線, .停車 = Station.TrainKind.特急 Or Station.TrainKind.急行 Or Station.TrainKind.準急 Or Station.TrainKind.区間準急 Or Station.TrainKind.各停})
.Add(New Station With {.駅名 = "栗平", .線 = Station.Line.多摩線, .停車 = Station.TrainKind.急行 Or Station.TrainKind.多摩急行 Or Station.TrainKind.区間準急 Or Station.TrainKind.各停})
.Add(New Station With {.駅名 = "南林間", .線 = Station.Line.江ノ島線, .停車 = Station.TrainKind.急行 Or Station.TrainKind.各停})
End With
Step3:早速クエリを作ってみます。条件は「小田原線内の多摩急行停車駅」です。まずは、一行で条件を書いてみます。
Dim queryInLine = colStations.Where(Function(x) (x.停車 And Station.TrainKind.多摩急行) AndAlso (x.線 And Station.Line.小田原線)) _
.Select(Function(y) New With {.result = y.駅名 & "[" & y.線.ToString() & "]"})
For Each stationName In queryInLine
Console.WriteLine(stationName.result)
Next
一行で書こうとするとやはりゴチャゴチャします。
このうちwhereの部分だけををC# で書くとすると、
var query = colStations.Where(x => (x.停車.HasFlag(Station.TrainKind.多摩急行)) && (x.線.HasFlag(Station.Line.小田原線)));
と、かなりすっきりするのですが、ないものねだりをしても仕方がありません。
また、Selectで匿名クラスを作っています。匿名クラスのメンバーであろうと、インテリセンスは補完してくれます。
Step4:同じ条件で、複数行で書いてみます。VS2010からは「暗黙の行継続文字」という機能がついたので、狂ったように”_”を付けなくていいところが救いです。「暗黙の行継続文字」が適用される条件については、コチラをご覧下さい。
Dim queryWithLines = colStations.Where(Function(x)
Return (x.停車 And Station.TrainKind.多摩急行) _
AndAlso _
(x.線 And Station.Line.小田原線)
End Function
).ToArray()
Array.ForEach(queryWithLines, Sub(station As Station)
Console.WriteLine(String.Format("{0} [{1}]", station.駅名, station.線))
End Sub)
確かに冗長ですが、見通しはかなり良くなりました。
なお、Array.ForEachの中で直接Console.WriteLineを呼んでで見ましたが、ここは”Sub”であるところに注意してください。つまり、値を返さないので、”Function”ではないのです。この使い分けが若干面倒ではあります。
Step5:最後に、デリゲートを使ってみます。
Dim fncFilter = Function(x As Station)
Return (
(x.停車 And Station.TrainKind.多摩急行) _
AndAlso _
(x.線 And Station.Line.小田原線))
End Function
Dim fncWriteLine = Sub(x As Station)
Console.WriteLine(String.Format("{0} [{1}]", x.駅名, x.線))
End Sub
Dim queryUseDeligate = colStations.Where(Function(x) fncFilter(x))
Array.ForEach(queryUseDeligate.ToArray(), Sub(x As Station) fncWriteLine(x))
C#の「匿名メソッド」のようなものです(コチラによると厳密には違うそうですが)。ここでは細かいことは気にせず、「こういうやり方もありますよ」ということで。
デリゲートの変数名とか書く場所に気をつけてあげれば、見通しの良いコードになりそうな気がします。