【テクニカル・上級編】VB.NETにおけるChar型とString型の境界線:文字コード(Unicode/Shift_JIS)の差異で文字化けを起こさない文字列処理 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

VB.NETにおけるChar型とString型の境界線:文字コードの罠と極限の文字列処理

レガシーな基幹システム、C/S型のVB6製アプリケーション、そして現代の.NET Core / .NET 8に至るまで、日本の企業システムは常に「文字化け」という名の亡霊と戦い続けてきた。

特に、Visual Basic (VB.NET) における `Char` 型と `String` 型の境界線、そしてその背後にある Unicode(UTF-16)と Shift_JIS(CP932)の厳然たる差異を理解していないプログラマが多すぎる。シングルクォート “ ‘ “ とダブルクォート “ ” “ の違いすら曖昧なままコードを書き、外部システムやレガシーなテキストファイル連携でデータ破損を引き起こす現場を、私は数え切れないほど見てきた。

本稿では、VB.NETの型システムの本質を暴き、文字コードの不一致を完全制圧するための実践的な知見を、シニアエンジニアの視点から叩き込む。

1. 型の基礎:Char型とString型、そしてリテラスの呪縛

まず、言語仕様レベルでの基本を確認する。ここを誤る者には、これ以降の高度なAPI連携を語る資格はない。

  • `Char` 型: 16ビット(2バイト)の符号なし整数であり、UTF-16のコードユニット1文字を保持する。.NET内部では `System.Char`。
  • `String` 型: 不変(Immutability)な参照型であり、ゼロ個以上の `Char` のシーケンス。.NET内部では `System.String`。

リテラスの罠(Option Strict の重要性)

VB.NETにおいて、シングルクォート “ ‘ “ は `Char` リテラルを生成し、ダブルクォート “ ” “ は `String` リテラルを生成する。ただし、`Option Strict Off`(デフォルト)の環境では、これが隠れた型変換のコストとバグの温床となる。

‘ 【悪夢の始まり】Option Strict Off の世界
Dim c As Char = “A” ‘ StringからCharへの暗黙の型変換が発生(パフォーマンス劣化と予期せぬ挙動の原因)

真のプログラマであれば、プロジェクトの先頭には必ず `Option Strict On` を宣言し、型を厳格に統制すべきである。

Option Strict On
Option Explicit On

Module CharStringBoundary
Sub Main()
‘ 正しいCharリテラル(C型文字修飾子、またはExplicitなキャスト)
Dim validChar1 As Char = “A”C
Dim validString As String = “A” ‘ ダブルクォートはString

Console.WriteLine($”Char size: {sizeof(Char)} bytes”) ‘ 2バイト
End Sub
End Module

2. Unicode (UTF-16) と Shift_JIS (CP932) の深い溝

.NETの `String` および `Char` は、内部表現として常に UTF-16 を採用している。しかし、日本のレガシーシステム、汎用機、CSV出力、古いDBとの連携ではいまだに Shift_JIS(正確にはMicrosoft拡張を含む CP932) が要求される。

ここに大きな罠がある。
「.NETの文字数(`String.Length`)と、Shift_JISに変換した際のバイト数は完全に一致しない」 という事実である。

サロゲートペアと文字幅の破壊

UTF-16では、基本多言語面(BMP)外の文字(環境依存文字、絵文字、一部の人名漢字など)を表現するために、2つの `Char`(サロゲートペア、計4バイト)を使用する。
これを `System.Text.Encoding.ShiftJIS`(実際には `Encoding.GetEncoding(932)`)でエンコードしようとすると、Shift_JISに存在しない文字は `?`(疑問符、置換文字)に化けるか、例外をスローする。

レガシーな固定長ファイル(DATファイル)を読み書きする際、このバイト数と文字数のズレを計算に入れないと、レコードのオフセットが完全に破壊される。

3. 実践:System.Text.Encoding を駆使した安全なファイル入出力

外部のレガシーシステムと安全に連携するためには、`StreamReader` および `StreamWriter` を生成する際に、明示的に `Encoding` を指定しなければならない。さらに、不正な文字に遭遇した際の挙動(DecoderFallback / EncoderFallback)を制御することが極限の品質を生む。

以下のコードは、CP932(Shift_JIS)のレガシーファイルを、文字化けやデータ損失を起こさずに安全に読み込むための実践的なスニペットである。

Imports System.IO
Imports System.Text

Public Class LegacyTextProcessor

”’

”’ CP932(Shift_JIS)のレガシーファイルを安全に読み込み、UTF-16のStringとして処理する
”’

”’ ファイルパス ”’ 読み込んだ文字列のリスト
Public Shared Function ReadLegacyFile(filePath As String) As List(Of String)
Dim results As New List(Of String)()

‘ CP932のEncodingを取得(OEM/ANSI環境の差異を吸収)
‘ 例外を投げるフォールバックを設定し、文字化けをサイレントにスルーさせない
Dim sjisEncoding As Encoding = Encoding.GetEncoding(932,
New DecoderExceptionFallback(),
New EncoderExceptionFallback())

‘ Usingブロックによる確実なリソース解放(IDisposableの遵守)
Using fs As New FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read),
sr As New StreamReader(fs, sjisEncoding)

Dim line As String
While True
line = sr.ReadLine()
If line Is Nothing Then Exit While

‘ 読み込んだ文字列に対するビジネスロジック
results.Add(line)
End While
End Using

Return results
End Function

”’

”’ 固定長レガシーフォーマット向けに、指定バイト数で厳密にパディング・切り詰めを行う
”’

Public Shared Function FormatFixedLengthField(value As String, targetByteLength As Integer) As Byte()
Dim sjis As Encoding = Encoding.GetEncoding(932)

‘ 文字列をCP932のバイト配列に変換
Dim bytes As Byte() = sjis.GetBytes(value)

Dim resultBytes(targetByteLength – 1) As Byte

If bytes.Length >= targetByteLength Then
‘ 指定バイト数を超える場合は切り詰め(マルチバイト文字の途中で切断しない配慮が必要な場合もある)
Array.Copy(bytes, resultBytes, targetByteLength)
Else
‘ 足りない部分は半角スペース(0x20)で右パディング
Array.Copy(bytes, resultBytes, bytes.Length)
For i As Integer = bytes.Length To targetByteLength – 1
resultBytes(i) = &H20C ‘ 0x20スペース
Next
End If

Return resultBytes
End Function

End Class

4. パフォーマンスとメモリ最適化:Stringの連結地獄からの脱却

VB.NET初学者が陥る最大のパフォーマンスアンチパターンが、ループ内での `String` 型の連結(`+` または `&` 演算子)である。

‘ 【厳禁】メモリの無駄遣いとGC(ガベージコレクション)を圧迫する最悪のコード
Dim text As String = “”
For i As Integer = 0 To 10000
text &= i.ToString() ‘ ループのたびに新しいStringオブジェクトがヒープに生成され、古いオブジェクトがゴミになる
Next

`String` は不変(Immutability)であるため、文字を結合するたびに新しいメモリ領域が確保され、メモリ断片化(Fragmentation)とGen 0/Gen 1 GCの頻発を引き起こす。

解決策:`System.Text.StringBuilder` の活用

大量の文字列操作や、レガシーな巨大テキストの生成には、必ず `StringBuilder` を使用せよ。

Imports System.Text

Public Sub BuildLargeTextOptimized()
Dim sb As New StringBuilder(1024) ‘ 初期容量を予測して割り当て、リアロケーションを抑制

For i As Integer = 0 To 10000
sb.Append(i.ToString())
sb.Append(ControlChars.CrLf)
Next

Dim finalResult As String = sb.ToString()
End Sub

5. Windows API連携とアンマネージメモリの境界線

レガシーシステムの統合において、VB.NETからWindows API(User32.dllやKernel32.dllなど)を叩く必要があるケースは依然として存在する。ここで `String` をネイティブコード(C言語の `char` や `wchar_t`)に渡す際、マーシャリング(Marshaling)の挙動を理解していないと、メモリリークやアクセス違反(Access Violation)の引き金となる。

VB.NETでは、`Declare` ステートメントではなく、現代的な `DllImport` 属性と `MarshalAs` を使用すべきである。

Imports System.Runtime.InteropServices

Public Class WinApiBridge

‘ ANSI版のWindows APIを呼び出す例(Shift_JIS文字列を渡す場合)

Private Shared Function MessageBoxA(
ByVal hWnd As IntPtr,
ByVal lpText As String,
ByVal lpCaption As String,
ByVal uType As UInteger) As Integer
End Function

Public Shared Sub ShowLegacyMessage(message As String)
‘ .NETのUTF-16文字列が、マーシャラーによって自動的にANSI(CP932)のバイト列に変換されてAPIに渡される
MessageBoxA(IntPtr.Zero, message, “Legacy API Integration”, 0)
End Sub

End Class

結び:コードの背後にある「バイト」を見よ

画面上の美しい文字の裏側には、必ずそれを表現する無機質な「バイト列」が存在する。
VB.NETの抽象化された世界に安住し、`Char` と `String`、そして `Encoding` の仕組みを軽視する者は、いつの日か本番環境での致命的な文字化けという名の牙に噛み付かれることになるだろう。

真の業務自動化エンジニア・チーフアーキテクトたる者、メモリの割り当てから文字コードの変換レイヤーに至るまで、すべての挙動を完全に掌握していなければならない。
あなたの書く一行のコードが、システムの命運を握っているという気概を持って、明日からの開発に臨んでほしい。

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