Visual Basic 初級講座 [改訂版] |
Visual Basic 中学校 > 初級講座[改訂版] >
2021/9/12
この記事が対象とする製品・バージョン
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 | × | 対象外です。 |
目次
Visual Basic を使ってコンパイラーに対する指示を行うことができます。このような指示をコンパイラーディレクティブ、プリプロセスディレクティブ、または単にディレクティブと呼びます。
すべてのディレクティブは # から始まります。たとえば、#Region というディレクティブな何の機能もありませんが、Visual Studio 上では #Region ~ #End Region で囲んだ範囲が折りたたんで表示できるようになります。
ディレクティブの中で最も強力なのは #If です。 #If はコンパイラーに対してコンパイルすべき部分と、すべきでない部分を示します。 この機能を条件付きコンパイルと呼びます。
ディレクティブ自体は、コンパイラーへの指示なので、コンパイルされて実行されることはありませんが、#If による指定はコンパイル結果に大きく影響します。
#If を使うとコンパイラーに対してコンパイルすべき部分とすべきでない部分を示すことができます。
先に結論から書いておくと。条件付きコンパイルは2021年時点ではほぼ使用されません。たいていの場合別のもっと良いやりかたがあるからです。それでも、ごく一部の場合、使用されることはあります。 |
まずは、役には立たないけれどもシンプルな例から紹介していき、徐々に何かに役立ちそうなものを示していくことにします。
次は最も単純な条件付きコンパイルの一例です。
#If False Then ~ #End If で囲んだプログラムはコンパイルされません。コンパイルされないのでVisual Studioのエラーチェック等も実施されず、何でも記述できます。Visual Studio 2010 以上の場合、Visual Basic はこの部分をグレーで表示します。
Debug.WriteLine("この行は出力されます。")
#If False Then
Debug.WriteLine("この行はコンパイルされないので出力されません。")
#End If
Debug.WriteLine("この行も出力されます。")
この例だと通常の If でも結果としては同じようなことができますが、ディレクティブはコンパイルされる前に実行できるので、次のような使い方もできます。これが機能面での通常のIfとの大きな違いです。
#If False Then
Private Function GetMyPictureCount As Integer
#Else
Private Function GetCount() As Integer
#End If
Dim myPictures = Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)
Return IO.Directory.EnumerateFiles(myPictures, "*", SearchOption.AllDirectories).Count
'↓VB 2005, 2008の場合
'Return IO.Directory.GetFiles(myPictures, "*", SearchOption.AllDirectories).Count
End Function
この例では #If によってメソッドの名前が切り替わるというわけです。
#If の条件式には、固定値のTrue/Falseの他、単純な計算や比較、別途宣言した定数や後述する「条件付きコンパイル定数」を指定することができます。
通常のプログラム内で使用する変数・定数・メソッドなどを使った条件指定はできません。 なぜならば、ディレクティブはコンパイル前に実行されるものであり、プログラム内の変数・定数・メソッドはコンパイル後に意味を持つものだからです。
#If False と書くとその部分は 必ずコンパイルされないので、あまり意味はないのですが、私は参考用に他の言語のプログラムを貼り付けるときに、先に#If False Then ~ #End If を記述しておいて、その間に貼り付けることがあります。こうすると、Visual Basicはこの部分を完全に無視するのでエラーになったり、勝手にVB風に整形されたり何かに置き換えられることがなく、貼り付けたままのプログラムが維持されます。 |
私は「シャープ イフ」と呼んでいて、通用するとは思いますが、他の人がなんと発音しているかわかりません。記号としては「#」は英語圏では「パウンド」と呼ぶこともあるようなので、「パウンド イフ」という発音もありえるかもしれません。でも「C#」は「シーシャープ」だから…。うーん。 |
#Const を使うと #If で使える定数を宣言できます。このような定数は通常の定数と区別して「条件付きコンパイル定数」と呼びます。「条件付きコンパイルシンボル」と言う場合もあります。
後述しますが、条件付きコンパイル定数には #Const で自分で定義するもの、プロジェクトの設定等を使って定義するもの、自動的に定義されるものの3種類があります。
次の例は#Const を使ってプログラムで条件付きコンパイル定数を定義する例です。
#Const Version2 = False
#Constを使った条件付きコンパイル定数の定義は同じファイル内であればどこに記述してもよいのですが、プログラムの中にまぎれるとわからなくなってしまいそうなので、目立つように冒頭に記述することが多いようです。
この定数を使って、次のようなディレクティブを記述できます。
Version2 であれば、変数xはLong型としてコンパイルされ、そうでなければxはIntegerとしてコンパイルされるというわけです。
#If Version2 Then
Dim x As Long
#Else
Dim x As Integer
#End If
新しいバージョンである、バージョン2が完成するまでは ディレクティブの定数 Version2 を False にすることでいつでも元のプログラムを実行できます。Version2 を True にすると新しいバージョンの機能の開発やテストができます。
このようにコンパイルする部分を条件で指定するので、この方法を条件付きコンパイルと呼びます。
ただ、この例のようにバージョンを切り替えられるようにしたいのであれば、ソースコード管理のブランチを使用する方が柔軟です。ソースコード管理のブランチとは、VBに限らず、いろいろなプログラムのソースコードを管理するGitなどの仕組みで利用できる機能で、複数バージョンのソースコードを並行開発する技術です。ただ、個人のごく小さなプログラムであれば条件付きコンパイルを使った方が手軽です。
#If では #Const で定義されていない定数を使用することもできます。定義されていない定数を使用すると必ず False と評価されます。
条件付き コンパイル定数は#Constではなく、コンパイラーのパラメーターを使って設定することもできます。。
Visual Studio のバージョンによって設定方法が少し異なりますが、だいたい同じです。Visual Studio 2019の場合は、ソリューションエクスプローラーでプロジェクトを右クリックして「プロパティ」を選択し、表示されるプロジェクトのプロパティの画面で、「コンパイル」を選択し、一番下にある「詳細コンパイルオプション」ボタンをクリックします。
次のようなダイアログが表示されます。
ここのコンパイル定数のカスタム定数欄に条件付きコンパイル定数を定義できます。
たとえば、ここにJapanese=Trueと記述して保存する、Japanseseという条件付きコンパイル定数を#Ifで使うことで条件付きコンパイルをすることが可能です。
#If Japanese Then
Console.WriteLine("こんにちは、世界!")
#Else
Console.WriteLine("Hello, World!")
#End If
これを利用することで、プログラムを変更することなく、条件付きコンパイル定数の定義を変更して、条件付きコンパイルを制御することができるようになります。
いくつかの条件付きコンパイル定数は、Visual Studioやコンパイラーにより自動的に定義されます。
たとえば、デバッグビルドをするときには DEBUG というコンパイラ定数が定義されるので、デバッグ用のプログラムとリリース用のプログラムとでコンパイルを分けることができます。
デバッグ用に詳細なログを出力したいけれども、ログ出力処理が多いとパフォーマンスに影響するという場合にこれが利用できます。
Module Program
Sub Main(args As String())
#If DEBUG Then
DebugLog("プログラムを開始します。")
DebugLog("マシン名:" & Environment.MachineName)
DebugLog("ユーザー名:" & Environment.UserName)
DebugLog("OS:" & Environment.OSVersion.ToString)
#End If
Console.WriteLine("Hello World!")
End Sub
#If DEBUG Then
Private Sub DebugLog(message As String)
Using writer As New IO.StreamWriter("C:\temp\debuglog.txt", True)
writer.Write(Now.ToString("yyyy/MM/dd HH:mm:ss.fff "))
writer.WriteLine(message)
End Using
End Sub
#End If
End Module
このプログラムだとデバッグ用のログの書き込みを実行すると DebugLogメソッドの呼び出し自体がデバッグビルドの時しかコンパイルされないので、リリースビルド時にはまったく余計な処理を行いません。
しかし、フレームワークに存在するDebugクラスやTraceクラスはもともとデバッグ時にしか動作しないようになっているので、自分で #If DEBUG を使用しなくても DeubクラスやTraceクラスの機能を使うことで十分な場合もあります。
たとえば、よく、Debug.WriteLine と記述して、Visual Studio の出力ウィンドウのデバッグ欄にメッセージを出力するサンプルがあります。この初級講座でもよく登場しています。
Deubgクラスには出力先を追加する機能があり、次のようにすることでテキストファイルに出力できるようになります。これを使うことで自分で#If Debugを使わなくてもデバッグビルド時にのみログを出力することが可能になります。
Dim defaultListener As DefaultTraceListener = DirectCast(Trace.Listeners("Default"), DefaultTraceListener)
defaultListener.LogFileName = "C:\temp\debuglog.txt"
Debug.WriteLine("この文字はテキストファイルにも出力されます。")
もともと Debugクラスは、登録されている「トレースリスナー」と呼ばれるものに出力指示を行っているだけで、出力ウィンドウのデバッグ欄に出力しているのはデフォルトのトレースリスナーからの指示です。このデフォルトのトレースリスナーにはファイル出力機能がついていて、LogFileNameプロパティでファイル名を指定するだけで、ファイルに出力してくれます。
この設定はプログラムの開始時点など1回だけ行っておけばよく、それ以降すべての Dedug.WriteLine や Debug.Write は、出力ウィンドウとともに、指定したファイルにも出力されます。
さらに、この仕掛けはなかなかよくできていて、.NET Frameworkのときは、プログラムではなく、アプリケーション構成ファイルに次のような記述をしてトレースリスナーを設定することもできるようになっていました。
.NET Core、.NET 5以降ではこれができなくなっているような…?公式アナウンスがあったかどうか覚えていないのですが、できている人いますか? |
ログ出力以外の処理をデバッグ時だけ行う場合は、後述するConditional属性を使うという選択肢もあります。
なお、リリースビルドを表す条件付きコンパイル定数は既定では存在しませんが、次のようにNot DEBUG を指定することでデバッグビルドでない場合を表現できます。既定では、デバッグビルドの他にはリリースビルドしかありませんから、リリースビルドという意味になります。
#If Not DEBUG Then
'ここに記述した内容は、デバッグビルドの時は実行されません。
'つまり、既定ではリリースビルドの時にのみ実行されます。
#End If
使用しているフレームワークのバージョンも条件付きコンパイル定数として自動的に定義される場合があります。すべての場合で、フレームワークのバージョンを条件にできるわけではありません。
たとえば、.NET Frameworkでは、日本語の文字コードであるShiftJIS(シフトジス)が既定で使用できました。
次の例は、.NET Frameworkで実行すると「あ」のShiftJISでの文字コード 82 A0 を出力します。
Dim code As Byte() = System.Text.Encoding.GetEncoding("shift_jis").GetBytes("あ")
Dim codeText As String = BitConverter.ToString(code).Replace("-", " ")
Debug.WriteLine("あ = " & codeText)
しかし、これを.NET Core、.NET 5以降で実行すると、次のエラーになります。
System.ArgumentException
'shift_jis' is not a supported encoding name. For information on defining a custom encoding, see the documentation for the Encoding.RegisterProvider method.
私訳:'shift_jis'はサポートされているエンコーディングの名前ではありません。カスタムエンコーディングの定義に関しては、Encoding.RegisterProviderメソッドのドキュメントを参照してください。
.NET Core、.NET 5以降ではUTF-8などの代表的ないくつかのエンコーディング以外は既定では使用できなくなっています。とはいえ、次のようにRegisterProvierメソッドを呼び出す1行追加するだけで使用できるようになります。
この例は、.NET Core/.NET 5以降で実行できます。
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance)
Dim code As Byte() = System.Text.Encoding.GetEncoding("shift_jis").GetBytes("あ")
Dim codeText As String = BitConverter.ToString(code).Replace("-", " ")
Debug.WriteLine("あ = " & codeText)
ところが、追加したRegisterProviderメソッドの行は.NET Framework では使用できないクラス CodePagesEncodingProvider を含んでいるため .NET Framework ではビルドエラーになります。.NET Frameworkでも.NET Core/.NET 5以降でも同じソースコードを使いたい場合は条件付きコンパイルの出番です。
Visual Studio 2022 (Preview 3.1) で、.NET 6(プレビュー版)のコンソールアプリをビルドして確認すると、フレームワークを条件にした次の条件付きコンパイル定数が自動的に定義されていました。
条件付きコンパイル定数 | 意味(私の推測) |
---|---|
NET6_0 | フレームワークは .NET 6です。 |
NETCOREAPP | フレームワークは .NET Coreまたは.NET 5以上です。 |
NET5_0_OR_GREATER | フレームワークは .NET 5以上です。 |
NET6_0_OR_GREATER | フレームワークは .NET 6以上です。 |
NETCOREAPP1_0_OR_GREATER | フレームワークは .NET Core 1.0 以上です。 |
NETCOREAPP1_1_OR_GREATER | フレームワークは .NET Core 1.1 以上です。 |
NETCOREAPP2_0_OR_GREATER | フレームワークは .NET Core 2.0 以上です。 |
NETCOREAPP2_1_OR_GREATER | フレームワークは .NET Core 2.1 以上です。 |
NETCOREAPP2_2_OR_GREATER | フレームワークは .NET Core 2.2 以上です。 |
NETCOREAPP3_0_OR_GREATER | フレームワークは .NET Core 3.0 以上です。 |
NETCOREAPP3_1_OR_GREATER | フレームワークは .NET Core 3.1 以上です。 |
※なお、.NET Core 3.1 の次のバージョンが .NET 5 なので、「.NET Core 1.0 以上」という表現の中には.NET 5以上も含んでいます。.NETになじみがない読者のために、私は「.NET Core/.NET 5以上」のような並べて書く表現を多用していますが、これは「.NET Core以上」と同じ意味ということです。
この表を見るとわかるように、.NET Core/.NET 5以上については細かくフレームワークのバージョンを表す条件付きコンパイル定数が用意されているのに対し、.NET Frameworkについてはまったくありません。
今回の CodePagesEncodingProriverクラスは .NET Coreまたは.NET 5以上なら使用できるので、 NETCOREAPP を使用すればよいようです。
次のように条件付きコンパイルを指定することで同じソースコードを.NET Core/.NET 5以上と.NET Framework で使用することができるようになります。
#If NETCOREAPP Then
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance)
#End If
Dim code As Byte() = System.Text.Encoding.GetEncoding("shift_jis").GetBytes("あ")
Dim codeText As String = BitConverter.ToString(code).Replace("-", " ")
Debug.WriteLine("あ = " & codeText)
条件付きコンパイルは、同じソースコードから、条件によって違う結果をコンパイルするものです。だから、同じソースコードを.NET Frameworkと.NET Core/.NET 5以上の両方で使うことは可能ですが、一度ビルドした成果物であるexeやdllなどのアセンブリはこの例の場合.NET Framework専用か、.NET Core/.NET 5以上の専用になります。 同じソースコードが使用できるということの意味は、同じアセンブリを使用できるという意味ではありません。 |
似たような条件付きコンパイル定数に VBC_VER というものがあり、こちらは使用しているVisual Basic の言語仕様のバージョンを表すことになっています。 定数と列挙型 (Visual Basic) | Microsoft Docs …が、わかりにくいですし、VBの言語仕様のバージョンが異なるものを同じソースコードで表現しようとすること自体に何か強烈な良くないことが潜んでいるように感じます。「そうだ!VBC_VER を使えばいいんだ!」と思ったら、たいがい悪い方向に進んでいます。ちょっと視野を広げてよく考えてみてください。 |
自動的に定義される条件付きコンパイル定数のほとんどは、コンパイル時にVisual Studioからコンパイラーにパラメーターとして渡されています。
Visual Studioがコンパイラーをどのように呼んでいるか見てみると、定義されている条件付きコンパイル定数を確認することができます。
コンパイラーの呼び出しを確認するには、Visual Studio の[ツール]メニューから[オプション]を表示し、「プロジェクトおよびソリューション」の中の「ビルドして実行」で、「MSBuild プロジェクト ビルドの出力の詳細」で「詳細」を選択して、「OK」します。
この設定画面の構成や表示はVisual Studioのバージョンによって少し違います。Visual Studio 2019, 2022 (Preview 3.1)ではこの位置にあることを確認しています。
また、以前のバージョンのVisual Studioでは、「詳細」ではなく、「標準」でもOKでした。
この状態でプロジェクトをビルドすると、出力ウィンドウに詳細なログが出力されます。簡単に試すには[ビルド]メニューから[ソリューションのビルド]を実行してみてください。
空のプロジェクトでもかなり大量のログが出力されます。VBのコンパイラーは vbc.exe なので、出力ウィンドウの内容を vbc.exe で検索すると、vbc.exe がどのように呼び出されているかわかります。
パラメーターが大量に付いていて、かなり長いので、抜粋したものを紹介します。これはVisual Studio 2022 Preview 3.1 で、.NET 6.0 プレビューの空のクラスライブラリーをコンパイルした時の例です。
C:\Program Files\Microsoft Visual Studio\2022\Preview\MSBuild\Current\Bin\Roslyn\vbc.exe /noconfig /imports:Microsoft.VisualBasic,System,System.Collections,System.Collections.Generic,System.Diagnostics,System.Linq,System.Xml.Linq,System.Threading.Tasks,System.Data,System.Drawing,System.Windows.Forms /optioncompare:Binary /optionexplicit+ /optionstrict:custom /nowarn:41999,42016,42017,42018,42019,42020,42021,42022,42032,42036 /nosdkpath /optioninfer+ /nostdlib /errorreport:prompt /rootnamespace:WinFormsApp1 /preferreduilang:ja-JP /highentropyva+ /vbruntime:"C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\5.0.0\ref\net5.0\Microsoft.VisualBasic.dll" /define:"CONFIG=\"Debug\",DEBUG=-1,TRACE=-1,_MyType=\"WindowsForms\",PLATFORM=\"AnyCPU\",NET=-1,NET5_0=-1,NETCOREAPP=-1,WINDOWS=-1,WINDOWS7_0=-1,NET5_0_OR_GREATER=-1,NETCOREAPP3_0_OR_GREATER=-1,NETCOREAPP3_1_OR_GREATER=-1,WINDOWS7_0_OR_GREATER=-1" /reference:"C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\5.0.0\ref\net5.0\Accessibility.dll","C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\5.0.0\ref\net5.0\Microsoft.CSharp.dll","C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\5.0.0\ref\net5.0\Microsoft.VisualBasic.Core.dll","C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref\5.0.0\ref\net5.0\Microsoft.VisualBasic.dll","C:\Program Files\dotnet\packs\Microsoft.WindowsDesktop.App.Ref
条件付き コンパイル定数は /define で定義することになっています。省略形は /d です。上記の例では /define の内容を赤くしました。
空のプロジェクトでもCONFIG, DEBUG, TRACE, _MyType などさまざまな条件付きコンパイル定数が定義されていることが確認できます。プロジェクトのプロパティから追加した条件付きコンパイル定数があれば、それもここで確認できます。(#Constで定義した定数はパラメーターとしては渡されません。)
このように自動的に定義される条件付きコンパイル定数のほとんどはVisual Studioがvbc.exeに渡しているものですが、私が知る限りVBC_VERだけは、コンパイラー自身が定義を追加する条件付きコンパイル定数のように見えます。
Conditional属性(読み方:Conditional=コンディショナル)を使うと、指定した条件付きコンパイル定数が定義されている場合だけ呼び出せるメソッドを定義できます。
通常のプログラムである属性が、プリプロセッサと連携するというかなり例外的な存在です。
例外的な存在のため、.NETのルールでは、コンパイラーはConditional属性を無視してよいことになっており、一部の.NET言語ではこの属性は機能しないようです。とはいえ、VB、C#、F#、C++のコンパイラーはこの属性を見てくれることになっているのであまり心配することはありません。
参考
ConditionalAttribute クラス (System.Diagnostics) | Microsoft Docs
Conditional属性を付けられるメソッドは、値を返さない Sub で定義されているメソッドのみです。
少し長いですが、次のようにデバッグビルド時にのみデバッグ用のログを出力する例を見てください。
Module Program
Sub Main(args As String())
StartDebug("プログラムを起動しました。")
Console.WriteLine("終了")
Console.ReadLine()
End Sub
''' <summary>
''' デバッグ用に環境情報を出力します。
''' </summary>
<Conditional("DEBUG")>
Private Sub StartDebug(additionalData As String)
Using writer As New IO.StreamWriter("C:\temp\debug.log")
'▼プログラムのパス
Dim appPath As String = Reflection.Assembly.GetExecutingAssembly().Location
writer.WriteLine("プログラムの場所:" & appPath)
'▼ホスト名
Dim host = Net.Dns.GetHostEntry(Net.Dns.GetHostName())
Dim hostName As String = host.HostName
writer.WriteLine("ホスト名:" & hostName)
'▼IPアドレス v4(この例では複数のIPアドレスがある場合、最初に見つかったそれっぽいものを取得します。)
Dim ipv4 = From address In host.AddressList
Where address.AddressFamily = Net.Sockets.AddressFamily.InterNetwork
writer.WriteLine("IPアドレス(v4):" & ipv4.First.ToString)
'▼追加情報
If Not String.IsNullOrEmpty(additionalData) Then
writer.WriteLine(additionalData)
End If
End Using
End Sub
End Module
この例では StartDebugメソッド内で、デバッグに役に立ついくつかの情報を記録するようにしています。これらの処理は単純に値をファイルに書き出すだけではなく、プログラムのパスやIPアドレスなどの情報取得を実行している点に注意してください。つまり、Debug.WriteLineを使ってもこれらの処理をデバッグ時だけ実行するようにはできないということです。
このメソッドには Conditional属性がついていおり、引数に "DEBUG" が指定されています。この属性に、このように条件付きコンパイル定数を指定することで、そのコンパイル定数が True と評価される場合だけ、このメソッドの『呼び出し』がコンパイルされます。
メソッド本体は常にコンパイルされます。条件付きコンパイルになるのはそのメソッドの呼び出しです。結果として上記の例はリリースビルドではStartDebugメソッドが実行されません。
だから、.NET Core/.NET 5以上だけ利用できる処理を <Conditional("NETCOREAPP")> で記述しても、.NET Frameworkでは実行できません。
一例を紹介します。次の例では InitNetCoreAppメソッド内に .NET Frameworkには存在しないクラス CodePagesEncodingProvider を含んでいるため、Conditional属性で NETCOREAPP を指定しても、.NET Framework ではコンパイルできません。
Module Program
Sub Main(args As String())
InitNetCoreApp()
Dim code As Byte() = System.Text.Encoding.GetEncoding("shift_jis").GetBytes("あ")
Dim codeText As String = BitConverter.ToString(code).Replace("-", " ")
Console.WriteLine("あ = " & codeText)
End Sub
''' <summary>
''' .NET Core/.NET 5以上の場合にだけ実行する処理
''' </summary>
<Conditional("NETCOREAPP")>
Private Sub InitNetCoreApp()
System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance)
End Sub
End Module
ソースファイルのプロパティでビルドアクションを、「なし」にすると、そのファイルはコンパイラーから丸ごと無視されます。
この設定には条件を付けることはできません。
ただ、この設定を行ってもプログラムの色がグレーになるなどの変化がないため、#If などと違ってわかりにくく、チーム開発している場合などは、他のメンバーがビルドアクションがなしになっていることに気が付かないで困ってしまうことがあるかもしれません。あまり多用しない方が良いでしょう。