更新日: 2010 年 2 月 8 日

執筆者: エディフィストラーニング株式会社 矢嶋 聡

この記事は、「MSDN プログラミング シリーズ」として発行している技術書籍「ステップアップ Visual Basic 2010 ~開発者がもう一歩上達するための必読アドバイス」(日経 BP 社刊) を基に先進的なテクニックを紹介しています。


目次

  1. はじめに
  2. VB6.0 と VB.NET では異なるローカル変数の宣言位置と適用範囲
  3. 暗黙的なローカル変数の宣言は使用すべきか?
  4. For 文ループでのローカルな変数の宣言

1. はじめに

今回は、変数宣言に関するもののうち注意すべき点をいくつか取り上げます。また、以前取り上げた Option Strict や Option Infer のほか、特に変数宣言に係るコンパイル オプションについて補足します。

Visual Basic 6.0 (.NETでないVisual Basic) から Visual Basic .NET に遷移する過程で、変数宣言に関しても変更されたものがあるほか、VB.NET 2002 以降もバージョン アップによって、追加や改訂された部分があります。ここでは、バージョン間の違いも踏まえながら、これらを確認していきます。なお、サンプル コードについては、特にバージョンが明示されていない場合、2010 年 2 月の現時点で利用可能な Visual Basic 2010 Beta 2 で動作を確認しています。

実際の業務では、Visual Basic 6.0 のコードの移植や、Visual Basic 2005 のコードを Visual Basic 2010 環境へ移植するなど、特定のバージョン間の差異を意識する必要が生ずる場合もあるでしょう。既存資産のコードを確認するときや、他の人が作成したコードをレビューするときなど、いざというとき困らないように、改めて確認していきましょう。

ページのトップへ


2. VB6.0 と VB.NET では異なるローカル変数の宣言位置と適用範囲

まず、メソッド (プロシージャ) の内部に定義されたローカル変数の宣言位置について確認します。以下の例は、TestProc1 メソッドの中に定義されたローカル変数の簡単な例です。

例 1. 様々な位置のローカル変数宣言

Visual Basic
Private Sub TestProc1()

    Dim a As Integer	←①
    a = 0
    a = a + 2

    Dim i As Integer	←②
    i = 1
    c = 2	'NG?		←③

    If i = 1 Then
        Dim b As Integer	←④
        b = 10
        Dim a As Integer 'NG?	←⑤
        a = 10
    End If

    b = b + 1	'NG?		←⑥

End Sub
 

基本的な事項となりますが、①、②、④のように、ローカル変数の宣言はメソッド内の任意の位置で行うことができます。必ずしも、メソッド ブロックの中の先頭で行う必要はありません。ただし、宣言した位置から後でないと、その変数を利用できないので、②の変数 i を使用できるの場所は、②より後であり、このメソッド ブロックの最後まで (End Sub の前まで) となります。

また、④のように制御構造のブロックの中に変数を定義した場合は、Visual Basic 6.0 と VB.NET では扱いが異なるので注意が必要です。Visual Basic 6.0 では、変数を定義したら単純にメソッドの最後まで変数が有効でした。しかし VB.NET では、If 文のブロックやループのブロックなど、制御構造のブロック内部で定義した変数は、そのブロックの中だけで有効なローカル変数になります。

つまり、④の変数 b ように If ブロック内部で宣言した際、⑥のように If 文のブロックの外で変数 b を参照した場合、Visual Basic 6.0 では純粋に④以降の位置で参照できるので問題ありませんが、VB.NET では変数が宣言されてないとして、⑥の位置でコンパイル エラーになります。
このような違いがあるので、Visual Basic 6.0 の既存資産のソース コードを VB.NET に移植する場合、変数の宣言位置に注意する必要があります。

また VB.NET の言語仕様では、④や⑤のような制御ブロック内のローカル変数は、その上位のブロックのローカル変数と重複する名前を付けることができないので注意が必要です。たとえば、⑤の変数 a は、その上位の①のローカル変数と名前が重複するので、⑤の時点でコンパイル エラーになります。

なお、Visual Basic 6.0 ではブロック内のローカル変数という概念がないので、そもそも①と⑤は同じメソッドのローカル変数とみなされ、やはり重複として扱われ、コンパイル エラーになります。

ページのトップへ


3. 暗黙的なローカル変数の宣言は使用すべきか?

VB.NET の既定のコンパイル オプションでは、Option Explicit が On の状態であり、変数は宣言しておかないと利用することができません。よって、例 1 の③のように宣言無しで変数 c を使用しようとすると、これもコンパイル エラーになります。

しかし、Option Explicit を Off の状態に変更した場合、③のように宣言していない変数を使用すると、その時点で「暗黙的に」変数の宣言を行ったとみなされ、有効な変数となります。ただし、このような暗黙的な宣言の場合は、たとえ Option Infer On の状態であっても、以前説明した「型の推論」は利用できず、Object 型の変数とみなされるので注意してください。

Note: 型の推論については、『第 3 回 Variant 型とは違う! Dim 文の変数宣言における「型の推論」』を参照してください。

なお、このような暗黙的な宣言は、プログラムの信頼性が低下するのでおすすめできません。たとえば次のコードのように、変数 count を宣言して、この変数に 10 を代入することにします。

例 2. 変数 count の宣言と利用

Visual Basic
Dim count As Integer
connt = 10    'スペルミス?
 

2 行目の代入文では、変数のスペルを「connt」と間違えており、当然ながら存在しない変数なのでコンパイル エラーになります。しかし、あくまでコンパイル エラーになるのは、既定構成である Option Explicit On の場合です。Off の状態に変更すると、正常にコンパイルできます。なぜなら、Off の状態では 2 行目の代入文を暗黙的な変数 connt の宣言と見なすからです。さらに悪いことに、この 2 行のステートメントが実行された直後では、問題無く変数 count と変数 connt がメモリ上に用意されるので、実行エラーさえ起こりません。おそらくは実行中のアルゴリズムが正しく動作しない段階でバグに気づくことになりますが、この 2 行のステートメント自体は正しい構文なので、このスペルミスがバグの原因であると突き止めることが困難になります。

このような暗黙的な宣言を使用する状況として考えられるケースは、Visual Basic 6.0 などの既存資産のソース コードを暫定的にそのまま利用するような場合です。Visual Basic 6.0 では、既定で暗黙的な宣言が有効だったので、既存のソース コードの中には暗黙的な宣言を使用している場合があります。VB.NET を有効活用するのであれば、既定の Option Explicit On の状態にするのが望ましく、Option Explicit Off は Visual Basic 6.0 からの移行時など、限定的に使用するほうが賢明と言えるでしょう。(もっとも、Visual Basic 2008 などのアップグレード ウィザードを使用して、Visual Basic 6.0 のソース コードを自動変換して移行すると、Option Explicit On の状態に自動変換されるので、そういう意味でも Option Explicit Off の使用はかなり限定的であると言えます。)

Note: よって、これ以降の説明も、Option Explicit On を前提にします。

ページのトップへ


4. For 文ループでのローカルな変数の宣言

Visual Basic .NET 2002 では、For...Next 文のループで使用するカウンタ変数などは、次の例の①ようにループよりも前に予め宣言する必要がありました。

例 3. VB.NET 2002: For 文ループでのカウンタ変数を予め宣言する

Visual Basic
Dim i As Integer	←①
For i = 1 To 5

Next
 

しかし Visual Basic .NET 2003 からは、次の例に示すように、For...Next 文や For Each...Next 文のループにおいて、そのループ ブロックで有効なローカル変数が定義できるようになりました。以下の例の②や④では、それぞれカウンタ変数 i、要素を表す変数 dt が宣言されています。

例 4. VB.NET 2003 以降: For 文ループでのローカル変数として宣言する

Visual Basic
Private Sub TestProc2()

    Dim data() As Integer = {10, 20, 30, 40, 50}
    Dim k As Intger		←①

    For i As Integer = 1 To data.Length		←②

    Next

    For k As Integer = 1 To data.Length	'NG?	←③

    Next

    For Each dt As Integer In data		←④

    Next

End Sub
 

これらのループ ブロックでのローカル変数は、この記事の最初に取り上げた制御ブロック内のローカル変数と同様で、そのループ ブロックにおいてのみ有効なローカル変数です。その意味で注意すべき点も同様であり、ループ ブロック用に宣言したローカル変数は、ループ ブロックの外側のローカル変数と名前が重複することができません。よって、上記の例の③のカウンタ変数 k は、①のローカル変数と重複するのでエラーになります。

また逆に「As 句」を省略した場合、『第 3 回 Variant 型とは違う! Dim 文の変数宣言における「型の推論」』で取り上げた 「Dim 文」における As 句の省略とは、振舞いが若干異なるので注意してください。

For 文ループのカウンタ変数などで As 句を省略した場合、そのカウンタ変数と同名の変数が、ループよりも前に宣言されていたら、その変数を引き継ぎます。つまり、例 3 にある VB.NET 2002 のケースになります。また、As 句を省略した場合に、そのカウンタ変数と同名の変数がループよりも前に宣言していなければ、既定の Option Infer On の状態のもとでは、型の推論が成立してループ内のローカルな変数の宣言と見なされます。

つまり、ループよりも前に同名のローカル変数が存在するか否かによって、振舞いが異なるのです。これは、VB.NET 2002 との整合性を保つための文法であるとも見ることもできます。
正確にいうとコンパイル オプションによって状況が異なります。Option Explicit、Option Strict、および Option Infer がそれぞれ On や Off の場合や、カウンタ変数と同名の変数が有無の場合など、合計 16 通りのケースが考えられるのですが、すべてを吟味すると切りがないので、ここでは既定構成の場合だけに話を留めておくことにします。

ここで既定構成 (Option Explicit On/Option Strict Off /Option Infer On) の状態で、For 文ループでのローカル変数の宣言方法についてまとめると、次のような 3 つのパターンの選択肢になります。

  • [A] カウンタ変数などの As 句を記述して、For 文ループ内のローカル変数を明示的に宣言する。ただし、ループよりも前に同名のローカル変数があってはならない。
  • [B] カウンタ変数などの As 句を書かず、ループよりも前に宣言した同名の変数を流用する。
  • [C] カウンタ変数などの As 句を書かず、ループよりも前に宣言した変数名の使用を避け、型の推論を用いてループ内のローカル変数として宣言する。

[C] は型の推論が伴うので、Visual Basic 2008 以降のみで可能な選択肢です。Visual Basic .NET 2003 から Visual Basic 2005 までは [A] と [B] の2つの選択肢となります。 Visual Basic .NET 2002 では [B] だけです。

それでは現在は、この 3 つの中うち、どれが最もよいのでしょうか。

筆者の意見としては、原則として [A] のパターンをおすすめします。また、一部の状況では [C] のパターンも必要になります (後述)。

そもそも、For...Next ループでのカウンタ変数や For Each...Next ループでの要素を表す変数は、そのループだけ使用する場合が多くあります。よって、ほとんどの場合は [A] のようなループ内でのローカル変数であれば十分です。そうした観点から、[B] を使う必要はほとんどないでしょう。(もちろん、ループによる集計が終了した時点のカウンタ変数の値を調べたいような場合は別です。) また、[A] の構文を使用することで、For 文ループのローカル変数であることを明示でき、可読性も向上します。

逆にループ内のローカル変数として宣言する際に As 句を書かずに、[C] の型の推論を使用してローカル変数を定義する場合、万が一、ループよりも前に同名のローカル変数が存在することを見過ごすと、その変数を引き継いでしまって [B] が成立します。つまり、他の目的で使用しているはずのローカル変数をループで使用してしまい、値を壊してしまいます。1 つのメソッドの中の場所によって、同じ名前の変数の動きが異なるため、可読性も低下することでしょう。

[A] であれば、カウンタ変数の名前がループよりも前の変数宣言と重複した場合、コンパイル エラーとして発覚するので、見逃すことはありません。

結局のところ、[C] を使おうとした場合、うっかり [B] になってしまう場合があるということですが、実は、状況によっては [C] が必要であり、[C] で問題がない場合があります。

それは、『第 3 回 Variant 型とは違う! Dim 文の変数宣言における「型の推論」』で取り上げた「匿名型」を使用する場合です。次の例の 2 つ目の Dim 文では、LINQ のクエリ式を定義しています。このクエリ式では、①で要素の構造を定義し、その要素の集合を返します。実質的に①の部分で匿名型の要素を定義しています。匿名型の要素なので、②の要素を表す変数 d では As 句が書けないので、必然的に [C] の型の推論を使用するパターンを使うことになります。

例 5. 匿名型要素の変数を使用した For Each ループ

Visual Basic
Dim rects(99) As Rectangle

Dim query = From r In rects Where r.Width >= 100 _
            Select r.Width, r.Height, Area = r.Width * r.Height	←①

For Each d In query	←②

Next
 

ただし、この場合は、ループよりも前に別の変数 d がたまたま同じ名前で定義されていたとしても、うっかり見落として [B] がまず成立することはありません。なぜなら、[B] の状況ではループよりも前に変数 d が明示的に宣言されているので、変数 d は匿名型のはずがありません。匿名型でない変数 d を②の部分でうっかり使用したとしても、②の部分の In の後ろの変数 query と型の整合性が取れず、コンパイル エラーとして発覚します。(厳密にいうと、この変数 d が For 文ループよりも前に、Object 型のローカル変数として宣言されていると、②の部分で使用してもコンパイル エラーにはなりません。しかし、Object 型のローカル変数として宣言する機会は少ないでしょう。というのも、ローカル変数として使用する以上、作成者にとってその変数の意図は分かっているはずなので、As 句を使って特定の型として変数 d を宣言するか、または、型の推論を使用して特定の型にするはずです。)

いずれにしても、[C] のケースである②の部分のような For 文ループのローカル変数 d では、そのループよりも前に同名のローカル変数が宣言してあるか意識する必要はあります。しかし、メソッド内の局所的な範囲で意識すればよいので、このことがコーディングを行う上での大きな負担になることはないでしょう。

このように For 文ループも Visual Basic のバージョン アップに伴って進化し、各バージョンに相応しい使用方法があるのでおぼえておいてください。


Code Recipe .NET Framework デベロッパー センター

ページのトップへ