こんにちは!現場でバリバリとシステムを組んでいると、「子画面で起きた出来事を、親画面にスマートに伝えたい」「バックグラウンド処理の進捗を、きれいな形でメインスレッドに通知したい」という壁にぶつかることありませんか?
マクロの記録や、ただ画面にボタンをペタペタ貼り付けるだけの開発から一歩抜け出し、プログラミングの本質である「部品の組み立て(コンポーネント指向)」をマスターすると、VB.NETでの開発は何倍も楽しく、そして強固になります。
今回は、実務中級者へのステップアップとして、`EventHandler(Of TEventArgs)` を使った「型安全なカスタムイベント」の設計術を徹底解説します。ここをクリアすれば、あなたもVB.NETの構造を意のままに操るエンジニアの仲間入りです。一緒にバッチリマスターしていきましょう!
—
1. なぜ「カスタムイベント」と「型安全」が必要なのか?
初心者の頃は、親フォームから子フォームを操作するために、子フォーム側に親フォームのインスタンスを直接渡したり、`Public` な変数を無理やり参照させたりしがちです。
しかし、それをやってしまうと「スパゲッティコード」の出来上がりです。クラス同士がガチガチに結びつき(密結合)、片方を改修するとあちこちが壊れる悪夢のシステムになってしまいます。
そこで登場するのが「イベント(Observerパターン)」です。
子側は「今、こういうデータが用意できたよ!」と外に向けて叫ぶだけ(発行)。親側は「その叫びを聞いたら、この処理をする」と耳を傾けておく(購読)。この仕組みによって、お互いの内部構造を知らなくても安全に通信できる「疎結合」な設計が実現できます。
さらに、昔ながらの `Object` 型を使ったイベントではなく、`.NET` のジェネリクス(Generics)を活用した `EventHandler(Of TEventArgs)` を使うことで、「コンパイル時にデータの型が完全に保証される(=型安全)」という絶大なメリットを手に入れられます。
—
2. 実装の全体像(イメージ図)
今回作る仕組みのイメージは以下の通りです。
[ Worker (子コンポーネント) ] —- (カスタムイベントを発行) —-> [ MainForm (親フォーム) ]
│ │
└─ 処理を実行中… 進捗データを `ProgressEventArgs` に詰める ──────┘
今回は、時間がかかる重い処理(ダミー)を実行し、その「進捗率(%)」と「メッセージ」を親フォームにリアルタイムで通知するコンポーネントを作ってみましょう。
—
3. 実践!型安全なカスタムイベントの作り方
それでは、実際のコードを見ていきます。
開発現場でそのままコピペして動かせるよう、詳細なコメントを添えています。
ステップ1:運搬するデータ(EventArgs)を定義する
まずは、イベントが発生したときに「どんなデータを持たせて相手に渡すか」を定義するクラスを作ります。これを `EventArgs` の派生クラスとして作成します。
Imports System
‘ 処理の進捗データを運ぶためのクラス
‘ EventArgs を継承するのが .NET のお作法です
Public Class ProgressEventArgs
Inherits EventArgs
‘ 読み取り専用プロパティとしてデータを保持(イミュータブルな設計が安全)
Public ReadOnly Property Percentage As Integer
Public ReadOnly Property Message As String
‘ コンストラクタで初期データを設定
Public Sub New(percentage As Integer, message As String)
Me.Percentage = percentage
Me.Message = message
End Sub
End Class
> 💡 先輩からのワンポイントアドバイス
> データの入れ物は、値が途中で勝手に書き換えられないよう `ReadOnly` プロパティ(または `Get` のみの定義)にしておくのが、バグを防ぐプロの技です。
—
ステップ2:イベントを発行する側(Workerクラス)を実装する
次に、実際に処理を行い、先ほど作った `ProgressEventArgs` を乗せたイベントを外部へ発信するクラスを作ります。
Imports System.Threading
Imports System.Threading.Tasks
Public Class HeavyWorker
‘ 【最重要】EventHandler(Of TEventArgs) を使った型安全なイベント宣言
‘ これにより、受け取る側は面倒な DirectCast や CType なしで安全にデータを受け取れます
Public Event ProgressChanged As EventHandler(Of ProgressEventArgs)
‘ 重い処理を実行するメソッド
Public Async Function DoWorkAsync() As Task
For i As Integer = 1 To 10
‘ 疑似的な重い処理(0.5秒待機)
Await Task.Delay(500)
Dim currentProgress As Integer = i 10
Dim currentMessage As String = $”処理中… {currentProgress}% 完了しました。”
‘ イベントを発火させる(メソッドを呼び出す)
‘ イベントが購読されている(Nothingではない)ことを確認して安全に呼び出します
RaiseEvent ProgressChanged(Me, New ProgressEventArgs(currentProgress, currentMessage))
Next
End Function
End Class
ここで使っている `EventHandler(Of ProgressEventArgs)` こが今回のキモです!
「このイベントが発火したときは、必ず `ProgressEventArgs` 型のデータが飛んできますよ」とコンパイラに約束させることで、型安全な世界を構築しています。
—
ステップ3:イベントを受け取る側(メインフォーム)を実装する
最後に、親フォーム側で `HeavyWorker` のインスタンスを生成し、イベントを購読(Handles または AddHandler)して画面に反映させます。
Public Class MainForm
Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
‘ ワーカークラスのインスタンス化
Dim worker As New HeavyWorker
‘ AddHandler を使ってイベントの「購読予約」をします
‘ ワーカーが叫んだとき(ProgressChanged)、どの処理を実行するかを紐付けます
AddHandler worker.ProgressChanged, AddressOf OnWorkerProgressChanged
btnStart.Enabled = False
lblStatus.Text = “処理を開始します…”
‘ 非同期で重い処理を実行
Await worker.DoWorkAsync()
lblStatus.Text = “すべての処理が完了しました!”
btnStart.Enabled = True
‘ 終わったら不要な購読を解除する(メモリリーク防止の観点からも重要)
RemoveHandler worker.ProgressChanged, AddressOf OnWorkerProgressChanged
End Sub
‘ イベントが発生したときに自動的に呼ばれるコールバックメソッド
‘ 引数の e が、先ほど子クラスから送られてきた型安全なデータです!
Private Sub OnWorkerProgressChanged(sender As Object, e As ProgressEventArgs)
‘ プログレスバーの更新
ProgressBar1.Value = e.Percentage
‘ メッセージの表示
lblStatus.Text = e.Message
‘ ログ出力など
Console.WriteLine($”[受信] {e.Percentage}% : {e.Message}”)
End Sub
End Class
—
4. 陥りやすい罠とエラー回避の知見
実務でこのパターンを使う際、中級者がハマりがちなポイントをいくつかシェアしておきます。ここを知っているだけで、トラブルシューティングのスピードが段違いになりますよ。
1. クロススレッド例外(Cross-thread operation not valid)
もしバックグラウンドスレッド(`Task.Run` や `BackgroundWorker` など)から直接 Windows Forms のコントロール(TextBox や ProgressBar)を操作しようとすると、例外が発生します。
実務では、イベントを受け取った側(親フォーム)で `InvokeRequired` をチェックするか、`Async/Await` を適切に用いてメインスレッドにコンテキストを戻す配慮が必要です。
2. メモリリーク(Event Handler Leak)
イベントの購読(`AddHandler`)をしたまま、親フォームやインスタンスの参照関係を放置すると、不要になったオブジェクトがガベージコレクション(GC)に回収されず、メモリリークを引き起こす原因になります。
使い終わったら `RemoveHandler` する、あるいは適切なライフサイクル管理を意識しましょう。
—
まとめ
今回は、VB.NETにおける `EventHandler(Of TEventArgs)` を活用した、型安全なカスタムイベントの設計術を解説しました。
- イベントを使うことで、コンポーネント同士を「疎結合」に保てる。
- `EventHandler(Of T )` を使えば、キャスト不要の型安全なデータ授受ができる。
- `EventArgs` の派生クラスはイミュータブル(読み取り専用)に設計すると堅牢になる。
ここをマスターすれば、あなたの書くVB.NETコードは一気にプロフェッショナルな品質へと昇華します。「マクロの延長」から脱却し、美しい設計のモダンな.NETアプリケーションを構築していきましょう。
もし分からない部分や、さらに深掘りしたい実務のシチュエーションがあれば、いつでもコメント欄や社内の先輩に聞いてくださいね。あなたのエンジニアライフを応援しています!
