|   | 
  | 
連載
改訂版 
プロフェッショナルVB.NETプログラミング
Chapter 03 ステートメントの変化 
株式会社ピーデー
川俣 晶 
2004/03/17 | 
 | 
 
 | 
 明瞭なDebugビルドとReleaseビルドの概念がないVB 6プログラマーには、DebugビルドとReleaseビルドで機能を異にするTraceクラスとDebugクラスの違いが、いまひとつピンとこないかもしれない。そこで、具体的な違いを実感する簡単なプログラムを記述してみた(リスト3-70)。
1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 
2:   Trace.WriteLine("Trace.WriteLine") 
3:   Debug.WriteLine("Debug.WriteLine") 
4: End Sub 
 | 
 
 
 | 
 
  
リスト3-70 TraceとDebugの違いを比較するためのプログラム
 | 
 このソースをDebugビルドして実行すると次のようになる。
1: Trace.WriteLine 
2: Debug.WriteLine 
 | 
 
 
 | 
 
  
リスト3-71 リスト3-70の実行結果(Debugビルドの場合)
 | 
 また、Releaseビルドして実行すると以下のようになる。
| 
 | 
 
  
リスト3-72 リスト3-70の実行結果(Releaseビルドの場合)
 | 
 見てのとおり、Trace.WriteLineは常時機能するが、Debug.WriteLineが機能するのはDebugビルドのときだけである。
 Debugビルドは、デバッガでソース・コードデバッグができるが、最適化機能が働かないため、動作が遅く、プログラムも大きくなる傾向にある。それに対して、Releaseビルドは最適化機能が働き、小さくすばやいコードが生成されるが、デバッグ情報がないため、ソース・コードを追いかけながらデバッグすることはできない。両者をうまく切り替えながら使っていこう。
 Assertメソッド(Debug.Assertメソッドの移行を参照)を使っていると、ときどき条件式が必要ない場合に遭遇する。つまり、ソース・コード上のある位置を実行してしまったというだけで、すでに間違っていることが明白な場合である。以下は、そういう状況を表現してみたプログラムである。
 1: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 
 2:   Dim i, sum As Integer 
 3:   For i = 0 To 99 
 4:     'sum = sum + i 
 5:     If sum > 100 Then 
 6:       Trace.WriteLine(sum) 
 7:       Exit Sub 
 8:     End If 
 9:   Next 
10:   Trace.Fail("内部エラー: ループが意図しない理由で終了しました") 
11: End Sub 
 | 
 
 
 | 
 
  
リスト3-73 条件式が不要なTrace.Failメソッドを使用したプログラム
 | 
 このソース・コードは本来、足し算を続け、3行目のForループが完結する前に終了することを意図している。しかし、4行目に書かれるべき足し算のコードを書き忘れているため、Forループから中途脱出せず、ループを終了してしまう。つまり、10行目が実行されることは、このプログラムの意図として必ず間違いということになる。そのような場合は、Assertメソッドではなく、10行目のようにFailメソッドを使うとよい。Failメソッドは条件に関係なく、常にプログラムの実行をそこで一時停止させる効能を持つ。
 これを実行すると以下のようになる。
 
  | 
 
| ●図3-74 リスト3-73の実行結果 | 
 見てのとおり、結果はAssertと同じだが、条件式を書く必要はなくなっている。
 本連載は、マルチスレッド機能については詳しく解説していない。これは、本連載が主に言語仕様の相違について解説を行うという趣旨であり、マルチスレッド機能は主に言語ではなくクラス・ライブラリを通じて提供されるという事情による。しかし、言語仕様の一部であるステートメントとして用意された機能もあるので、ここではそれを紹介する。それは、マルチスレッドの同期を取るためのSyncLockステートメントである。
 まず、マルチスレッドを安易に使ったがために、意図しない動作をするサンプル・プログラムを紹介する。これは、フォーム上にボタンが1個貼り付けられている状態でのサンプル・プログラムである(リスト3-75)。
 1: Public Class Form1 
 2:   Inherits System.Windows.Forms.Form 
 3:  
 4: …Windows フォーム デザイナで生成されたコード… 
 5:  
 6:   Private s As String = "" 
 7:  
 8:   Private Sub ThreadMain1() 
 9:     Dim i As Integer 
10:     For i = 0 To 99999 
11:       s = s + "A" 
12:     Next 
13:     Trace.WriteLine("ThreadMain1 done") 
14:   End Sub 
15:  
16:   Private Sub ThreadMain2() 
17:     Dim i As Integer 
18:     For i = 0 To 99999 
19:       s = s + "A" 
20:     Next 
21:     Trace.WriteLine("ThreadMain2 done") 
22:   End Sub 
23:  
24:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 
25:     Dim thread1 As New System.Threading.Thread(AddressOf ThreadMain1) 
26:     Dim thread2 As New System.Threading.Thread(AddressOf ThreadMain2) 
27:     thread1.Start() 
28:     thread2.Start() 
29:   End Sub 
30:  
31:   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 
32:     Trace.WriteLine(Len(s)) 
33:   End Sub 
34: End Class 
 | 
 
 
 | 
 
  
リスト3-75 マルチスレッドを安易に使ったため意図しない動作をするプログラム
 | 
 これを「ThreadMain1 done」と「ThreadMain2 done」というメッセージを確認してから、フォーム上のボタンを押すと、以下のような結果になった(この結果は条件により変化する可能性がある)。
| 
 | 
 
  
リスト3-76 リスト3-75の実行結果(例)
 | 
 このプログラムは、文字列に100000回1文字を追加するスレッドを2つ実行しているので、最終的な文字列の長さは200000文字になるはずである。しかし、結果がそうなっていないのは「s = s + "A"」という処理の途中でスレッドが切り替わる場合があるからだ。「s + "A"」を処理している途中でスレッドが切り替わり、変数sを書き換えてしまえば、正しい長さになるはずがない。
 この問題を解消するために、SyncLockステートメントを使用することができる。これを使ったサンプル・プログラムをリスト3-77に示す。
 1: Public Class Form1 
 2:   Inherits System.Windows.Forms.Form 
 3:  
 4: …Windows フォーム デザイナで生成されたコード… 
 5:  
 6:   Private s As String = "" 
 7:  
 8:   Private Sub ThreadMain1() 
 9:     Dim i As Integer 
10:     For i = 0 To 99999 
11:       SyncLock Me 
12:         s = s + "A" 
13:       End SyncLock 
14:     Next 
15:     Trace.WriteLine("ThreadMain1 done") 
16:   End Sub 
17:  
18:   Private Sub ThreadMain2() 
19:     Dim i As Integer 
20:     For i = 0 To 99999 
21:       SyncLock Me 
22:         s = s + "A" 
23:       End SyncLock 
24:     Next 
25:     Trace.WriteLine("ThreadMain2 done") 
26:   End Sub 
27:  
28:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 
29:     Dim thread1 As New System.Threading.Thread(AddressOf ThreadMain1) 
30:     Dim thread2 As New System.Threading.Thread(AddressOf ThreadMain2) 
31:     thread1.Start() 
32:     thread2.Start() 
33:   End Sub 
34:  
35:   Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 
36:     Trace.WriteLine(Len(s)) 
37:   End Sub 
38: End Class 
 | 
 
 
 | 
 
  
リスト3-77 SyncLockステートメントを使用して排他的に処理するようにしたプログラム
 | 
 これを同様の手順で実行すると結果は以下のようになる。
| 
 | 
 
  
リスト3-78 リスト3-77の実行結果
 | 
 意図したとおりの結果が得られたことが分かるだろう。ここで注目すべき点は、リスト3-77の11〜13行目と、21〜23行目のSyncLockステートメントである。SyncLockに続いて記述されたオブジェクト(ここではMe、つまりこのフォーム自身)に関するアクセスを調停してくれる。つまり、SyncLock〜End SyncLockの間を実行中のスレッドがあるとき、ほかのスレッドが同じオブジェクトに対するSyncLockステートメントに遭遇すると、他スレッドの処理が終わるまで待たせるのである。これにより「s = s + "A"」の処理の途中でほかのスレッドに切り替わり、変数sが書き変わることはなくなるのである。
 On…GoToとOn…GoSubは、大昔のBASICに存在していた制御構造の構文である。Visual Basicが生まれる前にすでに使われなくなっていた非常に古いもので、ある意味で、これがVB 6でもまだサポートされていたのは驚きである。しかし、とうとうVB.NETではサポートが行われないことになった。恐らく、Visual BasicになってからBASICを学んだプログラマーなら、存在すら知らないのが普通だと思う。しかし、まれに古いソース資産を継承しながら使われてきたプログラムの中に、この構文が使われている可能性がないともいえないので、説明しておこう。
 まずは、この構文を使用したVB 6サンプルから見ていただきたい(リスト3-79)。
 1: Private Sub DemonstrateOnGoto(ByVal n As Integer) 
 2:     On n + 1 GoTo a, b, c 
 3: a:  Debug.Print "goto-a" 
 4:     Exit Sub 
 5: b:  Debug.Print "goto-b" 
 6:     Exit Sub 
 7: c:  Debug.Print "goto-c" 
 8: End Sub 
 9:  
10: Private Sub DemonstrateOnGoSub(ByVal n As Integer) 
11:     On n + 1 GoSub d, e, f 
12:     Debug.Print "On..GoSub終わり" 
13:     Exit Sub 
14: d:  Debug.Print "gosub-d" 
15:     Return 
16: e:  Debug.Print "gosub-e" 
17:     Return 
18: f:  Debug.Print "gosub-f" 
19:     Return 
20: End Sub 
21:  
22: Private Sub Form_Load() 
23:   Dim n As Integer 
24:   n = 0 
25:   While n < 3 
26:     DemonstrateOnGoto n 
27:     DemonstrateOnGoSub n 
28:     n = n + 1 
29:   Wend 
30: End Sub 
 | 
 
 
 | 
 
  
リスト3-79 On…GoToとOn…GoSubを使用したプログラム
 | 
 これを実行すると以下のようになる。
1: goto-a 
2: gosub-d 
3: On..GoSub終わり 
4: goto-b 
5: gosub-e 
6: On..GoSub終わり 
7: goto-c 
8: gosub-f 
9: On..GoSub終わり 
 | 
 
 
 | 
 
  
リスト3-80 リスト3-79の実行結果
 | 
 ソース2行目がOn…GoToステートメントである。これはOnの次の数式を計算し、GoToのあとに列挙されたラベルから1つを選んでジャンプする(GoToを実行する)という機能を持つ。もし数式が1なら列挙されたラベルの1番目を、2なら2番目を、3なら3番目を呼ぶ。列挙されているリストに目的の番号に対応するものがなければ、次のステートメントを実行する。
 同様に、11行目がOn…GoSubステートメントである。これは、GoToではなくGoSubを行う。GoSubはサブルーチン呼び出しを行う機能で、Returnステートメントで呼び出し元に戻る。引数や戻り値のない関数呼び出しのようなもの、と理解すればよいだろう。
 では、VB.NETではどう書くことになるのか。一応書いてみたのがリスト3-81である。
 1: Private Sub DemonstrateSelectCase(ByVal n As Integer) 
 2:   Select Case n 
 3:     Case 0 
 4:       Trace.WriteLine("case 0") 
 5:     Case 1 
 6:       Trace.WriteLine("case 1") 
 7:     Case 2 
 8:       Trace.WriteLine("case 2") 
 9:   End Select 
10: End Sub 
11:  
12: Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load 
13:   Dim n As Integer 
14:   n = 0 
15:   While n < 3 
16:     DemonstrateSelectCase(n) 
17:     n = n + 1 
18:   End While 
19: End Sub 
 | 
 
 
 | 
 
  
リスト3-81 On…GoToの代わりにSelect Caseを使用したVB.NETのプログラム
 | 
 これを実行すると以下のようになる。
1: case 0 
2: case 1 
3: case 2 
 | 
 
 
 | 
 
  
リスト3-82 リスト3-81の実行結果
 | 
 On…GoToにせよ、On…GoSubにせよ、構造化構文ですらない構文なので、これに直接対応する機能は存在しない。しいて対応するものを挙げれば、Select Caseステートメントだろう。しかし、目的に応じて、いろいろな構文を使って置き換えることもできる。つまり、かなり大幅な書き換えが要求されると思って間違いないだろう。とはいえ、いまどき古いソースを掘り返しても、めったにお目にかかる構文ではないので、対策は目に入ってから考えても遅くないだろう。
 なお、もう1つ、このサンプル・ソースには注意点がある。この件については、WendをEnd Whileに書き換えるを参照していただきたい。
 
 
 
 『VB6プログラマーのための入門 Visual Basic .NET 独習講座』 
 
 本記事は、(株)技術評論社が発行する書籍『VB6 プログラマーのための 入門 Visual Basic .NET 独習講座』から許可を得て転載したものです。 
 
【本連載と書籍の関係について 】 
 この書籍は、本フォーラムで連載した「連載 プロフェッショナルVB.NETプログラミング」を大幅に加筆修正し、発行されたものです。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。 
 
 
→技術評論社の解説ページ 
 
ご注文はこちらから 
   |