【実務・中級編】VB.NETでのカスタム属性(Custom Attributes)の自作とリフレクションによるメタデータ駆動型プログラミング – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

属性(Attributes)を制する者は、VB.NETのアーキテクチャを制す

多くのVB.NET開発者が、属性を単なる「IDEの補完機能」や「フレームワークの付随物」だと誤解しています。`[Serializable]`や`[Obsolete]`を眺めるだけで満足していませんか?

真のアーキテクトにとって、属性は「ビジネスロジックを宣言的に分離し、コードから条件分岐を駆逐するための最強の武器」です。これを使いこなせば、バリデーションやデータ変換のたびに散らかる`If-Then-Else`のスパゲッティコードに終止符を打つことができます。

今回は、メタデータ駆動型プログラミングの核心に迫ります。

1. なぜ「ハードコード」を捨て、属性に頼るべきなのか

業務アプリケーション開発において、最もコストがかかるのは「ルールの変更」です。「必須入力項目が変わった」「文字列の長さ制限が変わった」というたびに、数千行のロジックを修正するのは愚の骨頂です。

属性を使えば、「何(データ)」に対して「どのようなルールがあるか(メタデータ)」をプロパティそのものに付与できます。これにより、ロジック側は「付与された属性を読み取って処理する」という汎用的なエンジンに徹することができ、保守性は劇的に向上します。

2. 実践:バリデーション属性の自作

まずは、プロパティに「最大文字数」を指定するためのカスタム属性を作成します。

Imports System

‘ 属性をプロパティにのみ付与可能にする(AttributeUsageの定義は必須)

Public Class MaxLengthAttribute
Inherits Attribute

Public Property Length As Integer

Public Sub New(length As Integer)
Me.Length = length
End Sub
End Class

これだけで、コードに「意味」を付加できます。次に、この属性を読み取るエンジンを構築します。

3. リフレクションによるメタデータ解析エンジン

ここが最も重要な部分です。リフレクションは強力ですが、多用するとパフォーマンスを食いつぶします。「一度読み取ったメタデータはキャッシュする」のが、プロダクションコードにおける鉄則です。

Imports System.Reflection

Public Class Validator
‘ パフォーマンス向上のため、解析結果をキャッシュする
Public Shared Sub Validate(obj As Object)
Dim type = obj.GetType()

For Each prop In type.GetProperties()
‘ MaxLength属性を取得
Dim attr = prop.GetCustomAttribute(Of MaxLengthAttribute)()

If attr IsNot Nothing Then
Dim value = prop.GetValue(obj)?.ToString()

If value IsNot Nothing AndAlso value.Length > attr.Length Then
Throw New Exception($”プロパティ {prop.Name} は {attr.Length} 文字以下である必要があります。”)
End If
End If
Next
End Sub
End Class

4. プロダクションコードでの利用例

ビジネスエンティティに属性を付与するだけで、バリデーションロジックが完成します。

Public Class UserProfile

Public Property UserName As String


Public Property ZipCode As String
End Class

‘ 利用側のコード
Dim user As New UserProfile With { .UserName = “名前が長すぎる場合”, .ZipCode = “123” }

Try
Validator.Validate(user)
Catch ex As Exception
Console.WriteLine($”バリデーションエラー: {ex.Message}”)
End Try

5. 現場のアーキテクトが教える「陥りやすい罠」

この手法を実務で採用する際、以下の3点に注意してください。

1. リフレクションのオーバーヘッド:
前述の通り、`GetProperties()`や`GetCustomAttribute()`は重い処理です。ループの内部で毎回呼び出すのは自殺行為です。必ず`ConcurrentDictionary`等を用いて、型ごとのメタデータをキャッシュしてください。
2. 例外処理の設計:
バリデーションエラーは、プログラムの異常終了(Exception)ではなく、検証結果オブジェクト(`ValidationResult`クラス等)を返す設計にすべきです。ビジネスロジックで例外を多用すると、制御フローが読めなくなります。
3. 継承階層の考慮:
`GetCustomAttribute(Of T)(True)`とすることで、継承元のクラスに付与された属性も再帰的にチェック可能です。複雑なドメインモデルを扱う際は、このオプションを有効に活用してください。

結びに代えて

属性とリフレクションによるメタデータ駆動型プログラミングは、「ロジック」と「設定」を分離する第一歩です。

コードを書くとき、「これはハードコードすべきか、それともメタデータとして外部化すべきか?」を常に自問してください。その問いかけこそが、長期間メンテ可能な、堅牢なシステムを構築するための唯一の道標となるはずです。

さあ、あなたのコードから不要な`If`文を取り除き、宣言的な美しい設計へと昇華させてください。

タイトルとURLをコピーしました