C#入門 第2回 ネームスペースとクラス

C#プログラムの構造

 まず最初に,C#プログラムの全体的な構造について説明しよう。C#ではネームスペースやクラスという大きな構造があり,欲しい機能を呼び出すためには,これを理解することは避けて通れない。クラスのような機能を意識する必要のないプログラム言語を使いっていた方々には面倒なことに思えるかも知れないが,もちろん,無意味に面倒を増やすために,このような機能があるわけではない。ちゃんと,存在することにはそれなりの理由があり,使いこなせばメリットが生じるものである。

 おおまかに言うと,C#プログラムは以下のような構造になる。

 (fig001.ppt C#プログラムの構造の図を挿入)

 つまり,ネームスペースという大きな箱があり,その中にクラスが入る。クラスの中には,メソッドと変数を入れることができる。メソッドの中にも変数を持つことができる。メソッドとは,C言語でいえば関数,BASICなら手続きや関数に相当するもので,この中に実行するプログラムの実体を書き込むことになる。クラスとは,オブジェクト指向でいうクラスである。詳しい話は後で述べるが,今は,メソッドや変数の入れ物として機能すると思って頂きたい。ネームスペースというのは,オブジェクト指向を扱うプログラム言語の経験者でも耳慣れない用語かもしれない。C++などの既存の言語でもネームスペースの機能を持つ場合もあるが,特に使わないことも多いためだ。だが,C#ではネームスペースを意識的に活用する必要が生じる。

 さて,能書きは横に置くとして,実際にこの構造がどのように記述され,機能するかを見てみよう。

Hello Worldの仕組み

 まず最初に,前回のプログラムがどういう仕組みになっているかを説明しよう。前回のプログラムのソースコードは以下のような内容であった。

    1: namespace ConsoleApplication1
    2: {
    3:     using System;
    4: 
    5:     /// <summary>
    6:     ///    Summary description for Class1.
    7:     /// </summary>
    8:     public class Class1
    9:     {
   10:         public Class1()
   11:         {
   12:             //
   13:             // TODO: Add Constructor Logic here
   14:             //
   15:         }
   16: 
   17:         public static int Main(string[] args)
   18:         {
   19:             Console.WriteLine("Hello World!");
   20:             return 0;
   21:         }
   22:     }
   23: }

 まず,1行目のnamespaceというキーワードに注目して頂きたい。これはネームスペースを宣言するキーワードである。ここでは,ConsoleApplication1という名前のネームスペースを宣言している。そして,ネームスペースConsoleApplication1の範囲はというと,その後の中括弧({})で囲まれた範囲と言うことになる。つまり2行目の開き中括弧から,23行目の閉じ中括弧までが,ネームスペースConsoleApplication1に属すると言うわけである。

 さて,3行目から7行目までは取り敢えず横に置き,8行目を注目して頂きたい。この行のclassというキーワードがクラスを宣言している。ここではClass1という名前のクラスを宣言している。クラスの範囲は,ネームスペースと同様,その後ろの中括弧が決める。つまり,クラスClass1の範囲は9行目から22行目までである。classキーワードの前に付いているpublicとは,プログラムのどこからでもこのクラスを利用できるという意志を示すために付けるキーワードである。取り敢えず最初はpublicだけ覚えておけばプログラムを書けるが,C#を深く知るようになれば他のキーワードを使ってアクセスを制限することもできる。

 10行目から16行目は,このサンプルプログラムでは特に何も機能しないコードなので,横に置こう。17行目は,メソッドの宣言である。この1行は,プログラムの実行が始まったとき,最初に実行されるメソッドを宣言するお約束である。メソッドを宣言するにあたって,namespaceやclassのようにメソッドであることを示すキーワードは特に存在しない。ごく大ざっぱに言えば,メソッド名の後ろに括弧を付けた宣言はメソッドの宣言と解釈される。ここでは,Mainがメソッドの名前で,その後ろにある括弧がMainがメソッドであることを示している。括弧の内部は,メソッドの引数だが,これは今回は利用していないので解説は割愛しよう。Mainの手前にあるpublic static intというキーワードが並んでいるが,それぞれ意味がある。publicは,別のクラスから呼び出すことができるという意味である。staticとはオブジェクトを生成しなくても呼び出し可能であることを示すが,これはオブジェクト指向とも関係することなので,次回以降に詳しく説明しよう。ここではメソッドにはstaticを付けると思っておいてほしい。intとは整数型で,Mainメソッドの戻り値のデータ型を示す。ここでは,値を返す機能を利用していないのだが,Mainメソッドを書くときのお約束なので,整数型を戻すとしている。メソッドの範囲は,namespaceなどと同じく,後ろの中括弧の範囲である。ここでは18行目から21行目までがそれに該当する。

 さて,ここまでは,ネームスペースやクラスやメソッドを宣言する構文であった。ここからは,逆に,既にあるネームスペースやクラスやメソッドを利用する構文に関する話になる。19行目のConsole.WriteLine("Hello World!");という部分は,ConsoleというクラスのWriteLineというメソッドを呼び出すという機能を記述している。このメソッドはコンソールに引数の文字列を表示するという機能を持つ。だが,ここで,「おや?」と思わないだろうか。このソースコードの中には,ConsoleというクラスもWriteLineというメソッドも宣言されていない。実は,このクラスは,Systemというネームスペースの中に存在する。Systemネームスペースは,標準のクラスライブラリの中に含まれているもので,必ずあるものと仮定して利用することができる。しかし,クラスライブラリには,膨大なネームスペースがあり,どのネームスペース内のクラスでも自由に利用可能にはできない。同じ名前のクラスが異なるネームスペースにあったりするからだ。

 そこで意味を持ってくるのが3行目である。usingとは指定されたネームスペースを利用するという宣言である。ここではSystemというキーワードを続けて記述することで,Systemネームスペースを利用すると宣言している。そして,終わりの印として最後にセミコロン(;)記号を必ず書き込む。usingは2行目から23行目までの中括弧の範囲内に記述されているので,この範囲内からSystemネームスペースを利用するという意志を示したものと言える。当然,19行目のConsole.WriteLine("Hello World!");はその範囲内なので,usingの指定が作用する。Consoleクラスはここには宣言されていないので,using指定されたSystemネームスペースと仮定して処理されるのである。

 この手順は,便利なクラスの宝庫であるクラスライブラリを活用する際に知っておく必要がある。よく頭に入れておこう。

 なお22行目のreturn 0;は今の段階では,Mainメソッドのお約束と言うことにしよう。意味はいずれ解説する。

メソッドのフルネーム

 上記のサンプルプログラムで,コンソールに文字列を出力するために,Console.WriteLine("Hello World!");と記述した。既に述べた通り,Consoleはクラスの名前,WriteLineはメソッドの名前で,Systemネームスペースに属していることが暗黙のうちに指定されている。しかし,暗黙で指定するだけでなく,明示的に指定することもできる。たとえば,この例の場合,Console.WriteLineのフルネームは,System.Console.WriteLineと書くことができる。このように書いても,メソッドを呼び出すことができる。

 逆に,同じクラス内のメソッドを呼び出す場合には,ネームスペースだけでなく,クラスも省略して書くことができる。

 これらの関係を実際に体験するために,以下のようなサンプルソースを用意してみた。Visual Stduio.NETで試す場合は,前回のHello World!(もちろん,1番目の方のサンプル)の作成手順と同じ手順でプロジェクトを作成後に,ソースコードとして以下のテキストを入力する。もちろん行番号を入力する必要はない。COPY&PASTEする場合は行番号を削除して頂きたい。

    1: using System;
    2: 
    3: namespace Namespace1
    4: {
    5:     public class Class1
    6:     {
    7:         public static void hello() 
    8:         {
    9:             Console.WriteLine("Namespace1のClass1のhello()");
   10:         }
   11:         public static void test() 
   12:         {
   13:             hello();
   14:             Class1.hello();
   15:             Namespace1.Class1.hello();
   16:             Namespace2.Class1.hello();
   17:         }
   18:         public static int Main(string[] args)
   19:         {
   20:             Namespace1.Class1.test();
   21:             Namespace2.Class1.test();
   22:             return 0;
   23:         }
   24:     }
   25: }
   26: namespace Namespace2
   27: {
   28:     public class Class1
   29:     {
   30:         public static void hello() 
   31:         {
   32:             Console.WriteLine("Namespace2のClass1のhello()");
   33:         }
   34:         public static void test() 
   35:         {
   36:             hello();
   37:             Class1.hello();
   38:             Namespace1.Class1.hello();
   39:             Namespace2.Class1.hello();
   40:         }
   41:     }
   42: }

 このサンプルソースのミソは,2つのネームスペースの中に,それぞれ同じ名前のクラス,同じ名前のメソッドが書き込まれていることである。ネームスペースの名前はそれぞれNamespace1とNamespace2と異なっているが,その他はみな同じである。ただ,Namespace1のクラスClass1にのみMainメソッドがあるが,Mainメソッドは最初に実行されるメソッドという条件から1プログラムに2個入れるわけには行かないので,片方にのみ記述されている。

 さて,このプログラムで特に注目すべき点は11行目からのtestメソッドと,34行目からのtestメソッドである。見て分かるとおり,2つのメソッドは,書かれた場所が異なるネームスペースという以外,まったく同じである。その内容はというと,最初にあるtest()は,ネームスペースもクラスも省略したメソッド呼び出し。次のClass1.hello();は,クラスは指定しているが,ネームスペースは省略した書き方。残りの2つは,フルネームで記述しているが,指定するネームスペースの名前が異なっている。

 呼び出される側のメソッドは,7行目と,30行目に用意している。どちらが呼び出されたのかが分かるように,それぞれメッセージを出力するようになっている。なお,これらのメソッド宣言で使われているvoidは,値を返さないメソッドであることを示すキーワードである。

 さて,実行して試す前に,ちょっとだけ考えて見て頂きたい。それぞれのメソッド呼び出しは,どちらのメソッドを呼び出すだろうか?

 return 0;の行にブレークポイントを仕掛けて実行してみると,結果は以下のようになる。

 この結果を要約すれば,同じ名前のクラスやメソッドがあった場合,何も書かずに省略すると同じネームスペースやクラスの属するものが優先的に呼び出されるが,ネームスペースからフルネームで指定すると,同じ名前のクラスやメソッドがあっても,それぞれきちんと呼び出すことができる,ということになる。

 つまり,たまたま自分のプログラムの中でConsoleクラスやWriteLineメソッドを作ってしまった場合でも,フルネームでSystem.Console.WriteLineと書けば自作のメソッドではなく,間違いなくコンソール出力の機能を呼び出せると言うわけである。

まとめ

 ネームスペースやクラスによってメソッドを細かく分離していく手法は,プログラムの規模が大きいときに非常に役に立つ。混乱を解決し,物事を分かりやすく整理する効果がある。しかし,小さいプログラムしか書かない場合でも,利用できるクラスライブラリは膨大である。それをうまくかき分けて利用するために,ネームスペースやクラスの機能は非常に有益である。

 だが,クラスの持つ機能は,単なるメソッドの分類だけではない。次回は,オブジェクト指向的にクラスを活用する方法を解説しよう。C#はオブジェクト指向の基本を知っていなければ使いこなせない言語である。オブジェクト指向を知らない読者はぜひこの機会に,魅惑のオブジェクトの世界に足を踏み入れて欲しい。オブジェクト指向をよく知っている読者には退屈かも知れないが,実はC#はオブジェクト指向と言うよりもコンポーネント指向と呼ばれており,古いオブジェクト指向とはやや傾向が異なっている面もある。その種の話題にも触れていきたいと考えている。

 それでは次回もLet's See Sharp!