VB.NETで依存性注入をしてみた

最近、VB.NETで依存性注入をする必要がありました。
覚え書きとしてサンプルを残しておきます。

はじめに

C#で依存性注入をすることは簡単です。
ググればサンプルがすぐに見つかりますし、書籍も多数あります。
でも、VB.NETだとなかなかサンプルが見つかりません。
AIリテラシが無いから見つからないだけかな。まぁ、いいや。

サンプルについて

本記事で提供するサンプルは、フォーム上のボタンをクリックするると、「Hello World!」が表示されます。
テストコードも付けます。
開発プラットフォームはVisual Studio 2022です。

手順

プロジェクトを追加する

「新しいプロジェクトの作成」→言語:「Visual Basic」、プラットフォーム:「Windows」、プロジェクト:「デスクトップ」→「Windows フォームアプリ」を選択→「次へ」

プロジェクト名:「Presentation」、ソリューション名:「DependencyInjectionInVB」→「次へ」→「作成」

「ソリューション’DependencyInjectioInVB'(1/1のプロジェクト)」を右クリック→「追加」→「新しいプロジェクト」

言語:「Visual Basic」、プラットフォーム:「Windows」、プロジェクト:「ライブラリ」→「クラスライブラリ」を選択→「次へ」

プロジェクト名に「UseCase」と入力→「次へ」→「作成」

「ソリューション’DependencyInjectioInVB'(2/2のプロジェクト)」を右クリック→「追加」→「新しいプロジェクト」

言語:「Visual Basic」、プラットフォーム:「Windows」、プロジェクト:「xUnit」→「xUnitテストプロジェクト」を選択→「次へ」

プロジェクト名に「XUnitTests」と入力→「次へ」→「作成」

プロジェクト参照の追加

「ソリューションエクスプローラ―」で、「Presentation」の「依存関係」を右クリック→「プロジェクト参照の追加」→「UseCase」にチェック

「XUnitTests」の「依存関係」を右クリック→「プロジェクト参照の追加」→「Presentation」と「UseCase」の両方にチェック

NuGetパッケージのインストール

次に、「Microsoft.Extensions.DependencyInjection」というNuGetパッケージをインストールします。

先ず、「Presentation」を右クリック→「NuGetパッケージの管理」→「参照」タブ→検索欄に「dependency.injection」と打つと「Microsoft.Extensions.DependencyInjection」が現れるので、選択してから、右上の「インストール」をクリック

「UseCase」、「XUnitTests」でも同様にして「Microsoft.Extensions.DependencyInjection」をインストールします。

依存関係におけるパッケージやプロジェクトの状態が次のようになりましたでしょうか?

ちなみに、XUnitTestsに警告マークが出る場合は、次のようにすると消えます。
「XUnitTests」を右クリック→「プロパティ」→「ターゲットOS」で「Windows」を選択

コードを入力

FormMain

「Form1.vb」を右クリック→「名前の変更」→「FormMain.vb」に変更→「F7」を押す
現れたエディタに次のコードをコピー&ペースト

Imports Microsoft.Extensions.DependencyInjection
Imports UseCase

Public Class FormMain

    ' サービス(クラスのインスタンス)管理用変数
    Private _serviceCollection As New ServiceCollection

    ''' <summary>
    ''' Button1がクリックされた時の処理
    ''' </summary>
    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        ' ApplicationServicreをインスタンスして、その中のExecuteメソッドを呼び出す
        Dim applicationService As New ApplicationService(_serviceCollection)
        applicationService.Execute()
    End Sub

    ''' <summary>
    ''' フォームがロードされた時の処理
    ''' </summary>
    Private Sub FormMain_Load(sender As Object, e As EventArgs) Handles Me.Load
        ' IShowTextインタフェースに対応するクラスをShowTextして_serviceCollectionに登録する
        _serviceCollection.AddTransient(Of IShowText, ShowText)
    End Sub

    ''' <summary>
    ''' コントロールにテキストを設定するメソッド
    ''' </summary>
    ''' <param name="ctrl"> 対象のコントロール </param>
    ''' <param name="text"> 設定するテキスト </param>
    Private Sub SetText(ByVal ctrl As Control, ByVal text As String)
        ' 別スレッドからの呼び出しか否かを確認
        If ctrl.InvokeRequired Then
            ' 別スレッドからの呼び出しであれば、Invokeを使って再帰呼び出し
            Invoke(New SetTextCallback(AddressOf SetText), ctrl, text)
            Exit Sub
        End If
        ' コントロールのテキストを設定
        ctrl.Text = text
        ctrl.Refresh()
    End Sub

    ''' <summary>
    ''' コントロールにテキストを設定するためのデリゲート
    ''' </summary>
    ''' <param name="ctrl"> 対象のコントロール </param>
    ''' <param name="text"> 設定するテキスト </param>
    Private Delegate Sub SetTextCallback(ByVal ctrl As Control, ByVal text As String)

    ''' <summary>
    ''' コントロール名をstringで与えて、テキストを表示させるためのメソッド
    ''' </summary>
    ''' <param name="ctrlName"> 対象のコントロール名 </param>
    ''' <param name="text"> 表示するテキスト </param>
    Friend Sub SetText(ByVal ctrlName As String, ByVal text As String)
        ' コントロール名で指定されたコントロールを探し、テキストを設定する
        For Each ctrl As Control In Me.Controls
            If ctrl.Name = ctrlName Then
                SetText(ctrl, text)
                Exit Sub
            End If
        Next ctrl
    End Sub
End Class

依存性注入のため、インタフェースとクラスを結び付ける「_serviceCollection.AddTransient(Of IShowText, ShowText)」というコードは、C#の書き方と若干異なっていますので、要注意です。

SetTextのコードが半分を占めていますが、非同期のイベントからコントロールに文字を表示させるための工夫がされています。

次に簡単なフォームを用意します。

「FormMain」をダブルクリック→ツールボックスから「Button」と「TextBox」を1つずつドラッグして配置します。

フォーム上のButton1をダブルクリックすると、Button1_Clickイベントと紐づきます。

ShowText.vb

PresentationプロジェクトにShowTextクラスを追加します。
コードを少なくして、インタフェースへの対応を分かり易く示すためです。

「Presentation」を右クリック→「追加」→「新しい項目」→「クラス」→「名前」欄に「ShowText.vb」と入力→「追加」→次のコードをコピー&ペースト

Imports UseCase

''' <summary>
''' IShowTextインタフェースを実装したクラス
''' このクラスは、コントロールにテキストを表示するためのメソッドを提供する
''' </summary>
Public Class ShowText
    Implements IShowText
    ''' <summary>
    ''' コントロール名を指定して、テキストを表示するメソッド
    ''' </summary>
    ''' <param name="ctrlName">コントロールの名前</param>
    ''' <param name="text">表示するテキスト</param>
    Public Sub SetText(ctrlName As String, text As String) Implements IShowText.SetText
        ' 現在開いているFormMainのインスタンスを取得
        Dim formMain As FormMain = Application.OpenForms.OfType(Of FormMain)().FirstOrDefault()
        ' FormMainのSetTextメソッドを呼び出して、指定されたコントロールにテキストを設定する
        formMain?.SetText(ctrlName, text)
    End Sub
End Class

クラス名の次の行にある「Implements IShowText」と、関数宣言と同じ行にある「Implements IShowText.SetText」の呼び出し方がミソです。

FormMainのインスタンスを取得するコードも見所です。

以上でPresentationプロジェクトは終了です。

ApplicationService.vb

次にUseCaseプロジェクトです。

「Class1」を右クリック→「ApplicationService.vb」に変更→次のコードをコピー&ペースト

Imports Microsoft.Extensions.DependencyInjection

''' <summary>
''' アプリケーションのサービスクラス
''' 依存性注入を使用して、IShowTextインタフェースを実装したクラスを取得し、テキストを表示する
''' </summary>
Public Class ApplicationService
    '<summary>
    ''' IShowTextインタフェースを実装したクラスのインスタンスを取得するためのコンストラクタ
    ''' </summary>
    ''' <param name="serviceCollection">サービスコレクション</param>
    ''' </param>
    Private ReadOnly _showText As IShowText
    Public Sub New(serviceCollection As ServiceCollection)
        _showText = serviceCollection.BuildServiceProvider().GetService(Of IShowText)()
    End Sub

    ''' <summary>
    ''' Executeメソッド
    ''' IShowTextインタフェースを実装したクラスのSetTextメソッドを呼び出して、テキストを表示する
    ''' </summary>
    Public Sub Execute()
        _showText.SetText("TextBox1", "Hello World")
    End Sub
End Class

IShowText.vb

ShowText用のインタフェースを用意します。

「UseCase」を右クリック→「追加」→「新しい項目」→「インターフェイス」を選択→「名前」欄に「IShowText.vb」を入力→「追加」→次のコードをコピー&ペースト

''' <summary>
''' コントロールにテキストを表示するためのインタフェース
''' </summary>
Public Interface IShowText
    Sub SetText(ByVal ctrlName As String, ByVal text As String)
End Interface

以上でUseCaseプロジェクトは終了です。

ApplicationServiceTests.vb

最後にXUnitTestsプロジェクトです。

[UnitTest1.vb」の名前を「ApplicationServiceTests.vb」に変更し、次のコードを貼り付けます。

Imports Microsoft.Extensions.DependencyInjection
Imports UseCase
Imports Xunit

Namespace XUnitTests
    Public Class ApplicationServiceTests

        ''' <summary>
        ''' ApplicationServiceのExecuteメソッドのテスト
        ''' IShowTextインタフェースを実装したクラスのSetTextメソッドが正しく呼び出されることを確認する
        ''' </summary>
        <Fact>
        Sub ExecuteTest()
            ' serviceCollectionを作成し、IShowTextインタフェースを実装した本ファイル内のSampleクラスを登録
            Dim serviceCollection As New ServiceCollection
            serviceCollection.AddSingleton(Of IShowText, Sample)
            ' applicationServiceをインスタンス化
            Dim applicationService As New ApplicationService(serviceCollection)
            ' applicationServiceのExecuteメソッドを呼び出す
            applicationService.Execute()
            ' SampleクラスのSetTextメソッドで設定された引数を確認する
            Assert.Equal("TextBox1, Hello World", arguments)
        End Sub
    End Class

    ''' <summary>
    ''' テスト用のIShowTextインタフェースを実装したクラス
    ''' SetTextメソッドが呼び出された際の引数をグローバル変数のarguments変数に保持する
    ''' </summary>
    Public Class Sample
        Implements IShowText
        Public Sub SetText(ctrlName As String, text As String) Implements IShowText.SetText
            arguments = $"{ctrlName}, {text}"
        End Sub
    End Class
End Namespace

Common.vb

本xUnitテストは、IShowTestというインタフェースを持つSampleクラスが、Friend変数に引数の値(コントロール名とそこに表示する値)を書き出し、それが想定された値と一致することを確かめます。そのFriend変数を用意するクラスがCommon.vbです。

「XUnitTests」を右クリック→「追加」→「新しい項目」→「モジュール」→名前欄に「Common.vb」と入力→「追加」→次のコードをコピー&ペースト

' グローバル変数を定義するモジュール
Module Common
    Friend arguments As String
End Module

動作確認

メニュー直下の真ん中あたりにある「▶Presentation」をクリックするとフォームが開きます。
Button1をクリックすると、Hello Worldが表示されます。

テストも合格しました。

まとめ

VB.NETの依存性注入のサンプル記事を記しました。
Hello Worldを表示するだけですが、ノウハウが結構入っているのではないかと思います。

コメント

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