鷲ノ巣

C# とか PowerShell とか。当ブログの記事は(特に公開直後は)頻繁に改定される場合があることをご了承ください。

Visual Basic の 4 つの Option について

普段は C# をメイン言語にしているのですが、諸事情により、VB も書かねばならないことがありまして。
今回は「おまじない」程度に認識していた*1Visual Basic の 4 つの Option についての備忘録となります。

4 つの Option とは

Visual Basic の 4 つの Option とは、以下のものを指します。

Option Compare は Binary または Text の値を取ります。他の 3 つは On または Off の値を取ります。
これらは、プロジェクトのプロパティで設定できるほか、各 *.vb ソース ファイルごとにも指定することができます。
ソース ファイルで指定する場合、ファイルの先頭に記述します。

なお、以下の記述において、挙動やソース コードの観察から得た情報については、記事執筆時点でのものとし、特記事項のない限り、バージョンは .NET 6.0 / Visual Studio 2022 を対象とします。

Option Compare

コード中で =< 等の演算子によって文字列を比較する際の比較方法を指定します。
Binary を指定すると、Unicode のコードポイントによって比較され、異なる文字は厳密に区別されます。
Text を指定すると、現在のカルチャに依存した比較となり、大文字と小文字、半角と全角、ひらがなとカタカナが同一視されます。

個人的な推奨値は Binary です。

Option Compare Binary

Imports System

Module Program
    Sub Main
        ' 結果はすべて False
        Console.WriteLine("A" = "a") ' 大文字と小文字
        Console.WriteLine("A" = "A") ' 半角と全角
        Console.WriteLine("あ" = "ア") ' ひらがなとカタカナ
    End Sub
End Module
Option Compare Text

Imports System

Module Program
    Sub Main
        ' 結果はすべて True
        Console.WriteLine("A" = "a") ' 大文字と小文字
        Console.WriteLine("A" = "A") ' 半角と全角
        Console.WriteLine("あ" = "ア") ' ひらがなとカタカナ
    End Sub
End Module

Roslyn の実装では、演算子による文字列比較は Operators.CompareString メソッドの呼び出しに変換されます。
Option Compare の指定は、このメソッドの TextCompare 引数に対応し、Binary を指定すると FalseText を指定すると True が渡されます。
そして、この引数の値が False の場合は、String.CompareOrdinal メソッドによって比較され、True の場合は、現在のカルチャにおける CompareInfo.Compare メソッドによって比較されます。
後者の場合に使用される CompareOptions は、IgnoreCaseIgnoreKanaTypeIgnoreWidth の組み合わせとなっています。

Option Explicit

ローカル変数の宣言を必須とするかどうかを指定します。
On を指定した場合、ローカル変数は事前に宣言されていなければなりません。
Off を指定した場合、未宣言の変数に対して代入または参照が行われると、その場で変数が定義されたものとみなされます。

個人的な推奨値はもちろん On です。

Option Explicit On

Imports System

Module Program
    Sub Main
         ' 未宣言の変数への代入はエラー
        NonDeclaredVariable = 1

        ' 未宣言の変数の参照はエラー
        Console.WriteLine(AnotherVariable)
    End Sub
End Module
Option Explicit Off

Imports System

Module Program
    Sub Main
        ' この時点で Object 型の変数が定義されたものとみなされる
        NonDeclaredVariable = 1

        ' AnotherVariable には Object 型の既定値(Nothing)が入っているとみなされる
        Console.WriteLine(AnotherVariable)
    End Sub
End Module

なお、Off を指定した場合でも、さすがにメンバー変数は暗黙に定義されません。

また、変数が暗黙に定義された場合、後述する Option Strict が On であってもエラーにはならず、その型は強制的に Object 型となります。後述する Option Infer が On であっても、型推論はされません。

Option Strict

変数の型を指定する必要があるかどうか、また、代入時の型チェックを厳密に行うかを指定します。
On を指定した場合、変数の型は明確に指定されなければならず、代入時には型が一致しなければなりません(数値型の拡大変換や、オブジェクトのアップ キャストなどの変換は有効です)。
Off を指定した場合、型を指定されていない変数は Object 型になります。また、型が明示されている場合、数値型の縮小変換、オブジェクトのダウン キャスト、数値と文字列の相互変換、メンバーの参照などが暗黙的に行われても、コンパイル エラーが発生しなくなります(変換が無効な場合、実行時エラーが発生します*2)。

全般的に、Off の場合、実行時に成功するかもしれない暗黙の変換はコンパイル エラーにしないという方針だと思われます。

個人的な推奨値は On です。

Option Strict On
Option Infer Off

Module Program
    ' 型が指定されていないためエラー
    Private FieldVariable

    Sub Main
        ' 型が指定されていないためエラー
        Dim LocalVariable

        ' 暗黙的な縮小変換はエラー
        Dim IntegerVariable As Integer = 100000
        Dim ShortVariable As Short = IntegerVariable

        ' String 型を Integer 型に代入しようとしているためエラー
        IntegerVariable = "x"

        ' 暗黙的な型のダウンキャストはエラー
        Dim d As Derived = New Base()

        ' Object 型には Hello というメンバーがないのでエラー
        Dim o As Object = New Base()
        Console.WriteLine(o.Hello)
    End Sub
End Module

Class Base
    Public Property Hello As String = "Hello"
End Class

Class Derived
    Inherits Base
End Class
Option Strict Off
Option Infer Off

Module Program
    ' 型は Object
    Private FieldVariable

    Sub Main
        ' 型は Object
        Dim LocalVariable

        ' コンパイル オプションによっては、切り捨てられたり、実行時エラー(OverflowException)になったりする
        Dim IntegerVariable As Integer = 100000
        Dim ShortVariable As Short = IntegerVariable

        ' String から Integer への変換を試みる(この場合は実行時エラー)
        IntegerVariable = "x"

        ' 実行時エラー(InvalidCastException)が発生する
        Dim d As Derived = New Base()

        ' 遅延バインディングが行われ "Hello" が表示される
        Dim o As Object = New Base()
        Console.WriteLine(o.Hello)
    End Sub
End Module

' Base と Derived の定義は上と同じなので省略

Option Infer

型推論を有効にするかどうかを指定します。
On を指定した場合、変数の型は右辺から推論されます。
Off を指定した場合の挙動は、Option Strict の指定に依存します。

個人的な推奨値は On です。

Option Infer On
Option Strict On

Module Program
    Sub Main
        ' 型は Integer
        Dim LocalVariable = 1
    End Sub
End Module
Option Infer Off
Option Strict On

Module Program
    Sub Main
        ' 型が明示されないためエラー
        Dim LocalVariable = 1
    End Sub
End Module
Option Infer Off
Option Strict Off

Module Program
    Sub Main
        ' 型は Object
        Dim LocalVariable = 1
    End Sub
End Module

なお、他の 3 つは Visual Basic 6.0 の頃からある由緒正しいオプションですが、Option Infer は、バージョン 9.0*3から追加されたオプションです。

Strict と Infer の整理

上記の例に見られるように、変数宣言時に型が明示されなかった場合、Option Strict と Option Infer は相互に関係した挙動をしますので、一覧にまとめます。

Strict Infer 初期値がある場合 初期値がない場合
On On 右辺から推測される エラー
On Off エラー エラー
Off On 右辺から推測される Object 型になる
Off Off Object 型になる Object 型になる

また、繰り返しになりますが、Option Explicit が Off の場合に、宣言されずに暗黙に変数が定義された場合、Option Strict が On であってもエラーにならず、その型は、Option Infer の指定にかかわらず Object 型になります。

各オプションの指定方法と既定値

上記の各サンプル コードにもあるように、個々の *.vb ソース ファイルの先頭に記述することで、オプションを指定することができます。

Visual Studio 上では、プロジェクトのプロパティで一括指定することができます(各ソース ファイルでも指定されている場合は、そちらが優先されます)。

プロジェクトのプロパティで指定した内容は、プロジェクト ファイル(*.vbproj)に、以下のように記録されます。
プロジェクト ファイルで指定しない場合の既定値も以下の通りです。

<PropertyGroup>
  <OptionCompare>Binary</OptionCompare>
  <OptionExplicit>On</OptionExplicit>
  <OptionStrict>Off</OptionStrict>
  <OptionInfer>On</OptionInfer>
</PropertyGroup>

.NET Core 以降の SDK スタイルのプロジェクトでは、特に変更しない限り、プロジェクト ファイルには何も書かれず、上記の既定値が採用されます。

.NET Framework のプロジェクトでは、プロジェクト作成時にプロジェクト ファイルに書き込まれているはずです。
通常、.NET Framework のプロジェクトファイルを手動で編集することはしないものですが、むりやり消した場合は、上記の既定値になります。

プロジェクト作成時に設定される値は、Visual Studio のオプションで指定することができます(この設定は SDK スタイルのプロジェクトには適用されません)。

最後に

Option Compare は Binary、他は全部 On にしましょう。C# と同じ挙動になるので。お兄さんとの約束だぞっ!

*1:だって一通りだけ暗記しておけば困らないんだもの。

*2:数値型の縮小変換がエラーになるかどうかはコンパイル オプションによります。

*3:C# 3.0 で var による型推論が導入され、.NET に LINQ が導入されたのと同じ世代。たぶん。