【テクニカル・上級編】初心者向け:VB.NETにおけるCInt、CLng、Val関数の挙動差異とオーバーフロー対策:レガシーな数値変換からモダンなTryParseへの移行 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

数値変換の罠と極限最適化:CInt、CLng、ValからモダンTryParseへのパラダイムシフト

レガシーなVisual Basic 6 (VBA) システムから、現代の.NET (VB.NET / .NET 8) アーキテクチャへ移行する際、最も多くのエンジニアが足元をすくわれるのが「数値型変換」の挙動差異である。

「動いていたはずのコードが、環境が変わった瞬間に突然のOverflowExceptionでクラッシュする」
「外部APIやレガシーDBから飛んできた不整形な文字列で、夜間バッチが停止した」

これらは単なるコーディングミスではない。VBの歴史的背景、CLR(共通言語ランタイム)のメモリモデル、そして型システムの厳密性を理解していないがために起こる、構造的な必然なのだ。

本稿では、`CInt`、`CLng`、`Val` という古の関数群が持つ極めて危険な挙動のメカニズムを解剖し、現代のエンタープライズ開発において許容される唯一の解である `TryParse` パターンへの移行戦略を、チーフアーキテクトの視点から徹底解説する。

1. 比較:レガシー関数群の正体と暗黙の罠

まずは、長年VBエンジニアを悩ませてきた3つの関数の挙動を、CLRの内部挙動と照らし合わせて整理する。

| 関数名 | 起源・背景 | 境界値超えの挙動 | 無効文字の挙動 | パフォーマンス |
| :— | :— | :— | :— | :— |
| `CInt` | VB6からの移植 | `OverflowException` | `InvalidCastException` / 例外発生 | 低(型判定・オーバーヘッド大) |
| `CLng` | VB6からの移植 | `OverflowException` | `InvalidCastException` / 例外発生 | 低(型判定・オーバーヘッド大) |
| `Val` | BASICの遺物 | 最大値で丸め(例外なし) | 先頭から数値として読める部分まで抽出(混入文字無視) | 極めて低(文字列パースの独自実装) |

`CInt` と `CLng`:厳密だが例外で爆発する

これらはVBのランタイムライブラリ(`Microsoft.VisualBasic.Conversion` 名前空間)に属する関数である。
内部的には `Convert.ToInt32` や `Convert.ToInt64` に近い安全チェックを行っているが、オーバーフロー時は容赦なく例外をスローする

特に注意すべきは、`CInt` は 32ビット符号付き整数(`-2,147,483,648` から `2,147,483,647`)、`CLng` は 64ビット符号付き整数(`.NET Framework` の VB6互換では Integer が 16bit だった名残で `CLng` は 32bit だったが、VB.NETでは `Long` は 64ビット)を扱う点である。
レガシーコードをVB.NETに移行した際、`Long` のビット幅の変化(32bit → 64bit)により、メモリレイアウトやP/Invoke(Windows API連携)で予期せぬ不整合を生む温床となる。

`Val`:百害あって一利なしの「レガシーの呪物」

VB6世代の開発者が好んで使った `Val` 関数だが、.NET環境においては使用を厳禁とすべきである。
`Val` は、文字列の先頭から数値をスキャンし、数値以外の文字(全角スペース、アルファベット等)に遭遇した時点でパースを打ち切る。さらに、オーバーフローが発生しても例外を吐かず、型の最大値で丸め込む。

.net
‘ Val関数の恐るべき挙動
Dim val1 As Double = Val(“12345円”) ‘ 結果: 12345 (単位を勝手に無視する)
Dim val2 As Double = Val(“ABC12345”) ‘ 結果: 0 (文字混入でゼロに化ける)
Dim val3 As Double = Val(“99999999999999999999”) ‘ オーバーフローしても例外なし

この「勝手に解釈する」挙動は、金融システムや厳密な在庫管理システムにおいて、サイレントエラー(データ破損)を引き起こす最悪のバグの温床となる。

2. 現場で頻発する障害:なぜ例外処理(Try-Catch)での対策は悪手なのか?

「不正な値が来たら例外をキャッチすればいい」という安易な設計は、高負荷システムやマルチスレッド環境において致命的なパフォーマンス低下を招く。

.net
‘ 【アンチパターン】例外制御によるパース処理
Dim inputStr As String = “123A45”
Dim result As Integer

Try
‘ CIntやConvertは失敗時に例外をスローする
result = CInt(inputStr)
Catch ex As Exception
‘ 例外オブジェクトの生成コスト(スタックトレースの構築)は極めて重い
result = 0
End Try

例外(Exception)のコストを知る

.NETのランタイムにおいて、`Throw` が実行されると、現在のスタックトレースをキャプチャするためにCPUサイクルとメモリが激しく消費される。
通常のビジネスロジックにおいて、ユーザー入力のミスや外部APIからの異常値は「例外的な事象(Exception)」ではなく、「ビジネスプロセスにおいて日常的に発生する分岐条件」である。これを例外機構で処理するのは、蚊を倒すために大砲を撃つようなものであり、GC(ガベージコレクション)の負担を増大させ、スループットを著しく低下させる。

3. シニアが選ぶモダン解:`Integer.TryParse` によるゼロ・エクスセプション設計

.NET Framework 2.0以降(およびすべての .NET Core / .NET 8 以降)、文字列からの安全な数値変換のデファクトスタンダードは、各数値型が提供する `TryParse` メソッドである。

例外を一切スローせず、戻り値(Boolean)で成否を返し、outパラメータ(VB.NETでは `ByRef` / `As Integer`)に結果を格納する。これにより、スタックトレースの生成コストを完全に排除できる。

実践:堅牢なTryParseラッパーの実装

現場のコードベースで散在しがちなパース処理を統一するため、次のような拡張メソッド、あるいは安全なユーティリティ関数としてカプセル化するのがシニアの流儀である。

.net
Imports System.Runtime.CompilerServices

Public Module NumberParsers

”’

”’ 安全にIntegerへ変換する。失敗時はデフォルト値を返す。
”’


Public Function ToInt32Safe(input As String, Optional defaultValue As Integer = 0) As Integer
Dim parsedValue As Integer

‘ TryParseは例外を発生させず、Booleanの成否のみを返すため高速かつ安全
If Integer.TryParse(input, parsedValue) Then
Return parsedValue
End If

Return defaultValue
End Function

”’

”’ 厳密な数値検証付きの64ビット整数変換(API連携・DBパラメータ用)
”’


Public Function ToInt64Safe(input As String, Optional defaultValue As Long = 0L) As Long
Dim parsedValue As Long

If Long.TryParse(input, parsedValue) Then
Return parsedValue
End If

Return defaultValue
End Function

End Module

この拡張メソッドを使用することで、呼び出し側のコードは劇的にクリーンになり、かつ堅牢性が担保される。

.net
‘ 呼び出し側のコード例
Dim rawInput As String = ” -123456 ”
Dim safeInt As Integer = rawInput.ToInt32Safe(0)

4. エンタープライズ開発における極限の知見:メモリとパフォーマンスの最適化

最後に、極限のパフォーマンスが要求されるシステム(高頻度バッチ処理、リアルタイム・テレメトリー処理など)におけるVB.NETの最適化手法に踏い込む。

1. アロケーションの抑制と `ReadOnlySpan` の活用 (.NET Core / .NET 8 以降)

モダンな .NET 環境では、文字列の切り出しやトリミング(`Trim()`)を行うだけで、マネージドヒープ上に新しいStringオブジェクトがアロケーション(生成)され、GCの負荷となる。
大量のテキストデータを処理する場合、`System.ReadOnlySpan(Of Char)` を利用したパース処理を検討すべきである。

.net
‘ .NET 6以降における高効率なスパンベースのパース
Imports System

Public Sub HighPerformanceParse(input As ReadOnlySpan(Of Char))
Dim parsedValue As Integer

‘ 余計なStringインスタンスを生成せずに直接パースを行う
If Integer.TryParse(input, parsedValue) Then
‘ 成功時の処理
End If
End Sub

2. P/Invoke(Windows API)連携時の型マッピングの厳密化

レガシーシステムからの移行期において、Win32 APIを叩くケースはまだ多い。その際、VB.NETの型選択ミスはメモリ破壊やクラッシュに直結する。

  • VB.NETの `Integer` は 32ビット (4バイト)。C++の `int` や `LONG` に対応。
  • VB.NETの `Long` は 64ビット (8バイト)。C++の `long long` または Windows APIの `INT_PTR` / `LONG_PTR` に対応(VB6の `Long` は32bitだったため、ここが最大の事故原因となる)。
  • Win32の `LONG`(32bit)を受ける場合は、必ず `CInt` ではなく `Integer` を使用し、API定義では `MarshalAs` 属性を適切に付与すること。

総括

レガシーな `CInt`、`CLng`、`Val` は、VBの歴史を語る上では不可欠だが、現代のエンタープライズ開発においては「技術的負債」の代名詞である。

  • `Val` は絶対に使わない。(データのサイレント破損を防ぐため)
  • `CInt` / `CLng` は、例外によるオーバーヘッドを理解した上で、制御された境界でのみ使用する。
  • 基本方針として `Integer.TryParse` / `Long.TryParse` を徹底し、例外駆動開発から脱却する。

このパラダイムシフトをチーム全体で徹底することこそが、保守性が高く、予測可能で、かつ極限まで最適化されたVB.NETシステムを構築するための唯一の道である。

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