Microsoft .NET のすべて
入門 C#
C++ のパワーと Visual Basic の簡潔性を兼ね備えた新言語 C#
Sharp New Language: C# Offers the Power of C++ and Simplicity of Visual Basic
(September 2000 Vol.15 No.9)
Joshua Trupin
Joshua Trupinは、『MSDN Magazine』のテクニカルエディタ。『MSDN Magazine』の前身である『MSJ』と『MIND』に多数の記事を執筆しているほか、『Hoop Stats: The Basketball Abstract』という著書がある。
この記事は、C++ と COM+ の知識があることを前提に書かれています。
関連する記事については、MSDN Magazine 日本語版 No.2「Visual Basicの機能:次世代向けの Web Form、Webサービス、および言語拡張」 (原著テキストは、The Future of Visual Basic: Web Forms, Web Services, and Language Enhancements Slated for Next Generation)
参考資料については、MSJ 日本語版 No.52「オブジェクト指向ソフトウェア開発を単純化する COM+ ランタイムサービス」 (原著テキストは、http://www.microsoft.com/msj/1197/complus.htm)、MSJ 日本語版 No.53「あらゆる言語でコンポーネントを書きやすくするCOM+ プログラミングモデル」 (原著テキストは、http://www.microsoft.com/msj/1297/complus2/complus2.htm)
このアーティクルは、株式会社アスキー
より 2000 年 9 月 18 日に発刊された MSDN Magazine
No.7 (ISBN4-7561-3574-9) に収録されています。
新しい言語 C# とは、Visual Basic のあらゆる長所を C++ に追加するとともに、少々煩瑣な C/C++ の伝統的な機能の一部をそぎ落としたのもので、C# を使えば、C/C++ プログラマが簡単に COM+ 対応プログラムを設計・作成・メンテナンスできるようになる。
C# には、タイプセーフティ、ガベージコレクション、簡素化された型宣言、バージョニングとスケーラビリティのサポートを始めとする数多くの機能が搭載され、従来よりも短期間で簡単にソリューションを開発できるようになる。
C# は今後、COM+ プログラムと Windows ベースの企業向け業務プログラムを開発するのに最適な Microsoft 製言語となるはずだ。本稿では、C# によってもたらされるプログラマに重要な各メリットについて、C# がどのように読者の日常的なコーディング作業の改善に貢献するのかを解説する。
Summary
書くのも読むのもメンテナンスするのも Visual Basic のように簡単で、なおかつ C++ と同等のパワーや柔軟性を兼ね備えた言語があったらいいのに。それが多くの開発者の願いだろう。そうした開発者のために登場した新しい言語が C# だ。C# には、タイプセーフティ、ガベージコレクション、簡素化された型宣言、バージョニングとスケーラビリティのサポートを始めとする数多くの機能が盛り込まれており、それによって従来よりも短期間で簡単にソリューション (とりわけ COM+ と Web Service) を開発できるようになる。この記事では、間もなく話題沸騰となること必至の言語 C# について、いち早く読者に紹介することにしよう。
最近の業界ニュースで、Microsoft が新しいプログラミング言語を開発中だという記事を目にした人もいるだろう。その言語が C# だ。この新しい言語 C# (「Cシャープ」と読む) を使えば、C/C++ プログラマが簡単に COM+ 対応プログラムを作成できるようになる。しかも C# はまったく新規に設計されており、それによってプログラムの記述とメンテナンスがやりやすくなっている。おおざっぱに言えば、Visual Basic のあらゆる長所を C++ に追加するとともに、少々煩瑣な C/C++ の伝統的な機能の一部をそぎ落としたのが C# だと言えるだろう。
C# は今後、COM+ プログラムと Windows ベースの企業向け業務プログラムを開発するのに最適な Microsoft 製言語となるはずだ。と言っても既存のC/C++ コードを移植する必要はない。読者が C# の新しい機能が気に入るようなら (きっと気に入ると思うが)、C# に転向すればいいだろう。現実を直視しよう。C++ は確かに強力な言語だが、必ずしも公園の散歩のようなお手軽な言語ではない。私は仕事で Visual Basic と C++ の双方を使ってきたが、しばらくすると、一体どうしてありとあらゆる C++ クラスにいちいちデストラクタを実装しなけりゃならないんだと思い始めた。このくらいやってくれよ、賢い言語なんだろ。Visual C++ には IntelliSense なんていうすごいのがあるくらいなんだから、代わりに後始末してくれたってよさそうなものなのに。C や C++ は好きだけれど、ときどき私と同じような思いに駆られる人には、C# はまさにぴったりだ。
C# の第1の設計目標は、純然たるパワーではなく簡潔性に置かれていた。処理能力は多少落ちるものの、その代わりにタイプセーフティや自動ガベージコレクションなどといったすばらしい機能が手に入る。C# によってコードの全体的な安定性と生産性が向上するので、長い目で見ればパワーの低下分を補ってあまりある結果が得られるはずだ。C# によってもたらされるプログラマに重要なメリットには次のようなものがある。
- 簡潔性
- 一貫性
- 今日性
- オブジェクト指向性
- タイプセーフティ
- 拡張性
- バージョンサポート
- 互換性
- 柔軟性
ではこうしたそれぞれの点について、C# がどのように読者の日常的なコーディング作業の改善に貢献するのかを見ていこう。
簡潔性
C++ を使っていて一番うっとうしいのは何だろうか。どこでポインタインジケータ -> を使い、どこでクラスメンバーの :: を使い、どこでドットを使ったらいいのかを区別しなければならないことではないだろうか。プログラマが間違えればコンパイラにはそれが分かる。おまけにコンパイラはプログラマが間違えたことをいちいち指摘してくるのだ。これが容赦ないあざけりでないとしたら何なのだろうか。
C# では、これが C++ プログラミングに根付いている煩瑣な側面だとみなされて単純化された。C# では「すべてのもの」がドットで表現される。自分が注目しているのがメンバーであれクラスであれネームスペースであれ参照であれ何であれ、使用する演算子を気にする必要はない。
では C/C++ を使っていて2番目にうっとうしいのは何だろうか。使用するデータ型を正確に把握しなければならないことだ。C# では Unicode 文字はもう wchar_t ではなく、char になった。64 ビット整数は _int64 ではなく long だし、char は char であって char でしかない。もう char だの unsigned char だの signed char だの wchar_t だのを区別する必要はないのだ。データ型についてはあとでまた取り上げよう。
C/C++ で出くわす 3 番目にうっとうしい問題は、整数がBooleanとして使用されるせいで、= と == を取り違えると代入ミスが発生してしまう点だ。C# ではこの問題が解決されており、この 2 つの型が分離されてブール型が独立している。ブールは真か偽であり、別の型には変換できない。同様に、整数やオブジェクト参照は真や偽に対してはテストできず、ゼロ (参照の場合にはヌル) と比較しなければならない。C++ で今まで次のようなコードを書いていたとしたら、
int i;
if (i) ...
C# ではそれを次のような感じに書き直す必要がある。
int i;
if (i != 0) ...
これ以外のプログラマフレンドリな機能としては、C++ に比べて switch 文の動作が改善されたことがある。C++ の場合には、case から case へ「落下」する次のような switch 文を記述できた。
switch (i)
{
case 1:
FunctionA();
case 2:
FunctionB();
Break;
}
この場合、i が 1 と等しければ FunctionA と FunctionB の両方が呼び出される。一方 C# は Visual Basic 風に動作し、個々の case 文の前に暗黙の break が置かれていると想定される。C# の case 文で本当に上記のような落下を実現したいときには、switch文を次のように書き直せばいい。
switch (i)
{
case 1:
FunctionA();
goto case 2;
case 2:
FunctionB();
Break;
}
一貫性
C# では型の体系が統一されており、言語のすべての型をオブジェクトとみなせるようになっている。つまり、使用するのがクラスであれ構造体であれ配列であれプリミティブであれ、それをオブジェクトとして扱えるのだ。オブジェクトはまとめてネームスペース中に置かれるので、プログラマはプログラム上ですべてのものにアクセスできる。つまり次のように自分のファイルにinclude命令を置くのではなく、
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
次のように特定のネームスペースを自分のプログラムにインクルードすれば、そこに含まれているクラスやオブジェクトにアクセスできるようになる。
using System;
COM+ の場合、すべてのクラスは単一の階層的なネームスペース中に存在している。C# のusing文を使えば、クラスを使うときに完全名を指定しなくて済むようになる。たとえばSystemネームスペースには、Consoleなど何種類かのクラスが含まれている。ConsoleにはWriteLineメソッドが存在するが、これはその名のとおりシステムコンソールに行を書き出すメソッドだ。だから C# でHello Worldのプログラムの出力部を書きたいときには、次のように記述すればいい。
System.Console.WriteLine("Hello World!");
これと同じコードは次のようにも書ける。
using System;
Console.WriteLine("Hello World!");
C# 版Hello Worldプログラムに必要な要素はほぼこれだけだ。ただし完全な C# プログラムにするためには、クラス定義とMain関数が必要になる。C# で記述した完全なコンソールベースのHello Worldプログラムは次のようになる。
using System;
class HelloWorld
{
public static int Main(String[] args)
{
Console.WriteLine("Hello World!");
return 0;
}
}
1行目は、System (COM+ 基底クラスのネームスペース) をプログラムに利用できるようにしている。プログラムクラス自体はHelloWorldと命名されており (コードはファイル単位ではなくクラス単位で分けられる)、HelloWorld内でMainメソッド (引数を取る) が定義されている。そしてCOM+ のConsoleクラスが友好的なメッセージを書き出し、プログラムが終了する。
もちろんもっと発展性のあるプログラムを書くのも可能だ。HelloWorldプログラムを再利用したいときにはどうすればいいのだろうか。簡単だ。独自のネームスペースに入れてしまえばいいのだ。クラスをネームスペースに包んで、そのネームスペースの外部からアクセスできるようにしたければ、クラスをpublicとして宣言するだけでいい (次のコードではMainをもっとふさわしいSayHiという名前に変更してある点に注意)。
using System;
namespace MSDNMag
{
public class HelloWorld
{
public static int SayHi()
{
Console.WriteLine("Hello World!");
return 0;
}
}
}
これでこのコードをDLLにコンパイルできるようになり、自分が開発しているほかのあらゆるプログラムにそのDLLを取り込めるようになる。呼び出し側のプログラムは次のような感じになる。
using System;
using MSDNMag;
class CallingMSDNMag
{
public static void Main(string[] args)
{
HelloWorld.SayHi();
return 0;
}
}
クラスについてもう一言。同じ名前のクラスが複数のネームスペースに存在する場合には、プログラマが完全名を指定しなくても済むように、 C# ではどんなクラスにもエイリアスを定義できるようになっている。例を示そう。次のようなNS1.NS2.ClassAというクラスを作ったとしよう。
namespace NS1.NS2
{
class ClassA {}
}
さらに第2のネームスペースNS3を作って、次のようにNS1.NS2.ClassAからNS3.ClassBというクラスを派生させる。
namespace NS3
{
class ClassB: NS1.NS2.ClassA {}
}
この構文があまりに長ったらしく感じられるときや何度か繰り返し使おうとする場合には、次のような文によってクラスNS1.NS2.ClassAにエイリアスAを使えばいい。
namespace NS3
{
using A = NS1.NS2.ClassA;
class ClassB: A {}
}
エイリアスの効果はオブジェクト階層のどのレベルにでも発揮させられる。だからたとえば次のように、NS1.NS2のエイリアスを作るのも可能だ。
namespace NS3
{
using C = NS1.NS2;
class ClassB: C.A {}
}
今日性
コーディング言語と同様に、プログラマのニーズも年月とともに変わっていく。かつては革命的だったものも、今となってはいくらか古臭く感じられるのもやむを得ないだろう。C/C++ は近所の空き地に置いてある年代物のトヨタカローラのように信頼できる輸送手段だが、人々が求めている機能が少々欠けているせいでタイヤを蹴っ飛ばされるはめになる。ここ数年Javaに手を染める開発者が多かった理由の1つもそこにある。
そこで C# は新規蒔き直しを図り、私がC++ に切望していた何種類かの機能を装備することになった。その一例がガベージコレクションだ。これによってあらゆるオブジェクトは参照されなくなった時点で始末される。ただしガベージコレクションには代償が伴う。ある種の危険な動作 (たとえば安全でないキャストや迷子のポインタの使用など) によって引き起こされる問題をつきとめるのがはるかにたいへんになるし、そうした問題によるプログラムへの被害が増大する危険性もある。この点を補うために C# にはタイプセーフティが実装されており、それによってアプリケーションの安定動作が確保される。タイプセーフティによってコードの読みやすさも当然向上するので、あなたが何をしているのかがチームのほかのメンバーにも分かるようになる (まあこれは良し悪しだと思うけどね)。タイプセーフティについては後述しよう。
C# には、C++ よりも充実したエラー処理用の組み込みモデルが存在する。読者は自分の同僚のコードを綿密に調べなければならないはめになった経験があるだろうか。まさに驚異の世界だよ。大量のHRESULTがチェックされないまま至るところに散らばっているし、呼び出しが失敗してもプログラムはいつも「エラー:エラーが発生しました」というメッセージを表示するだけなのだ。このような状況を改善するために、 C# にはthrowとtry...catchとtry...finallyのサポートが言語の要素として組み込まれている。確かにこれと同じことはC++ でもマクロとして実行できたが、それがネイティブに利用できるようになったのだ。
現代の言語の存在意義は、それを実際に何かに利用できる能力にある。当たり前のことのように思えるが、実際には金融や時間ベースのデータ型に対するニーズを完全に無視してしまっている言語が多くはないだろうか。そうした言語の経済観念はあまりに古臭すぎる。C# の場合には、SQLのような言語にならって小数や文字列などのデータ型のサポートが組み込まれているほか、効率の点で既存の型と変わらない新しいプリミティブ型をプログラマが実装できるようになっている。データ型や配列に関する新しい機能についてはあとで紹介しよう。
またこれも読者に喜んでもらえる話だと思うが、 C# はデバッグに関してよりモダンなアプローチを取っている。C++ でデバッグ可能なプログラムを記述する伝統的な手法は、プログラム中に#ifdefを散りばめて、大きな単位のコードセクションがデバッグプロセスでのみ実行されるように指定するやり方だった。その結果デバッグビルドとリテールビルドという2種類の実装ができ上がることになり、リテールビルド中の呼び出しの一部は何もしない関数を呼び出すことになる。一方 C# には、定義されたトークンに基づいてプログラムのフローを制御できるconditionalキーワードが存在する。
MSDNMagネームスペースを覚えているかな。これにconditional文を1個追加するだけで、SayHiメンバーをデバッグ専用関数に変身させられる。
using System;
namespace MSDNMag
{
public class HelloWorld
{
[conditional("DEBUG")]
public static void SayHi()
{
Console.WriteLine("Hello World!");
return0;
}
...
}
}
条件付き関数の戻り値型は (この例のように) voidでなければならない。一方クライアントプログラムは、Hello Worldメッセージを出力するには次のようになっている必要がある。
using System;
using MSDNMag;
#define DEBUG
class CallingMSDNMag
{
public static void Main(string[] args)
{
HelloWorld.SayHi();
return 0;
}
}
このコードは美しいし、いずれは無視される運命にある#ifdefがあちこちに散らばって乱雑になったりもしていない。
最後に、 C# は簡単に解析できるように設計されているので、ソースブラウジングや双方向コード生成を可能にするツールをベンダが開発できるはずだ。
オブジェクト指向
へぇ、C++ ってオブジェクト指向だったんだ。よろしい。私の個人的な知り合いに、多重継承に1週間取り組んだ挙げ句、挫折してノースカロライナに引っ込んで養豚場の汚水池の掃除をやっている人間がいるよ。C# が多重継承を放棄して、代わりにCOM+ 仮想オブジェクトシステムのネイティブサポートを実現しているのはそのためだ。カプセル化とポリモーフィズムと継承は何の問題もなく残されている。
C# では、グローバルな関数や変数や定数の概念が丸ごと捨てられている。代わりにスタティッククラスメンバーを生成できるため、 C# のコードは読みやすいし名前が衝突する可能性も低くなる。
名前の衝突について言えば、クラスメンバーを作ったのをすっかり忘れて、あとで自分のコード中で再定義してしまった経験はないだろうか。デフォルトでは C# のメソッドは非仮想関数なので、明示的なvirtual修飾子が必要になる。だからメソッドをうっかりオーバーライドしてしまう事態がかなり起こりづらくなるし、正しいバージョニングを実現しやすくなるし、vtableがすぐに大きくなってしまうこともない。C# のクラスメンバーはprivate/protected/public/internalとして定義可能で、プログラマはそれらのカプセル化を完全に制御できる。
C# でもメソッドと演算子をオーバーロードできるようになっているが、その際に使用する構文はC++ の構文よりもはるかに簡単だ。ただしグローバル演算子関数はオーバーロードできない。オーバーロードのスコープはローカルだけに限定されているからだ。以下に示すメソッドFのオーバーロードは C# でのオーバーロードの例だ。
interface ITest
{
void F(); // F()
void F(int x); // F(int)
void F(ref int x); // F(ref int)
void F(out int x); // F(out int)
void F(int x, int y); // F(int, int)
int F(string s); // F(string)
int F(int x); // F(int)
}
COM+ コンポーネントモデルはデリゲートの実装によってサポートされる。デリゲートは言わばC++ の関数ポインタのオブジェクト指向版だ。
インターフェイスは多重継承をサポートしている。クラス中で明示的にメンバーを実装することによって、コンシューマ側にいっさい知られることなくプライベートな内部インターフェイスを実装できる。
タイプセーフティ
異議を唱えるパワーユーザーもいるかもしれないが、タイプセーフティによってプログラムの堅牢性が助長される。C# には、適切なコードの実行 (とプログラムの堅牢性の向上) を助長するVisual Basicの機能がいくつか取り入れられた。たとえば、動的に割り当てられるオブジェクトと配列はすべてゼロに初期化される。C# ではローカル変数は自動的に初期化されないものの、それを初期化する前に使おうとするとコンパイラが警告してくれる。配列にアクセスするときには、範囲チェックが自動的に実行される。CやC++ とは違って、割り当てられていないメモリを上書きすることはできない。
C# では無効な参照を生成できない。あらゆるキャストは安全でなければならず、整数型と参照型の間でのキャストは許されない。C# のガベージコレクション機能によって、宙ぶらりんの参照が自分のコードに残るようなこともない。この機能とともに、さらにオーバーフローチェック機能もある。ターゲットの変数やオブジェクトをオーバーフローさせてしまう結果になる算術演算や算術変換は許されない。もちろん、正当な理由から変数をオーバーフローさせたいこともあるだろう。そういう場合には、このチェック機能を明示的に無効にすればいい。
先ほど少し触れたように、C# でサポートされるデータ型は読者がC++ で使い慣れていたデータ型と若干違っている。たとえばchar型は16ビット長だし、小数型や文字列型のような便利なデータ型も組み込まれている。しかしC++ と C# の最大の違いは、おそらく C# での配列の扱われ方だろう。
C# の配列は管理型 (値ではなく参照を保持する) であり、ガベージコレクションの対象となる。配列は何種類かの方法で宣言でき、たとえば多次元 (矩形) 配列として宣言したり、配列の配列 (多段階配列) として宣言したりできる。次の例で、角かっこが一部の言語のように識別子のあとではなく、型のあとに置かれている点に注意しよう。
int[ ] intArray; // 単純配列
int[ , , ] intArray; // ランク3 (3次元) の多次元配列
int[ ][ ] intArray; // 配列の配列 (多段階配列)
int[ ][ , , ][ , ] intArray; // 2次元配列の3次元配列の1次元配列
配列は実際にはオブジェクトであり、最初に宣言された時点ではサイズを持たない。そのため、プログラマは配列を宣言したあとでそれを生成しなければならない。仮にサイズ5の配列を作りたいのであれば、次のコードを実行すればいい。
int[] intArray = new int[5];
これを2回実行すると、配列が自動的に再割り当てされる結果になる。だから次のコードを実行すれば、
int[] intArray;
intArray = new int[5];
intArray = new int[10];
10個のメンバーを持つintArrayという配列が生成される。矩形配列のインスタンス生成も同様に簡単だ。
int[] intArray = new int[3,4];
ただし多段階配列のインスタンス生成にはもう少し手間がかかる。new int[3][4]などと書けばいいと思うかもしれないが、実際には次のように書かなければならない。
int[][] intArray = new int[3][];
For (int a = 0; a < intArray.Length; a++) {
intArray[a] = new intArray[4];
}
次のように中かっこを使えば、配列を生成してインスタンス化するのと同じ行で配列を初期化できる。
int[] intArray = new int[5] {1, 2, 3, 4, 5};
これと同じことは文字列ベースの配列でも可能だ。
string[] strArray = new string[3] {"MSJ", "MIND", "MSDNMag"};
かっこをネストすれば、多次元配列を初期化できる。
int[,] intArray = new int[3, 2] { {1, 2}, {3, 4}, {5, 6} };
多段階配列も初期化できる。
int[][] intArray = new int[][] { new int[] {2,3,4}, new int [] {5,6,7} };
さらにnew演算子を省略すれば、暗黙のサイズを持つ配列を初期化できる。
int[] intArray = {1, 2, 3, 4, 5};
C# では配列はオブジェクトとみなされるため、アドレス可能なバイトストリームとしてというよりもオブジェクトとして扱われる。とりわけ、配列は自動的にガベージコレクション処理されるため、使い終わった時点で破棄する必要はない。配列は C# クラスSystem.Arrayに基づいているため、概念的にはコレクションオブジェクトのような取り扱いが可能で、Lengthプロパティを使ったり、配列中の個々の項目をループでたどったりできる。先ほどのようにintArrayを定義したとするなら、次の呼び出しは5を返す。
intArray.Length
System.Arrayクラスには、配列のコピーやソートや検索を実行する手段も用意されている。
C# にはVisual BasicのFor Eachのように動作するforeach演算子があり、これを使えば配列中をループでたどれるようになる。次のコードを考えてみよう。
int[] intArray = {2, 4, 6, 8, 10, -2, -3, -4, 8};
foreach (int i in intArray)
{
System.Console.WriteLine(i);
}
このコードは、intArrayの個々のメンバーを1行ずつシステムコンソールに表示する。System.ArrayクラスにはGetLengthメンバー関数も存在するので、上のコードは次のようにも書ける ( C# の配列はゼロベースなので注意)。
for (int i = 0; i < intArray.GetLength(); i++)
{
System.Console.WriteLine(i);
}
拡張性
C や C++ の場合には、非常に単純なコードをコンパイルするときでもありとあらゆるヘッダーファイルが必要で、ヘッダーファイルの互換性のなさに泣かされることも多い。C# では型の宣言と定義を組み合わせることによって、こうした腹立たしいヘッダーが不要になっている。また COM+ のメタデータを直接インポートしたり出力したりできるので、インクリメンタルコンパイルが大幅に簡単になった。
プロジェクトがだいぶ大きくなってくると、自分のコードをもっと小さなソースファイル単位に分割したくなるケースがあるだろう。C# には、ソースファイルの場所や名前に関する制約はいっさいない。C# プロジェクトをコンパイルする場合には、すべてのソースファイルを連結して1個の大きなファイルにコンパイルするのだと考えればいい。だからどのヘッダーがどこにあるのかとか、どのルーチンがどのソースファイルにあるのかなどといったことを気にする必要はないのだ。この点はまた、ソースファイルを移動したり名前を変えたり分割したりマージしたりしても、コンパイル作業に影響しないということでもある。
バージョンサポート
「DLL地獄」は相変わらずユーザーにもプログラマにも頭の痛い問題だ。MSDN Onlineには、システムDLLの各種バージョンを調べたいユーザー専用のサービスまで存在するほどだ。プログラミング言語には、公開されたAPIをライブラリの作者がいじらないように防止する手立ては存在しない。ただし C# は、既存の派生クラスとのバイナリ互換性を維持することによって、バージョニングがはるかに楽になるように設計されている。派生クラスに存在するメンバーと同様の新しいメンバーを基底クラスに導入しても、それによってエラーが生じるわけではない。ただしクラスの設計者は、そのメソッドがオーバーライドなのか、継承される同様のメソッドを隠蔽するにすぎない新しいメソッドなのかを指定しなければならない。
すでに触れたように、 C# はネームスペースモデルに基づいて機能する。クラスライブラリのクラスとインターフェイスは、平面的なモデルではなく階層的なネームスペース中に定義されなければならない。アプリケーションはあるネームスペースのある1個のメンバーを明示的にインポートできるようになっているので、複数のネームスペースに同じような名前のメンバーが存在しても衝突はいっさい発生しない。ネームスペースを宣言すると、それ以降の宣言は同一の宣言空間に属するとみなされる。だから次のようなコードがあるとするなら、
namespace MSDNMag.Article
{
class Author
{
...
}
}
namespace MSDNMag.Article
{
class Topic
{
...
}
}
このコードは次のようにも表現できる。
namespace MSDNMag.Article
{
class Author
{
...
}
class Topic
{
...
}
}
互換性
Windowsプラットフォームで一般的なAPIには4種類のタイプがあるが、 C# はそのすべてをサポートしている。オールドスタイルのC APIに関しては、 C# による統合サポートが存在する。アプリケーションはCOM+ のN/Direct機能を利用することで、CスタイルのAPIを呼び出せるようになっている。また C# はCOMとOLEの標準Automation APIに透過的にアクセスでき、COM+ ランタイムによってすべてのデータ型をサポートしている。そして最も重要な点は、 C# がCOM+ のCommon Language Subset仕様をサポートしていることだ。プログラマがほかの言語からアクセスできないエンティティをエクスポートした場合には、オプションを指定すればコンパイラがそれを検出してくれる。たとえばクラス中にrunJobとrunjobという2つのメンバーを共存させることはできないが、それは大文字と小文字の区別がない言語だとこうした定義に対処できないからだ。
DLLエクスポートを呼び出す場合には、そのメソッドを宣言し、sysimport属性を付与し、COM+ のデフォルトをオーバーライドするカスタムマーシャリング情報や戻り値情報があればそれを指定する必要がある。次のコードは、挨拶メッセージを標準のWindowsメッセージボックスに表示するHello Worldプログラムの書き方を示している。
class HelloWorld
{
[sysimport(dll = "user32.dll")]
public static extern int MessageBoxA(int h, string m,
string c, int type);
public static int Main()
{
return MessageBoxA(0, "Hello World!", "Caption", 0);
}
}
個々の COM+ 型はデフォルトのネイティブデータ型にマップされる。COM+ はそれを利用して、ネイティブ API 呼び出しで受け渡される値をマーシャリングする。C# の文字列値はデフォルトでは LPSTR 型にマップされるが、次のようにマーシャリング文を使えばそれをオーバーライドできる。
using System;
using System.Interop;
class HelloWorld
{
[dllimport("user32.dll")]
public static extern int MessageBoxW(
int h,
[marshal(UnmanagedType.LPWStr)] string m,
[marshal(UnmanagedType.LPWStr)] string c,
int type);
public static int Main()
{
return MessageBoxW(0, "Hello World!", "Caption", 0);
}
}
DLLエクスポートの利用に加えて、クラシックCOMオブジェクトを扱う (CoCreateInstanceで生成し、インターフェイスを問い合わせ、そこにあるメソッドを呼び出す) 方法も何種類かある。
自分のプログラム中で使うためにCOMのクラス定義をインポートしたいときには、2段階のステップを踏む必要がある。まず、クラスを作成して、comimport属性を使ってそれが特定のGUIDに関連したクラスであるとマークしなければならない。作成するクラスには基底クラスやインターフェイスリストがあってはならないし、メンバーもいっさい存在できない。
// FilgraphManagerをCOMクラシックコクラスとして宣言
[comimport, guid("E436EBB3-524F-11CE-9F53-0020AF0BA770")]
class FilgraphManager
{
}
プログラム中でこのクラスを宣言したら、newキーワード (CoCreateInstance関数に相当) によってこれの新しいインスタンスを生成すればいい。
class MainClass
{
public static void Main()
{
FilgraphManager f = new FilgraphManager();
}
}
C# で間接的にインターフェイスを問い合わせるには、オブジェクトを新しいインターフェイスにキャストしてみればいい。キャストが失敗したときにはSystem.InvalidCastExceptionがスローされる。うまくいったときには、そのインターフェイスを表現するオブジェクトが手に入る。
FilgraphManager graphManager = new FilgraphManager();
IMediaControl mc = (IMediaControl) graphManager;
mc.Run(); // キャストが成功すればこの行が機能する
柔軟性
管理されたタイプセーフな環境が C# とCOM+ によってもたらされるのは事実だ。しかし現実のアプリケーションのなかには、性能上の理由から、あるいは別のプログラムが実装しているオールドスタイルの前近代的なAPIを使いたいという理由から、ネイティブコードレベルに降りていかなければならないアプリケーションがあるのもまた事実だ。自分の C# プログラムからAPIやCOMコンポーネントを使う方法についてはすでに紹介した。C# では、ポインタや構造体やスタティック配列を含んでいる安全でないクラスやメソッドを宣言できるようになっている。こうしたメソッドはタイプセーフにはならないものの、管理された空間の内部で実行されるため、安全なコードと安全でないコードの間の境界部分をプログラマが自分でマーシャリングする必要はない。
こうした安全でない機能は、COM+ EEとCOM+ のコードアクセスセキュリティに統合されている。これはつまり、ガベージコレクタが作業する際に無視して通り過ぎるオブジェクト (壁に掲げられた家訓や校則のコード版みたいなものか) を、開発者が指定できるという意味だ。安全でないコードは完全に信頼された環境の外部では実行されない。さらに、安全でないメソッドの実行中はガベージコレクションを無効にしてしまうこともできる。
入手について
C# は6月に発表され、将来リリースされるVisual Studio .NETスイートに付属することになっている。ただしコンパイラは、次世代の Visual Studio のリリースに先立って今年の後半に入手できるようになる見込みだ。