連載

C#入門

第20回 実行時に参照可能な属性

(株)ピーデー
川俣 晶
2002/01/23


廃止予定を事前に告げるObsolete属性

 C#言語仕様書では、Conditional属性、AttributeUsage属性と並んで、もう1つ「Obsolete」という名前の属性が標準で予約されている。これは、古くなって廃止予定の機能に付加するもので、将来なくなるので使わない方がよいと告げるために使用される。プログラムを開発中に、互換性のためにやむを得ず残しているものの、もう新規には使ってほしくない機能というものが生じることがある。そのような状況に対してエレガントに対処するために積極的に使用するとよいだろう。以下はObsolete属性を使用した例である。

 1: using System;
 2:
 3: namespace ConsoleApplication21
 4: {
 5:   [Obsolete("Sample1は今後サポートされません。Sample2を使ってください。")]
 6:   class Sample1
 7:   {
 8:   }
 9:   class Sample2
10:   {
11:   }
12:   class Class1
13:   {
14:     static void Main(string[] args)
15:     {
16:       Console.WriteLine( new Sample1() );
17:     }
18:   }
19: }
Obsolete属性を使用したサンプル・プログラム7
この属性は古くなって廃止予定の機能に付けるためのものだ。

 これをビルドすると以下のようになる。

 1: ------ ビルド開始 : プロジェクト : ConsoleApplication21, 構成 : Debug .NET ------
 2:
 3: リソースを準備しています...
 4: 参照を更新しています...
 5: メイン コンパイルを実行しています...
 6: f:\w\test3\consoleapplication21\class1.cs(16,27): warning CS0618: 'ConsoleApplication21.Sample1' は古い形式です : 'Sample1は今後サポートされません。Sample2を使ってください。'
 7:
 8: ビルドの完了 -- エラー 0、警告 1
 9: サテライト アセンブリをビルドしています...
10:
11:
12:
13: ---------------------- 終了 ----------------------
14:
15:   ビルド : 1 正常終了、0 失敗、0 スキップ
サンプル・プログラム7のビルド結果
Obsolete属性の付いたクラスを使おうとすると、ビルド時に警告される。

 ソースの5行目に記述されているのがObsolete属性である。引数には文字列を指定する。引数に指定された文字列が、ビルド結果に表示される。ビルド結果の6行目の後半は、Obsolete属性の引数に記述した内容がそのまま表示されたものだ。

 プリプロセッサの「#warning」でも同じような機能が実現できそうに思えるが、動作は同じではない。#warningは処理されれば必ず警告を発するが、Obsolete属性はその属性が付いた機能が利用されない限りメッセージは表示されない。ソース上にあっても利用しなければ警告されないので、互換のために残しながら使うのに適している。

Win32 APIの呼び出し

 これと次のサンプル・ソースはAPIやDLL呼び出しに興味のある読者向けの話題である。すべての読者が理解する必要はない。

 C#では、Win32 APIなどの外部のDLLを呼び出すためにも属性が使われる。Win32 APIだけでなく、COMオブジェクトの呼び出しにも使用できるが、Visual Studio .NETにはいちいち手動で属性を書かなくても簡単に呼び出す方法があるので、解説は割愛する。

 まずは、シンブルに整数1つだけを引数に取るAPIを呼び出してみよう。

 1: using System;
 2: using System.Runtime.InteropServices;
 3:
 4: namespace ConsoleApplication22
 5: {
 6:   class Class1
 7:   {
 8:     [DllImport("user32.dll")]
 9:     public static extern int MessageBeep( uint uType );
10:     static void Main(string[] args)
11:     {
12:       MessageBeep(0);
13:     }
14:   }
15: }
Win32 APIを呼び出しているサンプル・プログラム8
Win32 APIをC#のプログラムから呼び出すには「DllImport属性」を利用する。

 これを実行するとスピーカーから音が鳴るはずである。

 ここでポイントになるのは、もちろん、8行目の「DllImport」属性である。この属性は、externキーワードの付いたメソッド宣言(9行目)に付く。externキーワードは、処理の実体が外部にあることを示すキーワードである。8〜9行目の指定により、外部にあるメソッドを呼び出す指定がなされている。具体的に、どこにあるものを呼び出しているかというと、DllImport属性の引数で指定された“user32.dll”というシステムDLLである。その中にある“MessageBeep”という関数が呼び出される。

 このように引数が整数に限られれば、API呼び出しもさほど難しいものではない。.NET Frameworkは機能が充実しているので、滅多にAPIを呼び出す必要には迫られないが、まれに存在しない機能に遭遇することがあるので、知っておく価値はあるだろう。

 なおこれは、ポインタなどが使用できるunsafeなコードとは異なるものであることに注意が必要である。このDLL呼び出しは、システムによるメモリ管理下で動作するものである。

込み入ったAPI呼び出し

 Visual BasicからAPI呼び出しを利用したことがある経験者なら、文字列などを受け渡すようになると、急に事態がややこしくなる状況に遭遇したことがあるだろう。これは、文字列の表現方法がプログラム言語によって異なっていたり、文字コードにバリエーションがあったりすることにより発生する。これらの問題に対処するために、DllImport属性には多くの名前付き引数が用意されている。以下はそれを駆使して、ANSIバージョンのMessageBox APIと、UnicodeバージョンのMessageBox APIを呼び出してみた例である。

 1: using System;
 2: using System.Runtime.InteropServices;
 3:
 4: namespace ConsoleApplication23
 5: {
 6:   class Class1
 7:   {
 8:     [DllImport("user32.dll", CallingConvention=CallingConvention.Winapi,
 9:        CharSet=CharSet.Ansi, EntryPoint="MessageBoxA",
10:        ExactSpelling=false, PreserveSig=true, SetLastError=false )]
11:     public static extern int MyMessageBox1(
12:       uint hWnd,
13:       [MarshalAs(UnmanagedType.LPStr)] string lpText,
14:       [MarshalAs(UnmanagedType.LPStr)] string lpCaption,
15:       uint uType
16:     );
17:
18:     [DllImport("user32.dll", CallingConvention=CallingConvention.Winapi,
19:        CharSet=CharSet.Unicode, EntryPoint="MessageBoxW",
20:        ExactSpelling=false, PreserveSig=true, SetLastError=false )]
21:     public static extern int MyMessageBox2(
22:       uint hWnd,
23:       [MarshalAs(UnmanagedType.LPWStr)] string lpText,
24:       [MarshalAs(UnmanagedType.LPWStr)] string lpCaption,
25:       uint uType
26:       );
27:     static void Main(string[] args)
28:     {
29:       MyMessageBox1(0,"by ANSI","Hello!",0);
30:       MyMessageBox2(0,"by Unicode","Hello!",0);
31:     }
32:   }
33: }
MessageBox APIを呼び出すサンプル・プログラム8
ANSIバージョンおよびUnicodeバージョンのMessageBox APIを呼び出している。属性に指定する引数が少し複雑になっている。

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

サンプル・プログラム8の実行結果
ANSI文字列とUnicode文字列を表示する2つのメッセージ・ボックスが開く。

 DllImport属性に付いている名前付き属性は、それぞれ、以下のような意味を持つ。「CallingConvention」は呼び出し方法(stdcallなど)の種類を指定する。「CharSet」はANSIかUnicodeかを指定する。「EntryPoint」はAPIの真の名前を指定する。ここで呼び出したいAPIは、ANSIバージョンが“MesasgeBoxA”、Unicodeバージョンが“MessageBoxW”である。「ExactSpelling」は名前の文字列が完全に一致する場合のみ利用可能とするかどうかを指定する。「PreserveSig」はメソッドのシグネチャを保存することを指定する。「SetLasrError」は、そのAPIがエラーコードをSetLasrError APIで設定するかどうかを指定する。これらにより、異なる文字コードを使用する2つのMessageBox APIを別個に呼び出すようにコードを記述している。

 このサンプル・ソースでは、文字列の変換も明示的に指定してみた。13〜14行目、23〜24行目の「MarshalAs属性」は、引数に付く属性である。これはC#ソース側からstring型の引数に見える文字列を、実際の呼び出し時にどんなデータ型に変換するかを明示的に指定するものである。引数のUnmanagedType.LPStrはWin32 APIの「LPSTR」を、UnmanagedType.LPWStrは「LPWSTR」をそれぞれ意味する。


 INDEX
  第20回 実行時に参照可能な属性
    1.属性(アトリビュート)とは何か?
    2.クラスに付く属性
    3.名前付き引数
  4.廃止予定を事前に告げるObsolete属性
    5.Webサービスで使われる属性
 
「C#入門」


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メールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間