連載

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

Chapter 14 属性

株式会社ピーデー 川俣 晶
2004/09/02
Page1 Page2 Page3 Page4

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

 固定長文字列ランダム・ファイルとVBFixedString属性属性とは何か?を読み比べて、「あれ?」と思った読者もいるのではないだろうか。というのは、VBFixedStringAttribute属性を、VBFixedString属性と記述しているケースも存在するからである。さらに、名前空間を明示した名前を記述しても、エラーにならない。以下のサンプル・プログラムは、Serializableを例にして、これらのバリエーションを並べて書いてみたものである(リスト14-7)。

 1: <Serializable()> Private Class Sample1
 2:   Public a As Integer
 3:   Public b As Integer
 4: End Class
 5:
 6: <SerializableAttribute()> Private Class Sample2
 7:   Public a As Integer
 8:   Public b As Integer
 9: End Class
10:
11: <System.SerializableAttribute()> Private Class Sample3
12:   Public a As Integer
13:   Public b As Integer
14: End Class
リスト14-7 属性を記述する場合の3つバリエーション

 このサンプル・プログラムで示した3つのクラスは、まったく同じ内容を定義していることになる。これらのクラスに付いた属性名、Serializable、SerializableAttribute、System.SerializableAttributeは、すべて同じものを示している。これは、2つの事実を知ることで容易に理解することができる。

 第1の事実は、属性の実体はクラスであるということだ。そのため、記述方法もクラスと何ら変わることはなく、System.SerializableAttributeとフルネームで書いてもよく、省略可能な名前空間名を省略して、SerializableAttributeと書いてもよい。また、Imports文でデフォルトの名前空間名を指定すれば、属性の名前空間名の部分を省略して書くこともできる。属性の機能について調べたいときも、クラス・ライブラリのリファレンスを開き、クラス名として属性名を調べることで、必要な情報を得ることができる。

 第2の事実は、属性を実現しているクラスの名前は、必ずAttributeという文字列で終わり、この文字列は属性としてソース・コードに記述する際に省略可能ということである。つまり、SerializableAttributeという属性名を記述する際、最後の“Attribute”を省略して、Serializableとだけ書いてもよいということである。この機能により、VBFixedStringAttributeとVBFixedStringは、どちらの名前を使っても結果は同じである。また、リファレンス・マニュアルを引くときに、「VBFixedString」というキーワードでは出てこない場合があったとしても、それに省略された文字列Attributeを補い、「VBFixedStringAttribute」というキーワードで引いてみるというテクニックも可能である。しかし、この省略は属性として記述する場合にのみ有効なものであり、クラス名として記述する場合はAttributeという文字列を省略できない。

 なお、リファレンス・マニュアルの古い版では、VBFixedStringAttributeを引いても、これが属する名前空間が明記されていないが、これはオブジェクトブラウザで見ればすぐに確認できる。VBFixedStringAttribute属性の名前空間は、Microsoft.Visual Basicである。つまり、そのフルネームは、Microsoft.Visual Basic.VBFixedStringAttributeとなる。

●図14-8 オブジェクトブラウザで表示させたVBFixedStringAttribute属性(VBFixedStringAttributeクラス)
 
Declare文の引数型を変換するMarshalAs属性

 VB.NETは.NET Frameworkの幅広いクラス・ライブラリにアクセスできるため、Win32 APIを呼び出すDeclare文を使用する機会はVB 6と比べて減少したのではないかと思う。しかし、Win32 APIを呼び出す機会がゼロになったわけではなく、依然としてDeclare文は存在している。このとき問題になるのが、文字列などのデータ型がVB.NETとWin32 APIで互換性がまったくないことだ。VB.NETの文字列は、System.Stringクラスの文字列クラスにより表現されるが、Win32 APIでは文字型の配列として表現される。この相違を乗り越えるには、API呼び出し時に適切なデータ型の変換が必要となる。このときの変換方法を指定する手段として、MarshalAs属性が使用される。リスト14-9は、実際にMarshalAs属性を使用したサンプル・プログラムである。

 1: Imports System.Runtime.InteropServices
 2:
 3: Public Class Form1
 4:   Inherits System.Windows.Forms.Form
 5:
 6: …Windows フォーム デザイナで生成されたコード…
 7:
 8:   Declare Auto Sub MessageBox Lib "user32.dll" ( _
 9:     ByVal hWnd As Integer, _
10:     <MarshalAs(UnmanagedType.LPTStr)> ByVal lpText As String, _
11:     <MarshalAs(UnmanagedType.LPTStr)> ByVal lpCaption As String, _
12:     ByVal uType As Integer)
13:
14:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
15:     MessageBox(0, "Hello!", "Sample Program", 0)
16:   End Sub
17: End Class
リスト14-9 Declare文の使用時に、データ型の変換方法を指定するMarshalAs属性を使用したプログラム

 これを実行すると以下のようになる。

●図14-10 図14-9の実行結果

 ここで注目すべき点は、8〜12行目のDeclare文の中で使用されているMarshalAs属性である。10行目と11行目の文字列は、そのままWin32 APIに渡すことができないので、変換する必要がある。そこで、MarshalAs属性はその必要性を指定している。具体的にどのようなデータ型に変換するかは、UnmanagedType列挙型の値で指定している。MarshalAs属性とUnmanagedType列挙型は、System.Runtime.InteropServices名前空間に属しているので、1行目のImports文でこの名前空間を指定している。

 さて、Win32 API呼び出し時の文字列に関するもう1つの問題は、Win32 APIには文字コードの相違によって同じ機能を持ったAPIが実際には2つ存在している場合があることだ。つまり、その2つのどちらを呼び出すかの明示的な指定と、どちらの文字コードを指定して引数を変換させるかという指定が必要とされる。リスト14-9の場合、Declareキーワードの次のAutoキーワードが、OSの種類に応じて、自動的に2つのWin32 APIの中から選択することを指定している。Windows 9X系ではANSI系を、Windows NT系ではUnicode系のAPIが呼ばれることになると思う。引数の変換に指定したUnmanagedType.LPTStrという値は、状況に応じて適切な文字コードを選択するという機能を持つ。しかし、自動判定に頼らず、明示的に指定することもできる。以下は、明示的にUnicode系のWin32 APIを選ぶように記述したソースである。

 1: Imports System.Runtime.InteropServices
 2:
 3: Public Class Form1
 4:   Inherits System.Windows.Forms.Form
 5:
 6: …Windows フォーム デザイナで生成されたコード…
 7:
 8:   Declare Auto Sub MessageBoxW Lib "user32.dll" ( _
 9:     ByVal hWnd As Integer, _
10:     <MarshalAs(UnmanagedType.LPWStr)> ByVal lpText As String, _
11:     <MarshalAs(UnmanagedType.LPWStr)> ByVal lpCaption As String, _
12:     ByVal uType As Integer)
13:
14:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
15:     MessageBoxW(0, "Hello!", "Sample Program", 0)
16:   End Sub
17: End Class
リスト14-11 Unicode系のWin32 APIを呼び出すように図14-9を修正したプログラム

 ソース・コードの変更点は2つある。1つは、8行目のメソッド名をMessageBoxからMessageBoxWに変更し、Unicode系のMessageBox APIの本当の名前を指定したことだ。もう1つは、10〜11行目のMarshalAs属性で、Unicode文字列を意味するUnmanagedType.LPWStrという値を指定したことである。なお、いうまでもなく、メソッドの名前はDeclare文のAliasキーワードを活用すれば変更可能なので、MessageBoxという名前で呼び出すようにもできる。

 逆に、ANSI系(日本ではシフトJIS)を用いて文字列を受け渡すように変更したサンプル・プログラムをリスト14-12に示す。

 1: Imports System.Runtime.InteropServices
 2:
 3: Public Class Form1
 4:   Inherits System.Windows.Forms.Form
 5:
 6: …Windows フォーム デザイナで生成されたコード…
 7:
 8:   Declare Auto Sub MessageBoxA Lib "user32.dll" ( _
 9:     ByVal hWnd As Integer, _
10:     <MarshalAs(UnmanagedType.LPStr)> ByVal lpText As String, _
11:     <MarshalAs(UnmanagedType.LPStr)> ByVal lpCaption As String, _
12:     ByVal uType As Integer)
13:
14:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
15:     MessageBoxA(0, "Hello!", "Sample Program", 0)
16:   End Sub
17: End Class
リスト14-12 ANSI系のWin32 APIを呼び出すように図14-9を修正したプログラム

 ここでは、Win32 APIの名前はANSI系のWin32 APIとしての本名であるMessageBoxAに変更し、変換するデータ型をUnmanagedType.LPStrとしている。UnmanagedType.LPStrは、ANSI系の文字列を示す値である。

条件付きメソッド

 メソッドを呼び出す際、呼び出されるメソッドにSystem.Diagnostics.ConditionalAttribute属性が設定されている場合、それは条件付きメソッドと呼ばれるものになる。リスト14-13は、条件付きメソッドを使用したサンプル・プログラムである。

 1: Public Class Form1
 2:   Inherits System.Windows.Forms.Form
 3:
 4: …Windows フォーム デザイナで生成されたコード…
 5:
 6:   <Conditional("Debug")> Private Sub sample()
 7:     Trace.WriteLine("Hello")
 8:   End Sub
 9:
10:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
11:     Trace.WriteLine("call sample")
12:     sample()
13:     Trace.WriteLine("sample done")
14:   End Sub
15: End Class
リスト14-13 条件付きメソッドを使用したプログラム

 これをDebugビルドで実行すると以下のような結果になる。

1: call sample
2: Hello
3: sample done
リスト14-14 リスト14-13をDebugビルドで実行した場合の実行結果

 また、Releaseビルドで実行すると以下のような結果になる。

1: call sample
2: sample done
リスト14-15 リスト14-13をReleaseビルドで実行した場合の実行結果

 見てのとおり、ビルドの設定によって結果が異なっていることが分かるだろう。その点で条件付きコンパイルと似ているが、メソッド単位で効力を発揮するという点で異なるものである。

 メソッド呼び出しが可能かどうかの判断は、System.Diagnostics.ConditionalAttribute属性の引数に指定された条件付き定数(条件付きコンパイル定数)によって判断される。このあたりは、条件付きコンパイルと同様である(条件付きコンパイルを参照)。


 INDEX
  [連載] 改訂版 プロフェッショナルVB.NETプログラミング
  Chapter 14 属性
    1.属性とは何か?/固定長配列を指定するVBFixedArray属性/オブジェクトのシリアライズ
  2.属性の名前のバリエーション/Declare文の引数型を変換するMarshalAs属性/条件付きメソッド
    3.属性を自作する/クラスに付く属性/複数の属性を持たせる
    4.名前付き引数/Webサービスで使われる属性の概要
 
「改訂版 プロフェッショナル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