連載

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

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

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


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

 TraceクラスとDebugクラスの相違

 明瞭な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ビルドして実行すると以下のようになる。

1: Trace.WriteLine
リスト3-72 リスト3-70の実行結果(Releaseビルドの場合)

 見てのとおり、Trace.WriteLineは常時機能するが、Debug.WriteLineが機能するのはDebugビルドのときだけである。

 Debugビルドは、デバッガでソース・コードデバッグができるが、最適化機能が働かないため、動作が遅く、プログラムも大きくなる傾向にある。それに対して、Releaseビルドは最適化機能が働き、小さくすばやいコードが生成されるが、デバッグ情報がないため、ソース・コードを追いかけながらデバッグすることはできない。両者をうまく切り替えながら使っていこう。

 条件式抜きで必ず停止させるFailメソッド

 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ステートメント

 本連載は、マルチスレッド機能については詳しく解説していない。これは、本連載が主に言語仕様の相違について解説を行うという趣旨であり、マルチスレッド機能は主に言語ではなくクラス・ライブラリを通じて提供されるという事情による。しかし、言語仕様の一部であるステートメントとして用意された機能もあるので、ここではそれを紹介する。それは、マルチスレッドの同期を取るための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」というメッセージを確認してから、フォーム上のボタンを押すと、以下のような結果になった(この結果は条件により変化する可能性がある)。

1: 109646
リスト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ステートメントを使用して排他的に処理するようにしたプログラム

 これを同様の手順で実行すると結果は以下のようになる。

1: 200000
リスト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

 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に書き換えるを参照していただきたい。End of Article

VB6プログラマーのための入門 Visual Basic .NET 独習講座』

 本記事は、(株)技術評論社が発行する書籍『VB6 プログラマーのための 入門 Visual Basic .NET 独習講座』から
許可を得て転載したものです。

【本連載と書籍の関係について 】
 この書籍は、本フォーラムで連載した「連載 プロフェッショナルVB.NETプログラミング」を大幅に加筆修正し、発行されたものです。技術評論社、および著者である川俣晶氏のご好意により、書籍の内容を本フォーラムの連載記事として掲載させていただけることになりました。

技術評論社の解説ページ

ご注文はこちらから
 

 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