VB13以前でもNull条件演算子を使いたい
はじめに
ちょっと前にVB14の新機能についての話をしたじゃないですか。 その中にNull条件演算子なんてものがあったと思います。
弊社はEntity FrameworkみたいなライブラリでFirstOrDefault
みたいなクエリを割合ぽいぽい投げ返ってきた値がNothing
かそうでないかで処理を分岐させることが多いのですが、
Dim hoge = db.Hoges.Where(Function(it) it.UniqueKey = "HOGE").FirstOrDefault() Dim hogeName As String If Not hoge Is Nothing Then hogeName = hoge.Name Else hogeName = Nothing End If
みたいなコードをほぼ毎回のように書かなくてはならず非常にげんなりします。 まぁ、設計がクソなのも一理ありますが。
そこでNull条件演算子を使いたいのですが、現在使用している環境では使えないという最高にクソな感じに仕上がっており*1日夜冗長でクソなコードを生成している次第でございます。うおォン 俺はまるでクソコードジェネレータだ
あああああ
かくなる上はメタプログラミングを用いてごり押しで解決したいところなのですが、さすがに構文に介入することはできません。 コンパイル時に介入してASTを弄れれば話は別かもしれませんが。
Roslyn? なにそれ? おいしいの?
というわけで拡張メソッドでそれっぽく仕上げてお茶を濁すほかありません。
というわけでほい。
Module NullableExtension <Extension> Public Function N(Of TModel, TResult As Class)(value As TModel, expr As Func(Of TModel, TResult)) As TResult If value Is Nothing Then Return Nothing Else Return expr(value) End If End Function <Extension> Public Function Ns(Of TModel, TResult As Structure)(value As TModel, expr As Func(Of TModel, TResult)) As Nullable(Of TResult) If value Is Nothing Then Return Nothing Else Return expr(value) End If End Function <Extension> Public Function Ns(Of TModel, TResult As Structure)(value As TModel, expr As Func(Of TModel, Nullable(Of TResult))) As Nullable(Of TResult) If value Is Nothing Then Return Nothing Else Return expr(value) End If End Function End Module
とくに解説することも無いのですが、
N(Of TModel, TResult As Class)(value As TModel, expr As Func(Of TModel, TResult))
と
N(Of TModel, TResult As Structure)(value As TModel, expr As Func(Of TModel, TResult))
がオーバーロード不可なのはなんとなくわかりますが、
Ns(Of TModel, TResult As Structure)(value As TModel, expr As Func(Of TModel, TResult))
と
Ns(Of TModel, TResult As Structure)(value As TModel, expr As Func(Of TModel, Nullable(Of TResult)))
はオーバーロードできるんですね。驚きです。
んで、こんな感じに使います。
Dim a = New Hoge() With {.Id = 1, .Name = "Hoge", .ParentId = 0} Dim b = New Hoge() With {.Id = 1, .Name = "Hoge", .ParentId = Nothing} Dim c As Hoge Console.WriteLine(a.Ns(Function(it) it.Id)) Console.WriteLine(a.N(Function(it) it.Name)) Console.WriteLine(a.Ns(Function(it) it.ParentId)) Console.WriteLine(b.Ns(Function(it) it.Id)) Console.WriteLine(b.N(Function(it) it.Name)) Console.WriteLine(b.Ns(Function(it) it.ParentId)) Console.WriteLine(c.Ns(Function(it) it.Id)) Console.WriteLine(c.N(Function(it) it.Name)) Console.WriteLine(c.Ns(Function(it) it.ParentId))
おわりに
なんというか、拡張メソッドの乱用ですよね。
Optional
みたいなアプローチではなくnull
だろうがなんだろうがゴリ押しで値を取得するみたいなアプローチ、私は結構好きです。
ただ、実際に他人のコードでこんなのを見たら『うわぁ・・・』は必至ですね。
取り合えず設計をもう少しどうにかしろよ的な。
余談ですが、VBのラムダってFunction(it) it...
みたいに冗長なのが嫌いなんですよね。
同じ意味を持つC#のそれと比較してもit => it.Name
とFunction(it) it.Name
の差の大きいこと。
まぁ、そもそもC#/VBののラムダの内部に可変ステートを抱えられる仕様はちょっとどうかな~と思います。 シングルスレッドならともかく、マルチスレッドでうっかり可変ステートを外部に出してしまうとスレッドセーフの要件の一つである『不変なステートは常にスレッドセーフ』が途端に崩壊してしまうのでマルチスレッドで使うときはハラハラします。
とにかく、Decimalの内部構造を取り出して桁数を解析する以来のバッドノウハウの再来ということで。
おわり
*1:そもそもYield Returnが使えない。複数行ラムダはぎりぎり使える。あっ(察し)