読者です 読者をやめる 読者になる 読者になる

VB.NETでも式木を扱いたい(その1)

はじめに

最近はReflectionでToStringするアレの開発に勤しんでいた弊社です。 かなりのウンコっぷりを全世界に発信している感じで非常にアレなのですが、GitHubでプライベートリポジトリを使おうとすると金が掛かるという割と切実な理由で公開しています。

いやまぁ、他のサービスを使えば良いだけなんですけどね。

『ReflectionでToStringするアレ(以下「アレ」と表記)』自体はC#で書いているんですけど、アレでやってる事をVBで紹介しているサイトがほぼ皆無なのでライブラリの宣伝も兼ねて軽く紹介したいです。したいんです。

ライブラリの説明

アレの機能ですが、名前の通りリフレクションを使って動的にToStringを構築します。

・・・ん?DebuggerDisplay属性?いいじゃん、そんなこと。それより、ロギングは大切にねッ。

アレの機能の概要はこちらを参照してください。 が、ここでも一応VBでサンプルコードを載っけておこうと思います。

今回は以下のクラスの文字列形式をアレで取得する場合を考えます。

Class Hoge
    Public Property MyProperty As Integer
    Public Property MyProperty2 As String
    Public MyField As String
End Class

単純にパブリックプロパティを含んだ文字列形式を得たい場合は以下のコードで十分です。

Dim hoge As Hoge = New Hoge() With
    {.MyProperty = 1, .MyProperty2 = "Hoge", .MyField = "HogeHoge"}
Dim str As String = ToStringBuilder.ToString(hoge)
Console.WriteLine(str)
Hoge{MyProperty=1,MyProperty2=Hoge}

この場合、アクセス修飾子に関わらずフィールドは無視されます。 フィールドを出力に含む場合はToStringConfig(Of T).OutputTargetFieldもしくはBothを指定します。

Dim hoge As Hoge = New Hoge() With
    {.MyProperty = 1, .MyProperty2 = "Hoge", .MyField = "HogeHoge"}
Dim conf As ToStringConfig(Of Hoge) = New ToStringConfig(Of Hoge)()
conf.OutputTarget = TargetType.Both

Dim str As String = ToStringBuilder.ToString(hoge, conf)
Console.WriteLine(str)
Hoge{MyProperty=1,MyProperty2=Hoge,MyField1=HogeHoge}

NothingもしくはToStringの結果が空白の要素を形式に含めない場合は、ToStringConfig(Of T).IgnoreModeNullもしくはNullOrWhiteSpaceを指定します。

Dim hoge As Hoge = New Hoge() With
    {.MyProperty = 1, .MyProperty2 = Nothing, .MyField = "HogeHoge"}
Dim conf As ToStringConfig(Of Hoge) = New ToStringConfig(Of Hoge)()
conf.IgnoreMode = IgnoreMemberMode.NullOrWhiteSpace

Dim str As String = ToStringBuilder.ToString(hoge, conf)
Console.WriteLine(str)
Hoge{MyProperty=1}

特定のフィールドもしくはプロパティを完全に無視する場合は、ToStringConfig(Of T).SetIgnoreMemberで無視するメンバーを指定します。

Dim hoge As Hoge = New Hoge() With
    {.MyProperty = 1, .MyProperty2 = "Hoge", .MyField = "HogeHoge"}
Dim conf As ToStringConfig(Of Hoge) = New ToStringConfig(Of Hoge)()
conf.SetIgnoreMember(Function(it) it.MyProperty)

Dim str As String = ToStringBuilder.ToString(hoge, conf)
Console.WriteLine(str)
Hoge{MyProperty2=Hoge}

式木の利用

アレでは主に式木を以下の二つの目的の為に使用しています。

  1. プロパティもしくはフィールドのアクセッサを動的に生成する為
  2. 無視するプロパティもしくはフィールドの情報を得る為

1のアクセッサの動的生成ですが、もしかしたら

リフレクションをつかえばいいじょないか

と思った方もいるかと思います。というよりフツーはそう考えるかもです。

ではなぜ面倒な式木を使うと言うと、リフレクションは遅いからです。 リフレクションで値を取得しようとするとアレで使っているアクセッサを使う方法の倍は遅いです。 後述しますが、アクセッサの生成は割とコストが重いので場合によりけりですが、繰り返す場合はそのコストを回収できるのでアレではプロパティもしくはフィールドの値を取得する為に使用しています。

2の無視するプロパティもしくはフィールドの情報を得るについてですが、本家Apache Commonsでは無視するプロパティの指定に文字列を指定しています。

が、アレではその方法を使ってません。 理由は簡単に言うとVisual Studioの支援を受けられないからです。

まず、文字列でフィールド名もしくはプロパティ名を指定した場合にスペルミスをする可能性があります。 ラムダ式から式木を構築し、それを解析する方法ですとラムダ式を書く時にインテリ☆センス™の支援を受けられます。 また、存在しないフィールドもしくはプロパティを書くとコンパイル時に検出できるので、誤りは可能な限り早期に検出するという考え方に即しています。

次に、文字列で指定しますとリファクタの名前の変更の対象となりませんが、ラムダ式は変更の対象になります。 NameOf演算子?知らない子ですね。

jyuch.hatenablog.com

おわりに

なんか、式木の『し』の時も出てこなかったですが、まぁ、その、次回以降に色々書くんで。ハイ

つづく