|   | 
  | 
連載
改訂版 
プロフェッショナルVB.NETプログラミング
Chapter 03 ステートメントの変化 
株式会社ピーデー
川俣 晶 
2004/03/17 | 
 | 
 
 | 
 文字を記憶するには何らかの容量を消費する。その容量は、文字の種類や、文字のエンコード方式によって変化するので一定ではない。特に、シフト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の容量の違いを示すプログラム
 | 
 これを実行すると以下のようになる。
| 
 | 
 
  
リスト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ステートメントを使用したプログラム
 | 
 これを実行すると以下のようになる。
| 
 | 
 
  
リスト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で書き換えたプログラム
 | 
 これを実行すると以下のようになる。
| 
 | 
 
  
リスト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を、クラス・ライブラリを使用して書き換えたプログラム
 | 
 これを実行すると次のようになる。
| 
 | 
 
  
リスト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クラス・ライブラリを用いたテキスト・ファイルの入出力などを参照されたい。特別な宣言が不用ということは、これが本来使われるべき基本的な機能であることを示している。安心して、これを使ってよい。