Visual Basic 初級講座 [改訂版] |
Visual Basic 中学校 > 初級講座[改訂版] >
2020/4/18
この記事が対象とする製品・バージョン
Visual Basic 2019 | ◎ | 対象です。 | |
Visual Basic 2017 | ◎ | 対象です。 | |
Visual Basic 2015 | ◎ | 対象です。 | |
Visual Basic 2013 | ◎ | 対象です。 | |
Visual Basic 2012 | ◎ | 対象です。 | |
Visual Basic 2010 | ◎ | 対象です。 | |
Visual Basic 2008 | ◎ | 対象です。 | |
Visual Basic 2005 | ◎ | 対象です。 | |
Visual Basic.NET 2003 | △ | 対象外ですがほとんどの説明があてはまるので参考になります。 | |
Visual Basic.NET (2002) | △ | 対象外ですがほとんどの説明があてはまるので参考になります。 | |
Visual Basic 6.0 | × | 対象外です。 |
目次
変数を宣言しても使える場所と使えない場所があります。どこでどのように変数を宣言するかで使える場所が変わります。変数が使える場所のことを変数の「スコープ」または「適用範囲」(てきようはんい)と呼びます。
変数のスコープには次の3種類があります。
まだ初級講座で登場していない言葉もありますが、ひとまず一覧を紹介します。
ブロックスコープ
一番狭いスコープです。このスコープの変数はブロック内でのみ使用できるスコープです。
プロシージャスコープ
同じプロシージャ内ならどこでも有効なスコープです。このスコープの変数はプロシージャ内でのみ使用できます。
クラスレベルのスコープ
クラス・モジュール内で有効なスコープです。このスコープの変数は同じクラス・モジュールで有効です。
マイクロソフトのドキュメントでは「モジュールのスコープ」と表現されていますが、「クラスレベル」という言葉の方が一般的に使われているように思うのでここでは言葉を変えました。
参考
Visual Basic におけるスコープ
このあと、1つずつ説明しますが、先に概要がわかるプログラムを紹介します。
このプログラムにはまだ初級講座では登場していないIf ~ End If の構文が登場しています。VBにはこのように ○○ ~ End ○○ という構造が多く登場します。Class ~ End Class、Sub ~ End Subなどは今まで意味を解説はしていませんが、サンプルにも何度も登場していたと思います。
Public
Class Form1 Dim x As Integer '←この変数 x は クラスレベルのスコープ Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim y As Integer '←この変数 y はプロシージャスコープ If x = y Then Dim z As Integer '←この変数 z はブロックスコープ z = x + y End If End Sub End Class |
変数のスコープの問題とは要するにこの○○ ~ End ○○ の間で宣言した変数は、その○○ ~ End ○○の中でしか使用できませんよということなのです。
たとえば、変数 z は If ~ End If の間で宣言されているので、このブロックの外側では使用できません。
今回は説明の都合上初級講座でまだ説明していないキーワードが多数登場しますが、個々の機能については別途説明していきますので知らない機能については心配しないでください。私が主に今回説明したいのはブロックが存在する場合の変数の使い方であり、個々のブロックの機能ではないのです。
○○ ~ End ○○ という構造のほかに、For ~ Nextなどブロックを作る構造はいくつかあります。
VBでブロックを作る代表的なキーワードをまとめると次のようになります。
この表では参考にブロックの機能も書いておきますが、どの機能も初級講座の現時点では説明していないで、具体的なことは今後説明していきます。
ブロックを作るキーワード | 機能 |
---|---|
If ~ End If | 条件に合致する場合に処理を記述します。(間がElse、ElseIfで分割されている場合もあります。) |
Try ~ End Try | エラーが発生した場合の処理を記述します。(間がCatch、Finallyで分割されている場合もあります。) |
While ~ End While | 条件が成立するまで繰り返して実行する処理を記述します。 |
Using ~ End Using | リソースが確実に開放される処理を記述します。 |
SyncLock ~ End SyncLock | マルチスレッドで実行できない処理を記述します。 |
With ~ End With | 暗黙にオブジェクトを参照する処理を記述します。 |
Do ~ Loop | 条件に合う場合繰り返して実行する処理を記述します。 |
For ~ Next | 決められた回数実行する処理を記述します。 |
Select ~ End Select | 条件に応じて分岐する処理を記述します。間は Case で分割されます。 |
知らないキーワードが登場してもVisual Studioではブロックは自動的にインデントされるので、ブロックであることを知ることはできます。
ブロックは入れ子にすることもできます。たとえば、If ~ End If の間に、Do ~ Loop を記述することもできます。
こららのブロック内で宣言した変数はそのブロック内でのみ使用できるブロックスコープになります。複数のブロックをまたいで同じ変数を使いたい場合は、ブロックの外で変数を宣言し、変数のスコープを1段階上にする必要があります。
下記にスコープを間違ってエラーになる例を示します。
この例には3つのブロックが登場し、変数 userName は1つの目のブロック内で宣言されています。それを3つ目のブロック内で使用しようとしている出エラーです。
Visual Studioはこの箇所の userName に赤い波線を表示して、次のようなエラーを出力します。
'userName' は宣言されていません。アクセスできない保護レベルになっています。
解決方法の1つはuserNameを上位で宣言することです。たとえば、次のようにします。
Private Sub
Button1_Click(sender As Object, e
As EventArgs)
Handles Button1.Click Dim userName As String Try userName = InputBox("お名前を教えてください。") Catch ex As Exception 'ここに書いた処理はエラー発生時にだけ実行されます。 MsgBox("エラー発生 " & ex.Message) End Try If Len(userName) > 0 Then MsgBox("こんにちは、" & userName & "さん。") End If End Sub |
変数userNameを宣言する場所を変えただけですが、この位置だとすべてのブロックの外側になり、次で説明するプロシージャスコープになります。(要するにSub ~ End Sub内で有効になります。)
そのため、複数のブロックをまたいで使用できるようになります。
この例のように修正すると確かに赤い波線はなくなって実行できるようになります。でも、Visual
StudioはLen(userName)のuserNameに緑の波線を表示して、何かを警告します。[表示]メニューのエラー一覧で確認してみると次のように表示されます。
変数 'userName' は、値が割り当てられる前に使用されています。Null 参照の例外が実行時に発生する可能性があります。 これは変数userNameの初期化がされない状況で Len(userName)が実行される可能性があることを警告するもので、初級講座 第2回 変数と定数 で説明します。一見、userName = InputBox(・・・)の行で値が代入されるので、Len(userName)の時点では初期化が完了しているはずのように見えますが、このプログラムではTryを使っているため、InputBoxの行でエラーが発生すると、初期化が完了しないままLen(userName)が実行される可能性があるのです。Visual Studioはそれを警告しています。 |
発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。
Len関数は初期化されていない変数でも正常に処理してくれることを『私は知っている』ので、問題はないのですが、緑の波線がなんか嫌な感じがするのでVisual
Studioをおとなしくさせるためには、Dim userName As Stringの行でDim userName As String =
Nothing と書いて無意味な初期化を行います。初期化していない文字列型の値はNothingなので、明示的に = Nothing
を書く必要は機能上はないのですが、これを記述することでVisual
Studioは初期化はされているものとみなしLen(userName)を警告しなくなります。 |
もう1つの解決方法はuserNameを必要とする処理を同じブロック内に集めることです。
Private Sub
Button1_Click(sender As Object, e
As EventArgs)
Handles Button1.Click Try Dim userName As String userName = InputBox("お名前を教えてください。") If Len(userName) > 0 Then MsgBox("こんにちは、" & userName & "さん。") End If Catch ex As Exception 'ここに書いた処理はエラー発生時にだけ実行されます。 MsgBox("エラー発生 " & ex.Message) End Try End Sub |
これで変数 userName を使う処理はすべて Try ~ Catch の中に集められました。userNameはこのブロック内で有効なので正常に動作します。
このプログラムではブロックの中にブロックが入っているブロックの入れ子状態になっています。この場合親ブロックで宣言された変数は子ブロックでも有効なので問題ありません。
ところで、C#では { } を使って特に機能がないブロックを作ることができて、変数のスコープを限定したい目的だけで使うことができます。VBにはこのような機能がないスコープを作る機能はありません。無害なブロックが欲しい場合はWith Me ~ End Withが一番無害です(すべての場所で使用できるわけではありませんが)。もともとWithはプログラムを入力する手間を軽減するだけの存在で、機能は特にありません。下記の例でWith ~ End Withを使って2つのブロックを作り出していますが、ブロック自体には意味はなく、中の変数のスコープを分けているだけです。
With Me Dim count As Integer count = count + 111 MsgBox(count) '111と表示される。 End With With Me Dim count As Integer '新しいスコープの変数になる。 count = count + 222 MsgBox(count) '222と表示される。 End With |
ただ、意味のないWithは混乱しそうなので、私はこのように使うことを特に推奨しているわけではありませんし、このようなことがしたいという欲求を感じたこともほとんどありません。C# 等他の言語で { } を多用していて VBでも同じようなことがしたいというフラストレーションを抱えている方向けの情報だと思ってください。
プロシージャとはブロック構造の中で大きな構造を指す言葉で、Sub ~ End Sub、Function ~ End Functionが代表です。
言葉が違うだけで構造上はブロックであり、スコープの考え方も同じです。
WindowsフォームアプリケーションではButtonのクリックイベント処理を記述するとButton1_Clickのような名前がついたSub ~ End Subが自動的に作成されます。これはプロシージャです。
このスコープの変数は「ローカル変数」とも呼ばれます。ローカル変数という言葉はプログラムの話や説明をする中でしばしば出てきます。文脈によってはブロックスコープの変数のことも「ローカル変数」と呼ぶ場合もあります。この一連の連鎖危機時ではブロックスコープの変数とプロシージャスコープの変数の両方をローカル変数と呼ぶことにします。
例としてWindowsフォームアプリケーションでButtonを2つ配置してクリックイベントを記述した次のプログラムを考えて見ます。
このプログラムはエラーです。
Public
Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim userName As String userName = InputBox("お名前を教えてください。") End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click 'ここでエラーです。 MsgBox("こんにちは、" & userName & "さん。") End Sub End Class |
このプログラムでは、1つ目のプロシージャ(Button1_Clickプロシージャ)で宣言しているローカル変数 userName を、別のプロシージャ(Button2_Clickプロシージャ)で使用しようとしています。
プロシージャ内で宣言した変数はそのプロシージャ内でのみで有効なため別のプロシージャで使用することはできません。
では、次のようにButton2_ClickプロシージャでもuserName変数を宣言するとどうでしょう?この例でもまだ問題があります。
Public
Class Form1 Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim userName As String userName = InputBox("お名前を教えてください。") End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click Dim userName As String MsgBox("こんにちは、" & userName & "さん。") End Sub End Class |
エラーにはなりませんが、Button1を先にクリックして名前を入力してからButton2をクリックしても名前部分は何も表示されないはずです。
変数の名前が同じでも宣言されているプロシージャが異なると別の変数だからです。
このようにプロシージャをまたいで同じ変数を使用したい場合は、変数をもう1段階上のスコープであるクラスレベルのスコープにすることになります。
クラスとは基本的にはClass ~ End Classで囲まれた範囲です。
※クラスの定義は複数のClass ~ End Classに分割されている場合もありますが、この分割には機能上の意味はなく、クラスレベルの変数は分割とは無関係にクラス全体で有効になります。
ここで説明する内容はモジュールにも当てはまります。モジュールはModule ~ End Module で囲まれた範囲を指します。コンソールアプリケーションでは初期状態ではモジュールが作成されます。
さて、 次のように変数userNameを宣言する場所を変えると、このクラス(Form1)内のすべての場所(すべてのプロシージャ・すべてのブロック)でこの変数が使用できるようになります。
Public
Class Form1 Dim userName As String Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click userName = InputBox("お名前を教えてください。") End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click MsgBox("こんにちは、" & userName & "さん。") End Sub End Class |
クラスレベルで宣言された変数は、もはや通常の変数ではなく、クラスのメンバーの一員で「フィールド」になります。つまり、クラスレベルのスコープを持つ変数とはフィールドと同じ意味です。
クラスのメンバーは他のクラスからどのように見えるか Private, Protected, Friend, Publicというアクセス指定子と呼ばれるキーワードを使って定義することができます。アクセス指定子を省略したときどのような状態になるのかわかりにくいで、アクセス指定子を必ずつけることを推奨します。
発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 詳しい説明は自分でクラスを作成する方法を解説するときに行いますが、Privateがついているものは他のクラスからはアクセスしないもの、Publicがついているものは他のクラスからもアクセスするものです。ProtectedとFriendはアクセスできるクラスに条件をつけるものです。(Protectedは派生クラスからはアクセスする。Friendは同じプロジェクト内のクラスからはアクセスする。) |
そのため初級講座の現段階では特に困らないのですが、基本を勉強している段階からクラスレベルの変数はDimではなくPrivate Dim userName As StringやPublic Dim userName As Stringのようにアクセス指定子をつけるようにしましょう。
実際書いてみるとちょっと面白いことがおきます。Dimにアクセス指定子をつけるとVisual Studioは「Dim」の方を省いて省略形にしてしまいます。
たとえば、Private Dim userName As String と書くと、Visual Studioは自動的にDimを省いて、Private userName As String にしてしまいます。なので慣れてくるとはじめからDimを省略して Private userName As Stringと書くことになるでしょう。
結果、まとめると私の非推奨・推奨は次のような記述です。現段階では他のクラスから変数を使用することはないのでアクセス指定子はPrivateにしてください。
× 非推奨 Dim userName As String
○ 推奨 Private userName As String
なお、アクセス指定子の方を省略してDim userName As Stringと書いた場合は、Privateと同じ効果になります。アクセス指定子を省略した場合の効果は変数とそれ以外とでは異なるので、このことは変数についてだけだと覚えておいてください。
クラスレベルの変数は変数というよりもクラスのメンバーであるフィールドなのでローカル変数(プロシージャスコープの変数とブロックスコープの変数)とは違うルールが適用される場合がいくつかあります。
たとえば、プロシージャの中では変数を使っている箇所より上で変数を宣言する必要がありますが、クラスレベルの変数はどこで宣言しても良いです。クラスの上の方で宣言しても良いですし、End Classの直前で宣言しても良いです。(End Classの直後とだとクラスの外側になってしまうのでだめです。)
また、ローカル変数では型推論が利用できるのに対し、クラスレベルの変数では型推論は利用できません。
このプログラムではローカル変数である s2 の型推論機能により文字列型になりますが、フィールドであるs1の方では型推論機能が働かず単純に「As 型名」という記述が省略されているだけだとみなされます。「As 型名」が省略されて型推論もできない場合、その変数はObject型になります。
Public
Class Form1 Private s1 = "あいうえお" Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Dim s2 = "かきくけこ" End Sub End Class |
変数はそのスコープ内で宣言されたときに生成され、スコープから外れたときに消滅します。
次のプログラムはボタンがクリックされるたびに 1 プラスして、カウントを数えているように見えますが、クリックするたびに「1」と表示されます。
Private Sub
Button1_Click(sender As Object, e
As EventArgs)
Handles Button1.Click Dim count As Integer count = count + 1 MsgBox(count) End Sub |
実行がEnd Subに到達した時点で、このプロシージャレベルで宣言されている変数 count は消滅するからです。次にボタンをクリックしたときにまた新しくcount変数が生成されますが、それは新しい変数なので値は 0 です。
この問題を解決するには、やはり変数のスコープを1段階上にあげることになります。
Public
Class Form1 Private count As Integer Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click count = count + 1 MsgBox(count) End Sub End Class |
これでcountはクラスのフィールドになったので、クラスが消滅するまで(フォームの場合、フォームが閉じられるまで)存続します。
この解決方法だと、変数のスコープ自体が変わってしまうのでご注意ください。つまり、本来、プロシージャの中だけでしか使わないのに、消滅すると困るからクラスレベルに格上げしているわけです。そうすると、この変数は他のプロシージャからも見えるようになります。
博士のワンポイントレッスン それってまずいの?
|
もう1つ回避方法があります。
次のようにDimではなく、Static(読み方:Static = スタティック)を使ってローカル変数を宣言すると、スコープからはずれても変数が消滅しなくなり、意図したとおりカウントアップすることができます。
Private Sub
Button1_Click(sender As Object, e
As EventArgs)
Handles Button1.Click Static count As Integer count = count + 1 MsgBox(count) End Sub |
こちらの解決方法だと変数のスコープ自体は変化しないので他のプロシージャからアクセスできないのは良いことです。
このようにローカル変数であるのに、プロシージャ実行終了時にも消滅しない変数というのは、C#やJava、Pythonなど他のメジャーなプログラミング言語には存在しない機能であり、混乱してしまうプログラマーが多いようです。この機能利用するためのキーワード Static も他の言語では別の意味があり混乱に拍車をかけます。そのため、VBのこの機能は便利ではありますが、使用しないほうがいいのではないかといわれることが多いようです。
VBを勉強中のみなさんも他の原則的なルールの理解が不十分になってしまうため使用しないことをお勧めします。VBのことを十分理解したうえで、この機能を便利に使いたいのであれば止めはしません。
発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 マルチスレッドで値を参照・更新するプログラムは結果がプログラマの意図とは違うものになってしまうことがしばしばあります。複数のスレッドで同じ変数の値を参照・更新する場合は不整合が発生しないように対策が必要ですが、Staticは残念ながらこの対策としては使えません。 たとえば、100回カウントアップする機能を100個のスレッドでほぼ同時に実行してみます。単純計算ではカウントは10000になるはずですが、そうはなりません。実行するたびに違う値になります。
もちろん、値の取得・更新のところでSyncLockすればこの部分が同時に実行されることはなくなるので単純計算どおりの結論になります。
|