数万件のマスターを瞬殺する:ComboBoxインクリメンタルサーチの「極限」最適化
業務アプリケーション開発において、最も頻繁に遭遇する「UIの死」は、数万件の得意先マスターを読み込んだ`ComboBox`のパフォーマンス劣化です。
「文字を入力するたびにUIが固まる」「絞り込み結果が出るまで1秒かかる」。これらは、UIスレッドに対する無知と、データ構造の設計ミスによる人災です。
今日は、Visual Basic .NETの力を使い、数万件のレコードをミリ秒単位でフィルタリングする「インクリメンタルサーチ」の極限設計を伝授します。
—
1. なぜ「標準のComboBox」は死ぬのか
多くの初学者は、`ComboBox.Items`に数万件のオブジェクトを放り込み、`StartsWith`などでループを回します。これが遅い理由は単純です。
1. 描画コスト: `ComboBox`は内部で`Items`の全要素を描画しようと計算します。
2. イベントの連鎖: 絞り込みのたびに`SelectedIndexChanged`や`TextChanged`が走り、再描画のオーバーヘッドが重なります。
3. 検索アルゴリズム: 線形探索(O(n))は、データ量が増えれば増えるほど確実に破綻します。
我々が目指すべきは、「UIスレッドの解放」と「メモリ上での超高速検索」の二段構えです。
—
2. 実装の設計思想:キャッシュと遅延評価
数万件のデータは、ComboBoxの中ではなく、メモリ上の`List(Of T)`に保持させます。UIには、絞り込まれた「表示用リスト」のみを随時バインドします。
高速化の鍵:
- Virtual Mode的なアプローチ: 全データを表示せず、絞り込み結果のみを表示する。
- 検索の正規化: 入力文字とマスターを比較する際、`ToUpper()`や`Normalize()`をあらかじめ済ませておく。
—
3. 実践:プロダクションコード(VB.NET)
以下のコードは、数万件のマスターを扱う際の実用的なテンプレートです。これをそのままクラスとして切り出してください。
Imports System.Collections.Generic
Imports System.Linq
Public Class FastSearchComboBox
‘ マスターデータ(メモリ保持)
Private ReadOnly _masterData As List(Of Customer) = New List(Of Customer)()
‘ 絞り込み用の一時保管場所
Private _filteredData As List(Of Customer) = New List(Of Customer)()
‘ コンストラクタでデータを一括ロード
Public Sub New(dataSource As IEnumerable(Of Customer))
_masterData.AddRange(dataSource)
End Sub
”’
”’
Public Sub Filter(searchText As String, comboBox As ComboBox)
If String.IsNullOrWhiteSpace(searchText) Then
comboBox.DataSource = Nothing
Return
End If
‘ LINQは強力だが、大量データの場合はPLINQ(Parallel LINQ)を検討せよ
‘ ここではメモリ上のリストを高速検索
_filteredData = _masterData.Where(Function(c) c.Name.Contains(searchText) OrElse c.Code.StartsWith(searchText)).Take(100).ToList()
‘ カクつきを抑えるため、DataSourceの更新は最小限にする
comboBox.DataSource = Nothing
comboBox.DataSource = _filteredData
comboBox.DisplayMember = “Name”
‘ ドロップダウンを自動で開く
If _filteredData.Count > 0 Then comboBox.DroppedDown = True
End Sub
End Class
Public Class Customer
Public Property Code As String
Public Property Name As String
End Class
—
4. 現場で生き残るための「鉄則」
① 「全件表示」は悪である
数万件をドロップダウンさせるUIは、ユーザーにとっても不便です。「入力しないと何も出ない」仕様にしましょう。入力文字が2文字以上になってから検索を開始するなどの閾値設定が、サーバーへの負荷とUIのレスポンスを劇的に改善します。
② タイマーによる「サンプリング」
`TextChanged`イベントに直接検索ロジックを書くのは最悪です。キーを入力するたびに重い処理が走ります。
`System.Windows.Forms.Timer`を300ms程度で設定し、最後の入力から一定時間が経過した後に検索を実行する「ディバウンス(Debounce)」処理を必ず実装してください。
③ データベースとの付き合い方
数万件のマスターなら、アプリ起動時に一度だけメモリ(`List(Of T)`)に展開し、検索はメモリ内で行うのが現代の業務PCスペックにおける正解です。DBへの都度アクセスは、ネットワーク遅延という解決不可能なボトルネックを招きます。
—
チーフアーキテクトからのアドバイス
コードを美しく書くことと、速く動かすことは別の次元の話ではありません。
「データがどうメモリに配置され、CPUがどうキャッシュを辿るか」を意識するだけで、あなたの書くアプリケーションは、他のエンジニアが書くものとは一線を画す「軽快なプロダクト」へと進化します。
「動けばいい」という時代は終わりました。数万件のデータをも軽々と扱う、エンジニアとしての矜持を持って設計にあたってください。
もし、さらに大規模なデータ(百万件規模)に挑むなら、`Trie木`構造のアルゴリズムを導入する時です。その扉を叩く準備ができたら、またここへ戻ってきてください。
