連載

改訂版
プロフェッショナルVB.NETプログラミング

Chapter 03 ステートメントの変化

株式会社ピーデー 川俣 晶
2004/03/17


 本記事は、(株)技術評論社が発行する書籍『VB6プログラマーのための入門 Visual Basic .NET 独習講座』の一部分を許可を得て転載したものです。同書籍に関する詳しい情報については、本記事の最後に掲載しています。

 UTF-8によるファイル出力時の文字列サイズの変化

 文字を記憶するには何らかの容量を消費する。その容量は、文字の種類や、文字のエンコード方式によって変化するので一定ではない。特に、シフトJISに慣れたプログラマーは、シフトJIS以外の方式を使用した際に、文字を記憶するために必要な容量が変化することに注意を払う必要があるだろう。

 注意を払わなかった場合に、どのような弊害があるかを明らかにするために、1つのサンプル・ソースを用意した(リスト3-53)。

 1: Imports System.IO
 2:
 3: Public Class Form1
 4:   Inherits System.Windows.Forms.Form
 5:
 6: …Windows フォーム デザイナで生成されたコード…
 7:
 8:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 9:     Dim writer As StreamWriter, reader As StreamReader
10:     writer = New StreamWriter("c:\test.txt")
11:     writer.Write("亜意宇")
12:     Dim p As Long
13:     writer.Flush()
14:     p = writer.BaseStream.Position
15:     writer.Write("得御可")
16:     writer.Flush()
17:     writer.BaseStream.Position = p
18:     writer.Write("ΘΛ¶")
19:     writer.Close()
20:
21:     reader = New StreamReader("c:\test.txt")
22:     Do
23:       Dim s As String
24:       s = reader.ReadLine()
25:       If s = Nothing Then Exit Do
26:       Trace.WriteLine(s)
27:     Loop
28:     reader.Close()
29:     Trace.WriteLine(p)
30:   End Sub
31: End Class
リスト3-53 シフトJISとUTF-8の容量の違いを示すプログラム

 これを実行すると以下のようになる。

1: 亜意宇ΘΛ¶可
2: 9
リスト3-54 リスト3-53の実行結果

 このプログラムでは、文字列「得御可」を「ΘΛ¶」で上書きしている。にもかかわらず、「得御」は消えたのに「可」は残っていて、出力されてしまっている。

 これは、UTF-8で表現したとき、「得御可」はいずれも1文字3バイトで表現されるのに対して、「ΘΛ¶」は1文字2バイトで表現されているためだ。つまり、シフトJISで2バイトだった文字がすべて3バイトになるという前提を置くことはできない。したがって、このようにして、すでに書き込んだ文字を上書きする場合は、同じ文字数を書いても上書きになるとは限らない。十分に注意していただきたい(BaseStreamプロパティ、Positionプロパティ、Flushメソッドについては、次のテキスト・ファイルの書き込み位置を参照)。

 テキスト・ファイルの書き込み位置

 VB 6では、Seek関数により、書き込み中のファイルの先頭からの位置を知ることができる。また、Seekステートメントでこの位置を変更することができる。リスト3-55はそれを示すサンプル・プログラムである。

 1: Private Sub Form_Load()
 2:   Dim fileno As Integer
 3:   fileno = FreeFile
 4:
 5:   Open "c:\test.txt" For Output As #fileno
 6:   Print #fileno, "亜意宇";
 7:   Dim p As Long
 8:   p = Seek(1)
 9:   Print #fileno, "えお"
10:   Seek #1, p
11:   Print #fileno, "得御"
12:   Close #fileno
13:
14:   Dim s As String
15:   Open "c:\test.txt" For Input As #fileno
16:   While Not EOF(fileno)
17:     Line Input #fileno, s
18:     Debug.Print s
19:   Wend
20:   Close #fileno
21:   Debug.Print p
22: End Sub
リスト3-55 Seek関数とSeekステートメントを使用したプログラム

 これを実行すると以下のようになる。

1: 亜意宇得御
2:  7
リスト3-56 リスト3-55の実行結果

 これと同等の機能を持つVB.NETのプログラムは以下のようになる。

 1: Public Class Form1
 2:   Inherits System.Windows.Forms.Form
 3:
 4: …Windows フォーム デザイナで生成されたコード…
 5:
 6:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 7:     Dim fileno As Integer
 8:     fileno = FreeFile()
 9:
10:     FileOpen(fileno, "c:\test.txt", OpenMode.Output)
11:     Print(fileno, "亜意宇")
12:     Dim p As Long
13:     p = Seek(1)
14:     Print(fileno, "えお")
15:     Seek(1, p)
16:     PrintLine(fileno, "得御")
17:     FileClose(fileno)
18:
19:     Dim s As String
20:     FileOpen(fileno, "c:\test.txt", OpenMode.Input)
21:     While Not EOF(fileno)
22:       s = LineInput(fileno)
23:       Trace.WriteLine(s)
24:     End While
25:     FileClose(fileno)
26:     Trace.WriteLine(p)
27:   End Sub
28: End Class
リスト3-57 リスト3-55をVB.NETで書き換えたプログラム

 これを実行すると以下のようになる。

1: 亜意宇得御
2: 7
リスト3-58 リスト3-57の実行結果

 見てのとおり、ほとんど同じ機能を持つSeek関数が提供されているので、VB.NETへの移行は難しくない。だが、クラス・ライブラリを使用するように書き換える場合はどうだろうか? リスト3-59は、同等の機能をクラス・ライブラリのStreamWriterクラスを使用して記述した例である。

 1: Imports System.IO
 2:
 3: Public Class Form1
 4:   Inherits System.Windows.Forms.Form
 5:
 6: …Windows フォーム デザイナで生成されたコード…
 7:
 8:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
 9:     Dim writer As StreamWriter, reader As StreamReader
10:     writer = New StreamWriter("c:\test.txt")
11:     writer.Write("亜意宇")
12:     Dim p As Long
13:     writer.Flush()
14:     p = writer.BaseStream.Position
15:     writer.Write("えお")
16:     writer.Flush()
17:     writer.BaseStream.Position = p
18:     writer.WriteLine("得御")
19:     writer.Close()
20:
21:     reader = New StreamReader("c:\test.txt")
22:     Do
23:       Dim s As String
24:       s = reader.ReadLine()
25:       If s = Nothing Then Exit Do
26:       Trace.WriteLine(s)
27:     Loop
28:     reader.Close()
29:     Trace.WriteLine(p)
30:   End Sub
31: End Class
リスト3-59 リスト3-57を、クラス・ライブラリを使用して書き換えたプログラム

 これを実行すると次のようになる。

1: 亜意宇得御
2: 9
リスト3-60 リスト3-59の実行結果

 このプログラムにはいくつか、初めてお目に掛かるキーワードがある。まず、Flushメソッドはまだ書き込まれていないデータを書き込む働きがある。パフォーマンスを向上させるために、書き込みデータのバッファリングが行われているため、確実に書き込むためには、このメソッドを呼び出す必要がある。ただし、ファイルを閉じるときには確実に書き込まれるため、通常は間違いなく閉じるようにすれば問題ない。問題が起きるのは、閉じる前に書き込み位置を変更したりする場合である。

 次に、BaseStreamというプロパティは、実際に入出力を行うオブジェクトを参照する機能を持つ。そして、Positionプロパティは、書き込み位置を設定/取得できるプロパティである。つまり、PositionプロパティはVBのSeek関数、Seekステートメントとほぼ同じ役割を持つものである。バッファリングはStreamWriterクラスで、位置の管理はBaseStreamプロパティで参照されるクラスで行われるため、Positionプロパティを読み書きする場合は、その前にFlushメソッドを呼んでバッファを書き込んでおく必要がある。

 さて、出力されている最後の数値を見ていただきたい。“7”だったはずの数値が、クラス・ライブラリを使うと“9”に化けている。値が違うことには2つの理由がある。1つは、1文字の占めるバイト数がシフトJISとUTF-8では違うこと。漢字やかなは3バイトを要するため、シフトJISよりも多くの容量を占有する。詳しくは、UTF-8によるファイル出力時の文字列サイズの変化を参照していただきたい。もう1つの理由は、位置を数える起点となる位置が違うためだ。VBではファイルの先頭のバイトは1とするが、クラス・ライブラリでは0としている。この違いが、値が1つずれる原因となっている。

 さまざまなファイル入出力手段の中でどれを選ぶべきか

 VB.NETには、ファイルを入出力する方法がいくつかある。その中で、VB 6との互換性を意識したものとして、VB専用ファイル入出力関数(FileOpen関数など)と、ファイルシステム・オブジェクトが使用できる。VB.NETの時代には、この2つのどちらを使うのが好ましいだろうか? あるいは、過去のソースの互換などを意識せず、まったく新規にソースを記述できるとしたら、どちらを選ぶべきだろうか?

 まず、VB専用ファイル入出力関数は、過去のVB 6からの移行を容易にするように、書式は違っても似通った機能が提供されていることが特徴といえる。例えば、開いたファイルを区別するために整数のファイル番号を使うことは、明らかに伝統的なBASICとの互換性を重視していることをうかがわせる。一方、この方法は使い勝手がよいとはいえない。まったく無関係の整数をファイル番号として使ってしまっても、構文エラーにはならず、実行して初めてエラーメッセージを見ることになる。あるいは、ほかの開いているファイル番号と偶然重なると、エラーメッセージも出ないまま、不正な結果を目にする羽目になる。そのような特徴から考えれば、これらは過去の資産を活かすために使われるべきもので、ゼロから新規に開発するプログラム向きとはいいがたい

 では、ファイルシステム・オブジェクトはどうか。こちらのほうは、開いたファイルを番号ではなくオブジェクトとして扱うので、関係ない整数を誤って代入して混乱するような事態は未然に防ぐことができる。例えば、整数には入出力メソッドはないので、間違った整数を入れてしまっても、間違いなくエラーになる。しかし、COMオブジェクトの世界は、.NET Frameworkの世界の外側に存在するものであり、VB.NETで開発したプログラムを実行できる環境のすべてが、ファイルシステム・オブジェクトをサポートしているという保証はない。いまのところ、Windowsには標準でファイルシステム・オブジェクトが含まれているが、.NET FrameworkがWindowsのAPI(Application Programming Interface。応用ソフトがシステムに機能要求を伝えるインターフェース)であるWin32 APIの後継に位置づけられている以上、Windowsではない環境で動作する可能性もないとはいえないのである。

 また、効率の点からもCOMオブジェクトへのアクセスは好ましいものではない。CreateObjectを使って実行時に参照を処理する方法は効率が悪いし、かといって、参照を使ってラッパークラス経由でアクセスするのも非効率である。

 これらのことから考えれば、VB専用ファイル入出力関数も、ファイルシステム・オブジェクトも、どちらを選んでも一長一短ということになる。

 では、どう対処すれば、より良い解決方法といえるのか。それは、.NET Frameworkのクラス・ライブラリの活用である。VB 6のプログラマーなら、Win32 APIを呼び出して活用した人も多いと思うが、.NET Frameworkのクラス・ライブラリを呼び出すということは、それと似た行為であるといえる。しかし、VB 6からWin32 APIを呼び出す場合には特別なDeclare宣言をいちいち記述しなければならなかったが、VB.NETから.NET Frameworkのクラス・ライブラリを呼び出す場合は、そのような宣言は必要ない。これについては、.NET Frameworkクラス・ライブラリを用いたテキスト・ファイルの入出力などを参照されたい。特別な宣言が不用ということは、これが本来使われるべき基本的な機能であることを示している。安心して、これを使ってよい。


 INDEX
  [連載] 改訂版 プロフェッショナルVB.NETプログラミング
  Chapter 03 ステートメントの変化
    1.WendをEnd Whileに書き換える/プロシージャ脱出とReturnステートメント/関数の戻り値とReturnステートメント
    2.読み取り専用変数/複合代入ステートメント/ Staticの付いたプロシージャ/ランダム・ファイルとVBFixedString属性
    3.ステートメントを用いたテキスト・ファイルの入出力/ファイルシステム・オブジェクトを用いたテキスト・ファイルの入出力
    4.参照を使ったファイルシステム・オブジェクトの利用/ .NET Frameworkクラス・ライブラリを用いたテキスト・ファイルの入出力
    5.エンコーディング名を指定したテキスト・ファイルの入出力/シフトJISとUTF-8の相違
  6.UTF-8によるファイル出力時の文字列サイズの変化/テキスト・ファイルの書き込み位置/さまざまなファイル入出力手段の中でどれを選ぶべきか
    7.Debug.Assertメソッドの移行
    8.TraceクラスとDebugクラスの相違/条件式抜きで必ず停止させるFailメソッド/マルチスレッドを同期するSyncLockステートメント/古い制御構造 On…GoToとOn…GoSub
 
「改訂版 プロフェッショナルVB.NETプログラミング 」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)
- PR -

注目のテーマ

業務アプリInsider 記事ランキング

本日 月間
ソリューションFLASH