特集

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

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

 起きてから寝るまで、息を吸うのも、厠(かわや)での一連のアクションも.NET Frameworkで構築している私だが、実は少し前まで目を閉じるとまぶたの裏でJavaのマスコットである“Duke”がゴーゴー・ダンスを踊っちゃうくらいにJavaな日々を送っていた。そんな私が過去の資産を捨てて.NET Frameworkに転んだ理由は簡単。.NET Framework、特にC#の設計思想が私のし好に合っていると感じたためだ。

 本稿では、私がJavaからC#に乗り換えた理由を示し、それを基にC#の“正しい”使い方について考察する。C#に興味のあるJavaプログラマや、どう使えばよいのか悩んでいるC#プログラマに読んでいただきたい。そうそう、アーキテクトにも。プログラミングできないアーキテクトなどあり得ないのだから。

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

 C#とJavaは似ていない。確かに表面上は似ているが、C#にはJavaにはない文法やJavaの常識では理解できない文法が多数存在するのだ。この項では、これらの相違点の中から私がC#に乗り換える原因となったものを10個選択して紹介する。本稿に記載した以外のJavaとC#の文法の違いについては、日本ユニシスのサイトにある私のBLog「.NET的視点」を参考にしていただきたい。また、本稿の対象範囲はC#という“言語”関連に限定し、J2EE(Java 2 Enterprise Edition)から.NET Frameworkへの“環境”の乗り換えは対象外としたことも了承していただきたい。

 さて、私がC#に乗り換えた10の理由とは、「struct」「delegate」「property」「custom attribute」「thread」「interface」「virtualとoverride」「#if」「Visual Studio .NET」「Javaが嫌になった」である。以下にその詳細を示す。

- 理由01 - struct

 Javaで実行効率が問題となった場合、言語のレベルで最初に疑うのはインスタンスの生成と破棄の部分だろう。インスタンスの生成ではヒープにメモリを確保するという重い処理が必要だし、インスタンスの破棄のためのガベージ・コレクションはさらに重い処理だからである。

 結果として、大量のインスタンスを扱う場合は、生成と破棄のオーバーヘッドを避けるためにインスタンスを使い回したりインスタンスの粒度を大きくしたりすることになる。そして、ソース・コードの可読性が落ち、バグの発生率が上昇し、プロジェクトが失敗する。嘆かわしい話だ。

 そう、生成と破棄のオーバーヘッドを避ける抜本的な解決は、生成と破棄のオーバーヘッドをなくしてしまうことなのだ。ヒープにメモリを確保することなくインスタンスを生成し、ガベージ・コレクションに頼らなくてもメモリを破棄できる手段を用意すればよいのである。

 実際、生成と破棄のオーバーヘッドがないデータ表現の方式はJavaにも存在する。intやdoubleのような値型だ(Javaでは基本データ型と呼ぶ)。値型はスタックに配置されるのでヒープにメモリを確保する必要がない。値型のスコープは明確なのでガベージ・コレクションに頼らずとも的確にインスタンスを破棄できる。これならオーバーヘッドはほとんど発生しない。

 このように考えていくと、インスタンスの生成と破棄のオーバーヘッドを回避する根本的な手段は新たな値型を定義可能にすることだと分かる。C#には、この新たな値型を定義するための文法struct(構造体)がある。C#に乗り換えたくなってこないだろうか?

 以下、structの概要を解説する。理解を容易にするために、まずはstructのコード例を示す。

using System;

//  structの定義。
public struct MyStruct
{
  //  フィールドの定義
  private int x;
  private int y;

  //  コンストラクタの定義。
  public MyStruct(int x, int y) { this.x = x; this.y = y; }

  //  メソッドの定義。

  public void DoSomething() { }
}

public class C
{
  public static void Main()
  {
    //  structのインスタンスの生成とメソッドの呼び出し。
    MyStruct myStruct = new MyStruct(0, 1);
    myStruct.DoSomething();

    //  classのインスタンスの生成とメソッドの呼び出し。
    MyClass myClass = new MyClass(0, 1);
    myClass.DoSomething();
  }
}
structのコード例

 コードから分かるように、structはclass(クラス)とよく似ている。newでインスタンスを作成できるし、メソッドへの呼び出しもclassと同じである。異なるのはインスタンスをメモリに配置する方式だけだ。

 structのインスタンスはスタックに格納される。classの場合はスタックにはインスタンスへの参照が置かれるのみで、インスタンスはヒープに格納される。上記のソース・コードでのメモリ状態は以下の図のようになる。structはスタックしか使わないのだから、classに比べてインスタンスの生成と破棄にオーバーヘッドが生じないのである。

struct(構造体)とclass(クラス)の違い
structのインスタンスはスタックに格納されるが、classのインスタンスはヒープに格納される。

 と、よいことばかりを書いてきたが、structにもデメリットはある。structのデメリットは、ポリモルフィズムの使用が困難なことと実行効率が悪い場合があることである。まず、実体がスタックに置かれるのでポリモルフィズムは不可能だ。そして、メソッドの呼び出しではスタックをコピーしなければならないので、あまり大きなオブジェクトを表現すると実行効率が落ちてしまう。

 ポリモルフィズムに関しては逃げ道がないわけではない。C#にはボックス化というclassとstructの相互運用のための機能がある。ボックス化とは、structのインスタンスをヒープにコピーしてclassのインスタンスとして扱う機能だ。JavaのCollectionにintやdoubleを格納する際には、IntegerやDoubleというclassでラップする必要があった。ボックス化はこのような作業を自動で行ってくれる。

 そう、このボックス化を使用すれば、structを使用する場合でもオブジェクト指向のポリモルフィズムの恩恵にあずかることが可能なのである。まぁ、ボックス化はヒープにコピーするので実行効率を落とすしメソッド呼び出し時のコピーも実行効率を落とすので、structはポリモルフィズムを“あまり”使用しない場合かつ“小さな”オブジェクトにしか適用できないのだが。

- 理由02 - delegate

 継承よりもコンポジションを重視するのが現代オブジェクト指向である。Javaでのコンポジションの単位はインターフェイス(interface)だが、インターフェイスはこの目的に使うには小回りが利かなすぎる場合がある。Javaでは大量のクラスの作成を余儀なくされることがあるのだ。

 具体的に考えてみよう。Webのユーザビリティの低さに嫌気がさして、スマート・クライアントを作ることにしたとする。その場合にJavaで発生するのは、GUIコントロール(コンポーネント)のイベント・ハンドラ(リスナ)の実装をどうするかという問題だ。フォーム(フレーム)にボタンを2つ貼り付けた場合、ボタンのイベント・ハンドラをフォームのメソッドで実装することはできない。同じインターフェイスなのでメソッド名が同じになってしまうためだ。これを避けるためにはボタンA用のイベント・ハンドラを表現するクラスとボタンB用のイベント・ハンドラを表現するクラスを作らなければならない。貴重な労力の無駄遣いだ。嘆かわしい。

 問題は、コンポジションの単位がインターフェイスしかないことにある。インターフェイスは詰まるところメソッド宣言の集合で、それでは粒度が大きすぎる分野があるのだ。この問題は、メソッド単体のレベルでコンポジションする機能を追加すれば解消できる。C#のデリゲート(delegate)は、まさにこのメソッド単位でのコンポジションを可能にする。どうだろう、C#に乗り換えたくなってこないだろうか?

 以下、デリゲートの概要を解説する。理解を容易にするために、まずはコード例を示す。

using System;

public class MyClass
{
  //  delegateの宣言。
  public delegate void Callback();

  //  delegateを引数に取るメソッド。

  public void DoSomething(Callback callback)
  {
    //  delegateの実体を呼び出す。
    callback();
  }
}

public class C
{
  //  delegateの実体となるメソッド。
  //  メソッド名は自由に付けて構わない。

  public static void MyCallback() { }

  public static void Main(string[] args)
  {
    //  delegateを生成してメソッドを呼ぶ。
    new MyClass().DoSomething(new MyClass.Callback(MyCallback));
  }
}
delegateのコード例

 コードを解説する。「public delegate void Callback()」の部分がデリゲートの宣言だ。デリゲートは型なので、この「Callback」はDoSomethingメソッドの引数の型として使用できる。型の実体となるメソッド(MyCallback)の名前は自由に付けて構わない。クラスの名前がインターフェイスの名前と無関係であるのと同じことだ。デリゲートとメソッドとの関係付けは、デリゲートをnewすることで行う。以上、デリゲートを使用すればメソッドのレベルでのコンポジションが可能であることがお分かりいただけたと思う。

 .NET Frameworkでは、GUIのイベント・ハンドラなどのメソッド単位で完結するもののコンポジションにデリゲートを使用している。先ほどのコードを実装するのは面倒に感じるかもしれない。しかし、デリゲートの宣言までは.NET Frameworkがしてくれている。我々がしなければならないのはメソッドを割り当てる部分だけであり、それも演算子オーバーライドによって+=の後ろにメソッドを書くだけとなる。デリゲートの効果は大きく、Javaよりもはるかに読みやすいGUI部分のソース・コードを記述できるのだ。

 

 INDEX
  [特集]私がJavaからC#に乗り換えた10の理由
   1.struct、delegate
     2.property、custom attribute、thread
     3.interface、virtualとoverride、#if、Visual Studio .NET、Javaが嫌になった
     4.私が思うC#の“正しい”使い方
 
更新履歴
【2003/07/16】本ページの内容に以下のような誤りがありました。お詫びして訂正させていただきます。

- 理由01 - struct

結果として、Javaプログラマは、生成と破棄のオーバーヘッドを避けるためにインスタンスを使い回したりインスタンスの粒度を大きくしたりすることになる。
結果として、大量のインスタンスを扱う場合は、生成と破棄のオーバーヘッドを避けるためにインスタンスを使い回したりインスタンスの粒度を大きくしたりすることになる。

structのデメリットは、ポリモルフィズムの使用が困難なことである。structはポリモルフィズムをあまり使用しない場合にしか適用できないのだ。
structのデメリットは、ポリモルフィズムの使用が困難なことと実行効率が悪い場合があることである。まず、実体がスタックに置かれるのでポリモルフィズムは不可能だ。そして、メソッドの呼び出しではスタックをコピーしなければならないので、あまり大きなオブジェクトを表現すると実行効率が落ちてしまう。

ただし、逃げ道がないわけではない。
ポリモルフィズムに関しては逃げ道がないわけではない。

まぁ、ヒープにコピーするせいで実行効率が落ちてしまうので、前述したようにstructはポリモルフィズムを“あまり”使用しない場合にしか適用できないのだが。
まぁ、ボックス化はヒープにコピーするので実行効率を落とすしメソッド呼び出し時のコピーも実行効率を落とすので、structはポリモルフィズムを“あまり”使用しない場合かつ“小さな”オブジェクトにしか適用できないのだが。

- 理由02 - delegate

.NET Frameworkでは、GUIのイベント・ハンドラなどのメソッド単位で完結するもののコンポジションにデリゲートを使用している。デリゲートの効果は大きく、Javaよりもはるかに読みやすいGUI部分のソース・コードを記述できる。
.NET Frameworkでは、GUIのイベント・ハンドラなどのメソッド単位で完結するもののコンポジションにデリゲートを使用している。先ほどのコードを実装するのは面倒に感じるかもしれない。しかし、デリゲートの宣言までは.NET Frameworkがしてくれている。我々がしなければならないのはメソッドを割り当てる部分だけであり、それも演算子オーバーライドによって+=の後ろにメソッドを書くだけとなる。デリゲートの効果は大きく、Javaよりもはるかに読みやすいGUI部分のソース・コードを記述できるのだ。



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 記事ランキング

本日 月間