連載

C#入門 最終回

第22回 落ち穂拾い

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


 この連載は、C#の言語仕様を中心に解説するものだが、基本的に入門連載であるため、必ずしもすべての言語仕様を網羅的に説明してきたわけではない。連載の中で、知っておく価値があるにもかかわらず、原稿から取りこぼしたトピックがいくつかある。今回はそれを取り上げて最終回としたい。

コンポーネントという言葉の意味

 初期には用語の錯綜もあったので、あらためていくつかの用語を説明したい。プログラムの部品を組み合わせてソフトウェアを作り出す方法を、コンポーネント指向と紹介したが、コンポーネントという言葉には厳密な意味が与えられており、必ずしも「プログラムの部品」=「コンポーネント」というわけではない。プログラムの部品として使われるものには、クラス(Class)、コンポーネント(Component)、コントロール(Control)などの言葉もある。

 C#において、クラスはいうまでもなく、classキーワードで宣言するものである。これに関しては迷うことはないだろう。では、コンポーネントとは何を意味する言葉だろうか。一般用語としては、再利用が可能な、ほかのオブジェクトとインタラクトするオブジェクトということになるが、C#では(より正確には.NET Frameworkでは)、System.ComponentModel.IComponentインターフェイスを直接または間接的に実装したクラスということになる。このインターフェイスは、サイトに関する機能を実装するために使用される。サイトとは、コンポーネントと、その入れ物となるコンテナの関係を表現するために使用される機能である。コンテナはSystem.ComponentModel.IContainerインターフェイスを実装したクラス、サイトはSystem.ComponentModel.ISiteインターフェイスを実装したクラスとなる。

 では、Visual Basicプログラマならおなじみのコントロールはどのような意味を持っているのだろうか。言葉の意味としては、ユーザー・インターフェイスを持ったコンポーネントがコントロールということになる。.NET Frameworkには、具体的に2種類のコントロールが存在する。1つは、クライアント側のWindows Formsで使用されるもので、System.Windows.Forms.Controlを直接または間接的に継承したクラスがこれに当たる。もう1つは、サーバ側のASP .NETで使用されるもので、System.Web.UI.Controlを直接または間接的に継承したクラスがこれに当たる。すべてのコントロールはコンポーネントでもあるが、すべてのコンポーネントがコントロールというわけではない。

 このように考えると、コンポーネント指向とオブジェクト指向は、互いに相反する別個の方法論ではない。少なくとも.NET Frameworkのコンポーネントは、オブジェクト指向の考え方のうえで構築するようにデザインされたものであり、C#もオブジェクト指向言語の1つであるという事実も変わらない。むしろ、コンポーネント指向は、オブジェクト指向という大きなカテゴリの中にある、1つの方法論と考えた方が分かりやすいだろう。実際に、オブジェクト指向とひと言でいっても、それに含まれる方法論や流儀は多岐にわたり、その中には互いに相反するものさえある。例えば継承しなければオブジェクト指向とはいえないとする流儀もあれば、継承は少ない方がよいとする流儀もある。そのような広大なオブジェクト指向という世界の中の一部がコンポーネント指向と考えることができれば、それが最も分かりやすい理解ではないだろうか?

メソッド引数のref、outキーワード

 メソッドの引数には、“ref”および“out”というキーワードを付加することができる。それぞれ、参照引数、出力引数を意味する。何もせず、通常の方法で引数を記述すると、引数は参照ではなく値を渡す。たとえ引数に指定されたものが参照型の変数であっても、明示的に指定しない限り、参照型の変数への参照が渡されるわけではなく、変数に格納された参照情報の値が渡されるだけである。その結果、引数に変数名を書いたとしても、その変数の値を書き換えることはない。つまり、

object a = A;
instance.method( a );

のようにソースを書いたとき、インスタンスAへの参照が渡るだけであり、変数aへの参照は渡らないということである。その結果、変数aを書き換えるようなメソッド“method”は記述できない。しかし、refやoutキーワードを用いれば、引数に記述された変数を書き換えるようなメソッドを記述することが可能となる。

 以下は、ref、outキーワードを用いたメソッドを記述した例である。主要なエラーや警告が出る行はコメント・アウトしてある。

 1: using System;
 2:
 3: namespace ConsoleApplication1
 4: {
 5:   class Class1
 6:   {
 7:     private static void sample1( int x )
 8:     {
 9:       x ++;
10:     }
11:     private static void sample2( ref int x )
12:     {
13:       x ++;
14:     }
15:     private static void sample3( out int x )
16:     {
17:       // x ++;  // out パラメータ 'x' はコントロールが現在のメソッドを抜ける前に割り当てられる必要があります。
18:       x = 123;
19:     }
20:     static void Main(string[] args)
21:     {
22:       int a = 0, b;
23:       sample1(a);
24:       //sample1(b);   // 未割り当てのローカル変数 'b' が使用されました。
25:       sample1(1);
26:       Console.WriteLine(a);
27:       //Console.WriteLine(b); // 未割り当てのローカル変数 'b' が使用されました。
28:
29:       int c = 0, d;
30:       sample2(ref c);
31:       //sample2(ref d);   // 未割り当てのローカル変数 'd' が使用されました。
32:       //sample2(ref 1);   // ref または out 引数は左辺値でなければなりません。
33:       Console.WriteLine(c);
34:       //Console.WriteLine(d); // 未割り当てのローカル変数 'd' が使用されました。
35:
36:       int e = 0, f;
37:       sample3(out e);
38:       sample3(out f);
39:       //sample3(out 1);   // ref または out 引数は左辺値でなければなりません。
40:       Console.WriteLine(e);
41:       Console.WriteLine(f);
42:     }
43:   }
44: }
refおよびoutキーワードを用いたサンプル・プログラム1
主要なエラーや警告が出る行はコメント・アウトしている。a、c、e、fはどのような値になるだろうか。

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

サンプル・プログラム1の実行結果
refあるいはoutキーワードを用いることにより、呼び出し元の変数の値を操作することができる。

 7〜10行目のメソッドは、通常の引数を用いたメソッドである。11〜14行目は、refキーワードを用いた引数を持つメソッドである。15〜19行目はoutキーワードを用いた引数を持つメソッドである。それぞれ、引数のデータ型(int)の手前に、refやoutキーワードが付加されていることが分かるだろう。

 さて、Mainメソッドの中で、22〜27行目の挙動についてはあらためて説明する必要はないだろう。9行目で引数に1を足しているが、その計算は一切Mainメソッドの変数には反映されていない。問題は、29行目以降の挙動だ。30行目では、refキーワードを用いたメソッドに、変数cを渡している。実行結果画面を見ると分かるとおり、33行目で出力されている値は1であり、13行目で1を足した結果が変数cにも反映されていることが分かるだろう。13行目でxとなっている部分は、実際には変数cへの参照が格納されており、これに1を足すということは、変数cに1を足すことになるのである。

 注目すべき点が、ほかに2つある。1つは、31行目のように、初期化していない変数を渡そうとすると警告されることである。つまり、refキーワードは、何かの値の入った変数を渡すための機能なのである。もう1つは、32行目のように、変数ではなく数値を渡そうとするとエラーになることである。変数には、具体的な格納場所が存在するので参照することができるが、定数値にはそのような特定の場所がないので、参照はできない。なお、Visual Basicでは、ByRefキーワードが、C#のrefキーワードと似た効能を持つが、ByRefキーワードの付いた引数では定数値を渡すことができるので動作が異なる。もちろん、定数値を渡した場合、メソッドが変更した値を呼び出し側で得ることはできない。そのため、このような場合は値渡し(ByValキーワード)で十分である。そのような事情から、C#では、refキーワードで定数値を渡す必要はないと判断され、定数値渡しの機能はサポートされていないものと思われる。Visual Basicプログラマは注意しよう。

 次はoutキーワードの例である。17行目のように、outキーワードでは、引数の値を利用することはできない。outキーワードを指定した引数には、値を代入することはできるが、もともと何かの値が入っていると期待して、それを使うことはできない。このことは、36行目以降を見ても明らかだろう。ほかの例では、初期化していない変数を渡すことはできないが、outキーワードを使った場合に限っては、何の値も入れていない変数を渡しても問題はない。つまり、outキーワードはrefキーワードと似ているが、メソッドにより変数に何かの値を入れてもらう用途専用だといえる。この点で、outキーワードとrefキーワードは挙動が異なる。両者の特性をよく理解して使い分けなければならない。

 最後に補足するが、refキーワードとoutキーワードを積極的に使うべきかというと、必ずしもそうではない。例えば、座標計算などを行うメソッドを作成していると、2つの値を返すメソッドが作りたくなる場合もあるだろう。しかし、座標値ならSystem.Drawing.Pointというstructがあり、これを戻り値にすれば、結果的に2つの値を1つの戻り値で返すことができる。このような相互に関連性が深い値は、クラスやstructなどにまとめて、戻り値として戻すことができるので、refキーワードやoutキーワードの出番はそれほど多くはないかもしれない。逆にいえば、意味のあるデータの集まりは、クラスやstructなどにまとめて扱った方が、ソース・コードを読みやすくするうえでも効果があるので、refキーワードやoutキーワードを使う前に、クラスやstructを作ることで回避できないか検討してみる価値はあるだろう。

 

 INDEX
  第22回 落ち穂拾い
  1.コンポーネントという言葉の意味
    2.ユーザー定義のデータ型変換
    3.演算子を定義する
    4.constとreadonly
    5.シールされたクラス
 
「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 記事ランキング

本日 月間