連載:C# 2.0入門第1回 総論:C# 2.0らしいプログラミングとは株式会社ピーデー 川俣 晶2007/06/01 |
|
|
後退するクラスの立場
先のソートの例は、実はC# 1.xとC# 2.0の決定的な差が何であるかを非常によく表現している。
つまり、動作のカスタマイズを行うためにクラスを経由する必要性の多いC# 1.xと、クラスを経由せずメソッドのレベルで動作をカスタマイズできてしまうC# 2.0の差である。いい換えれば、C# 2.0では動作のカスタマイズの主体がクラスではなくメソッドなのである。
C# 1.xの時代、メソッドの動作をカスタマイズするには継承やインターフェイスの実装を行い、メソッド単位で動作を交換することができた。それに対してC# 2.0では、引数に匿名メソッドを気軽に渡すことができるため、単なる普通のメソッドの動作を外部からカスタマイズするコードを容易に書くことができるようになった。
その結果、C# 2.0プログラミングではクラスの立場が大幅に後退し、単なるフィールドやメソッドの入れものとしてしか機能していないことも多くなったような印象も受ける。メソッド単位で動作がカスタマイズできる以上、継承などの高度な機能は出番も大幅に減る。それどころか、インスタンス化されないクラスも珍しくはない。プログラム中でたった1つだけあればよい「入れもの」の需要は割と多いのである。
それを考えると、C# 2.0の新機能である「静的クラス」が必然的に大きな価値を持つことが分かるだろう。インスタンス化できないクラスは、C# 1.xではprivateなコンストラクタによって記述できたが、C# 2.0ではstaticキーワードを使った専用の構文が用意されたのである。
このような状況はどのように受け止めればよいのだろうか?
クラス・ベースとプロトタイプ・ベース
ここで注目すべきキーワードは、OOPを分類する「クラス・ベース」と「プロトタイプ・ベース」という2つの言葉である。
大ざっぱにいえば、クラス・ベースとはクラスを基にオブジェクトを作成するもので、プロトタイプ・ベースとはプロトタイプを基にオブジェクトを作成するものである。つまり、クラス・ベースではクラス定義に従ってオブジェクトが作られるのに対して、プロトタイプ・ベースではプロトタイプとなる別のオブジェクトの複製として新しいオブジェクトが作られる。クラス・ベースではオブジェクトに属するメソッドなどはクラスによって規定されるが、プロトタイプ・ベースではいつでもいくつでも、オブジェクトにメソッドなどを付け加えることができる。
このようなプロトタイプ・ベースの特徴は、システムに組み込まれたオブジェクトすら後から動的に拡張できることを意味する。なぜ、Webブラウザ上のJavaScriptが最前線のプログラム言語として活躍し続けられるのかといえば、後付けでいくらでもシステムを拡張できる柔軟性があればこそだろう(このようなメソッドの後付けは、C# 3.0でも「extension method」として追加されるようである)。
さて、PCのデスクトップ・プログラミングのOOPは、C++→Java→C# 1.xと流れてきた感があるが、これらはすべてクラス・ベースである。C# 2.0も本質的な意味ではクラス・ベースであることに変わりはない。しかし、C# 2.0はクラスに対する依存性を大幅に引き下げている、クラス・ベースでありながら、プロトタイプ・ベースのような書き方ができてしまうこともある。なぜC# 2.0はクラスから離れねばならないのだろうか?
クラスの持つ問題点
クラス・ベースとプロトタイプ・ベースは互いにどのような位置付けにあるのだろうか。
筆者も不勉強で知らなかったのだが、実はプロトタイプ・ベースとは、クラス・ベースの問題を解決するために提唱されたより新しい概念ということらしい。プロトタイプ・ベースの最初の言語はSmalltalkを改良した「Self」という言語であり、1986年にはすでに設計されていたらしい。
つまり、PC上のOOPのブームが始まってすらいない1980年代のうちに、すでにクラス・ベースの問題点は指摘されていたのである。
さて、クラス・ベースの問題とは何だろうか。
それは、変化に弱いことと、例外的な処理に弱いことだと筆者は考える。
変化に弱い……というのは、奇異に思えるかもしれない。なぜかといえば、クラスには継承という強力な機能があり、元のクラスを書き換えることなく異なる振る舞いを持つオブジェクトを作り出すことができるからである。
ところが、このような強力なカスタマイズ能力を発揮できるのは、元クラスの定義に変化が必要とされない場合に限られる。もし、多数のクラスの基底クラスとなったクラスに修正が必要となった場合、その変更の影響は多数のクラスに及び、とても簡単には済まされない。かといって、あらゆる拡張に対応できるような万能のクラス定義を目指すと、あらゆる可能性に対する無限の備えを行うことになり、現実的なシステムとしては破たんしてしまう。
実は、クラス・ベースのOOPはクラス数がある程度増えると、そこで1つの壁に突き当たるのではないか……という印象もある。その壁に達すると、うかつにクラスの定義を書き換えられなくなり、どうしてもコーディングの手が止まってしまうのである(実は、筆者がC# 1.xで書いていたメーリング・リスト・サーバの「りすと亭」の開発が滞っている理由はこのあたりにあるような気がしないでもない)。
例外的な処理に弱い……というのは、特定の条件を満たす場合にのみ別の処理を行う……といった処理を扱いにくいという意味である。「例外的な処理」の具体的な説明は、過去に筆者が書いた記事にあるので引用しよう。
XMLデータベース開発方法論(1) ■例外的処理こそが重要である 1980年代、筆者は「ドラクエ」で有名なエニックス(現スクウェア・エニックス)でプログラミングの仕事をしていた。そのころ、エニックスには、マニア上がりのすご腕のプログラマが何人もいて、技術を競っていた。また、パソコンのスペックが貧弱であるために、職人芸的なすご腕がなければ成立しないソフトも多かった時代である。このような時代、彼らと話していて必ず同意する話題があった。それは「例外的処理が重要」という考えであった。ここでいう「例外的処理」とは、最近のプログラム言語が備える「(try構文などで使用される)例外」のことではなく、基本的なルールから外れた特殊な処理のことをいう。 例えばゲームの例でいえば、シナリオの都合上、通常の処理とは異なる例外的な処理がしばしば要求される。弾が当たればダメージを受けるというのが通常の処理だとすれば、シナリオ上勝てないとされた強敵と遭遇した場合だけは弾が当たってもダメージを受けないようにするのが例外的な処理に当たる。通常の処理を短くエレガントに記述するのは容易なのだが、例外的な処理はまさに例外的であるために、短くエレガントにまとめられないことが多い。しかし、だからといって長々と泥臭いコードを書いていては、貧弱なスペックの当時のマシンでは実行できなくなってしまう。つまり、「例外的処理が重要」ということになるわけである。 |
ここでいうような例外処理に、クラス・ベースはとても弱い。クラスを設計した時点では予測もしなかった例外処理が要求されたとき、玉突き的に多数のクラスに変更が求められることもあり得る。それだけではなく、あくまで局所的、例外的な処理にすぎないにもかかわらず、クラスの全体的な設計の整合性を配慮すると、多くのクラスが参照する基底クラスのレベルで配慮しなければならないこともある。
これらの問題に対処するために生まれたのがクラスのないプロトタイプ・ベースであるが、実際の世の中は、すべてをクラスで解決するというJavaに向かって急速に流れていった感がある。実はJavaと並行してJavaScriptという優秀なプロトタイプ・ベースのOOP言語が存在していたにもかかわらず、それがOOP言語であるという事実すら浸透しなかった。クラス・ベースの問題に対する批判は黙殺され、世間に幅広く知られることはなかったのである。
そのような観点からC# 1.xを見ると、実は興味深いことが分かる。クラスですべてを解決するというJavaと異なり、C# 1.xとはデリゲート型などの新しい仕掛けを導入することによって、クラスを絶対的な地位から引き下ろしたOOP言語であると見ることができるのである。それこそが、Javaと比べたときのC# 1.xの圧倒的な実用性の高さの源泉といえるかもしれない。しかし、C# 1.xはクラスに対する依存性がまだ強かった。そして、ついにクラスを主役の座から引き下ろしたのがC# 2.0かもしれない。つまり、C#の進化の歴史とは、問題の多いクラスからの決別の歴史であると見るとよいのかもしれない。
JavaScriptとの相違点
ここでは、JavaScriptプログラミングで、匿名関数を湯水のごとく使うのと同様に、C# 2.0でも匿名メソッドを湯水のごとく使うプログラミングが可能であることを説明した。
しかし、クラス・ベースとプロトタイプ・ベースの違いを別としても、両者には決定的な差がある。それは、C# 2.0はJavaScriptと違って、強い「型付け」がなされたタイプセーフな言語であるという点である。
JavaScriptは、常に型変換が自動的に行われてデータの型を意識しなくてもプログラミングできるようになっている。例えば、「javascript:alert("3" - 2);」は「1」を表示する。しかし、C# 2.0ではこのような文字列と数値を引き算するようなプログラミングは通らない。すべての型は厳格に扱われているのである。
つまり、クラスが主役から引き下ろされたとしても、型チェックの厳格さは手放していないのである。それどころか、ジェネリックの導入により、より厳格な型チェックが可能になったといってもよい。
それ故に、コンパイル時の型チェックは厳格に行われ、安全度は高い。プログラムが巨大化しても、型の整合性の破れはコンパイル時に検出できる可能性が高く、ある程度以上の規模の開発にも利用できるかもしれない。
また、継承の使用頻度が減ったことから、キャストの出番も減った感がある。キャストが減れば実行時の型不整合も起こりにくい。これも、好ましい特徴といえるだろう。
まとめ:C# 1.xプログラマーよ、心してかかれ!
まとめておこう。
C# 2.0には、C# 1.xに便利な機能が付いただけと見なす“Better C# 1.x”としての使い方と、クラスを主役から引き下ろして匿名メソッドを「あたかも数値や文字列と同じように」湯水のごとく使う“C# 2.0”としての使い方があり得るだろう。
ここで、前者の使い方が必ずしも誤りではないことに留意していただきたい。例えば、C# 1.xのソース・コード資産をメンテナンスしながら運用するような場合には、無理にコーディング・スタイルを変えるよりも、もともとのC# 1.xのスタイルを堅持した方がスムーズにいくだろう。それは、正当な使い方の1つといえる。
しかし、それは1つの使い方であって、すべての使い方ではない。もし、“Better C# 1.x”をマスターしたことで“C# 2.0”をマスターしたと思い込むと、恥をかく可能性もあり得るだろう。それは、かつてBetter CとしてのC++をマスターしたことでC++をマスターしたと思い込んで恥をかいた者たちや、Javaからの類推でC#を理解した気になり、うかつなことを語って恥をかいた者たちと同じ立場に立つことを意味する。
次回予告
次回から、C# 2.0の個々の新機能を突っ込んで解説していく。あまり先走りすぎても戸惑う読者も多いと思うので、まずは“Better C# 1.x”としても非常に役立つ機能から解説しておこう。
次回のテーマはジェネリックである。
ジェネリックを使うと、任意の型のデータを扱うクラスやメソッドなどをスマートに記述することができるようになる。例えば、.NET Frameworkクラス・ライブラリに含まれるジェネリックのコレクションは、従来型のコレクションと違って、宣言時に型を指定することで、キャスト抜きで格納された値を扱うことが可能となる。
これを使うだけで、従来のC# 1.xのソース・コードからキャストが減り、コンパイル時の型チェックの精度も上がり、よいことばかりである。
しかし、単に機械的に置き換えるとわなに落ちることもある。そのあたりも含め、解説を行う。
C# 2.0主要新機能一覧
最後に、今後の連載予定の紹介を兼ねて、C# 2.0の主要な新機能をリストアップしておく。
第2回 ジェネリック
C# 1.xでは、ArrayListクラスなどのコレクションを使用すると、どうしてもキャストを行わねばならなかった。しかし、ジェネリックの導入によって、無駄なキャストが不要になった。
第3回 反復子
yield文の導入により、イテレータの記述が容易になった。
第4回 null許容型
nullになるかもしれない整数のようなデータ型を使うことができ、データベースから値を受け取ることが容易になった。
第5回 匿名メソッドとデリゲート
匿名メソッドとデリゲートを使うことで、コード片を数値や文字列と同じぐらい手軽に変数に代入したり、引数に渡したりすることが可能になった。しかし、匿名メソッドはクロージャではない。
第6回 クラスの強化
クラスの機能にも、部分クラスや静的クラスなど、さまざまな機能強化が見られる。
第7回 名前空間のエイリアス修飾子 と外部アセンブリ
名前空間のエイリアス修飾子や外部アセンブリのエイリアス、フレンド・アセンブリなどの新機能によって、複数の名前空間やアセンブリを使う場合の利便性が上がった。
第8回 そのほかの言語の拡張
固定サイズ・バッファ、IntPtr型および UIntPtr型へのvolatile、インライン警告制御、C#コンパイラの新機能などのさまざまな細かい変更点もある。
INDEX | ||
C# 2.0入門 | ||
第1回 総論:C# 2.0らしいプログラミングとは | ||
1.意外性あり? この連載で解説すること | ||
2.C# 2.0らしいソース・コードとは? | ||
3.インターフェイスとの比較 | ||
4.後退するクラスの立場、クラスの持つ問題点 | ||
「C# 2.0入門」 |
- 第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用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
|
|