特集

私がJavaからC#に乗り換えた10の理由

日本ユニシス 尾島 良司
2003/07/05
Page1 Page2 Page3 Page4

- 理由03 - property

 オブジェクトがメッセージを送りあうことによってシステムが動作するという素朴なオブジェクト指向観を採用するなら、メソッドしかないJavaの文法は正しい。しかし、オブジェクト指向を応用しようとする場合、例えば開発環境に応用するような場合にはメソッドだけでは不十分である。このような場合には状態を設定するためのメソッドである「プロパティ(property)」が必要になるのだ。

 JavaはJava Beanというコンポーネント規格を定める際にこの問題にぶつかった。その際にJavaが採用した解決策は、プロパティとしての役割を持つメソッドの名前をgetXxx/setXxx/isXxxに統一するというものだった。文法を拡張せずにコーディング・ルールを追加したわけだ。

 この方式には実装効率と実行効率の2点で問題がある。まずは実装効率での問題。それは正しくルールに従っているかを人間がチェックしなければならないことだ。文法を拡張すればコンパイラで自動チェックできることに貴重な人間の労働力をつぎ込むとは、なんとも嘆かわしい話だ。次に実行効率で問題となる理由。それはプロパティかどうかを調べるには文字列の比較をしなければならないことだ。メソッド名が「get」で始まっているけれども、プロパティではないものをはじいたりもしなければならない。文法を拡張したならフラグを調べるだけで済むことを、CPUパワーをつぎ込んで推論する。実に嘆かわしい話である。

 C#には、プロパティを定義するための文法が準備されている。propertyだ。実装効率も実行効率もよい。C#に乗り換えたくなってこないだろうか?

 以下、プロパティの概要を解説する。例によって、まずはコード例を示す。

public interface IMyInterface
{
  //  interfaceでのプロパティの宣言。
  int X { get; set; }

  //  interfaceでの読み取り専用プロパティの宣言。
  int Y { get; }
}

public class MyClass: IMyInterface
{
  private int x = 0;
  private int y = 1;
  private int z = 2;

  //  プロパティの実装。
  public int X
  {
    get { return x; }   //  取得部分。

    set { x = value; }  //  設定部分。

  }

  //  読み取り専用プロパティの実装。
  public int Y
  {
    get { return y; }
  }

  //  もちろん、interfaceとは関係のないプロパティも定義できる。
  public int Z
  {
    get { return z; }
    set { z = value; }
  }
}
propertyのコード例

 難しい文法ではない。アクセサ・メソッドであるsetの引数がvalueという暗黙の変数に入るということだけを覚えれば、すぐに使えるだろう。こんな簡単な文法を追加するだけで、実装効率も実行効率も向上できるのである。

- 理由04 - custom attribute

 Javaではシリアル化できるクラスかどうかを判断するために、Serializableというインターフェイスを使用する。Serializableインターフェイスにはメソッドが1つもない。instanceof(C#でのisに相当する)で判断するためだけに使われているのである。

 JavaのEJB(Enterprise JavaBeans)では、コンポーネントの振る舞いに関する記述をソース・コードとは別のXMLファイルで供給する。ソース・コードとは関係のないところに、ソース・コードに対する補足を書かなければならないわけだ。

 なぜ、このような不自然なことをしなければならないのかを考えてみよう。答えは簡単。メタ・データを指定する方法がほかになかったためだ。メタ・データとはクラスの内部構造を表す情報である。クラス名、メソッド名、シグネチャ、アクセス制御子。もちろんJavaにもメタ・データはあり、Java VMはメタ・データを解釈しながら動作している。

 しかし、神ならぬ身である人間には、事前にすべてを見通すことは不可能である。例えば、Javaのバージョンが0.xxのときにはシリアル化が可能かどうかを表現するメタ・データが必要であることなど分からなかった。例えば、EJBというサーバ・サイドのコンポーネント規格ができて、トランザクション制御のためのメタ・データが必要になるとは予想できなかったのである。新たなメタ・データが必要になるたびに文法を変更するわけにはいかないので、Javaではインターフェイスを使用したり外部ファイルで指定したりと場当たり的な対応を取らざるを得なかったのだ。あぁ、嘆かわしい。

 では、どうすればよかったのだろうか? 独自のメタ・データを後から定義できる機能を用意すればよかったのである。C#のカスタム属性(custom attribute)は、このメタ・データの定義と設定の機能を提供する。カスタム属性を使用すれば、シリアル化やトランザクション制御の指定はソース・コードの中に自然に記述できるのだ。そろそろ、C#に乗り換える決意が固まってきたのではないだろうか?

 以下にカスタム属性の概要を解説する。

 Visual Studio .NETのウィザードでコンソール・アプリケーションを作成すると、以下のコードのようなメイン・ルーチンがあらかじめ生成される。

[STAThread]
static void Main(string[] args)
{
  // TODO: アプリケーションを開始するコードをここに追加してください。

}
自動生成されるコンソール・アプリケーションのメイン・ルーチン

 このコードの1行目、[STAThread]の部分がカスタム属性を指定している部分である。STAThreadというカスタム属性を指定すると、アプリケーションを「シングル・スレッド・アパートメント」というモードで動作させることができる。Windowsにはスレッドを協調させるためのモードがいろいろあるのだが、そのモードの指定がたった1行でできてしまったのだ。クラスをシリアル化可能にするのも同様のコードで済む。シリアル化可能にしたいクラスの直前に[Serializable]と書くだけだ。トランザクション制御も簡単で、トランザクション制御を設定したいメソッドの前に[Transaction(TransactionOption.Required)]と書くだけなのである。

 今回は解説を割愛するが、もちろん独自のカスタム属性も作成できる。ユニット・テストのフレームワーク「xUnit」の.NET Framework版である「NUnit」では、[Test]や[TestFixture]といったカスタム属性を独自に定義して、有効に活用している。

- 理由05 - thread

 Javaではスレッド(thread)が言語レベルに組み込まれていると知ったときにはうれしかった。整合性のない後付けのライブラリで無理やりスレッドに対応した場合の地獄をそれまで味わい続けてきたからだ。

 しかし、人間の欲望には限りがない。喜びは持続せず、すぐに不満だらけの日々がやってきてしまった。スレッド・プーリングを自前で実装するのが面倒くさい。スレッドに引数を与えるためにはクラスを作らなければならないのが面倒くさい。これらは決して小さなことではない。スレッド関係のバグはデバッグが大変なのだ。こんな小さなことでも嘆かわしい事態が発生する原因になってしまう。

 C#ならこれらの不満は存在しない。痒いところに十分に手が届いている。スレッド・プーリングも、引数のあるメソッドをスレッドとして動かすための仕組みも、C#は言語のレベルで提供するのである。Javaでスレッドをまともに扱ったことのあるプログラマなら、C#が魅力的に感じられるのではないだろうか?

 以下にC#でのスレッドの概要を解説する。

 C#にもJavaと同様にクラスを使用したスレッドがあるが、それはあまり使われない。以下のコードのようにデリゲートを使用する方式がメインである。

using System;

public class TestCSharp
{
  //  引数を取るスレッドで動作させたいメソッド。
  public static void ThreadMethod(int x)
  {
    …
  }

  //  同じ引数を持つデリゲートを定義。
  delegate void ThreadMethodDelegate(int x);

  public static void Main(string[] args)
  {
    //  デリゲートを生成。
    ThreadMethodDelegate d = new ThreadMethodDelegate(ThreadMethod);

    //  メソッドを別スレッドで実行。
    d.BeginInvoke(0, null, null);
  }
}
デリゲートを使用したスレッドのコード例

 スレッドとして動作させたいメソッドと同じ引数を持つデリゲートを作成して、BeginInvokeメソッドを呼ぶ。それだけでそのメソッドは別スレッドで実行される。コードを見れば分かるように、引数は自由に設定できる。また、ループで繰り返してBeginInvokeしてみるとスレッドが一定の数(デフォルトは25)で制限されることが分かる。スレッド・プーリングだ。

 なお、上記のコードのBeginInvokeメソッドでnullが2つ余計に指定されているのは、スレッドの終了をチェックするためのものである。C#では、スレッドの完了をチェックする仕組みまで言語レベルで提供されるのである。


 INDEX
  [特集]私がJavaからC#に乗り換えた10の理由
     1.struct、delegate
   2.property、custom attribute、thread
     3.interface、virtualとoverride、#if、Visual Studio .NET、Javaが嫌になった
     4.私が思う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 記事ランキング

本日 月間