Skip to content.

Sections
Personal tools
You are here: Home » 技術文書 » C# » Java プログラマのためのC# 考察 -- 言語仕様の比較

Document Actions
Java プログラマのためのC# 考察 -- 言語仕様の比較
(株)永和システムマネジメント    平鍋健児
作成日:初版 2000,8/17

2000年6月に発表された Microsoft の新しい言語,C#(シー・シャープ)は, その目指す目標,言語仕様ともに Java と密接な関係がある.この記事では, C# 言語仕様の紹介を,特に Java との比較の観点から行う. C# およびこの言語が動作する .NET(ドットネット)プラットホームについては 多分にセンセーショナルな側面があるが,ここではJava と C# 周辺の政治的な部分には立ち入らず, 技術的側面にのみ焦点をあてる.

   Hello, World

なにはともあれ,最初に Hello Wolrd プログラムを見て頂こう.

C# Java
hello.cs
using System;

class Hello
{
  static void Main() {
    Console.WriteLine("Hello, World");
  }
}
Hello.java
import java.lang.System;

public class Hello {
  public static void main(String [] args) {
    System.out.println("Hello, World");
  }
}
コンパイル,実行
> csc hello.cs
> hello.exe
  Hello, World
コンパイル,実行
> javac Hello.java
> java Hello
  Hello, World

これを見れば,C# のシンタクスがいかに Java に近いかがお分かり 頂けると思う.この短い Hello World プログラムに見られる C# の特徴として,以下が挙げられる.

  • クラスがプログラムの基本構成要素である.

    コードを組み立てる大域概念は(主に)クラスであり, 関数や変数はトップレベルにはない.主にと書いたのは, Java には実際クラス以外にも interface があり, さらに C# にはstruct, enum, interface, delegate などのトップレベル概念があることを念頭に置いている.しかし, 「基本はクラス」というプログラム構成は Java と似ている.

  • C++ 言語をシンタクスの基礎に置いている.

    これも,Java と非常に良く似ている.begin end でなく { } でクラスや メソッドの始まりと終わりを示す.";"(セミコロン) を文の区切りに 用いる,「オブジェクト.メソッド名(引数)」でメソッド呼び出しを行う, などの基本構文は同じである.この例では,Console クラスの WriteLine メソッドを "Hello World" という引数で呼び出している. また,C++ ではクラス定義の最後には";" を付ける(class A { }; ). しかし,C# および Java では共にクラス定義の最後には,";" は不要だ (class C { }).

  • using 指定で,他の名前空間のシンボルを参照する.

    第1行目は,Console クラスが存在する System というクラスライブラリ(.NET 中のライブラリ)の 名前空間の利用を宣言している.名前空間(namespace)は, Java のパッケージ(package)に 相当し,using は Java の import に対応する.Java のパッケージは クラスファイルが置かれている物理的なディレクトリ構成と関係して いるが,C# の名前空間は完全に論理的な構成であり,ディレクトリ やファイル構成とは無関係である.例えば1ファイルに複数の public クラスが存在しても構わない.また,C# の using は, C++ の #include のように 宣言をファイルテキストで展開して取り込むのではなく,名前空間から 名前をシンボルとして取り込んでいる.この点は Java と同じである.

  • 宣言と実装が分割されていない.

    C++ では,クラスの宣言はヘッダファイル(.h や .hpp というサフィックス を持つ事が多い)に,実装ファイル(.cpp や .cc というサフィックスを持つ ことが多い)に実装本体を記述する. そして,実装ファイルではヘッダファイルを #include というプリプロセッサ指令によって取り込む.C# では(Java と同じく),宣言と 実装が同じファイル(.cs というサフィックス)である.

  • 実行の開始は,クラスの Main メソッドから開始される.

    これは Java と同じである.Main は static と宣言され,Hello クラスに属する クラスメソッドである.

  • メソッド名は,大文字ではじめ,クラス定義の開 始の括弧"{"は,改行して次段の最初に書くのが習慣らしい.

    これは言語仕様ではなく単なるコーディング習慣であるが,Java とは違う. Java はメソッド名は小文字で始めることが慣例であり,クラス定義の開始を 示す括弧はそのままクラス名に改行なしで続くスタイルが多い.

  • コンパイルを行うと,実行形式ができあがる.

    これは大きく Java と異なる.Java は Java VM(JVM) を介してターゲット環 境とインタラクションする.すなわち,Javaのソースをコンパイルするとバイ ト・コードと呼ばれるコードが生成され、それをJVMが解釈して実行するわけ である.

    それに対して C# では,ソースをコンパイルすることによって直接実行可能 な.EXE 形式のファイルが生成される.ただし,マイクロソフトは、C# のソー スからJavaのバイト・コードに対応したコード「Imediate Language (IL)」を 生成する機構も提供している.ILは、.NETプラットホームでサポートされるバ イト・コード形式であり,言語非依存である.IL は C#だけでなくC++や 「Visual Basic」,COBOLなどからも生成することが可能であり,生成したIL を .NETプラットホームの実行環境(「Common Language Runtime[CLR]」と呼ば れる)上で動作させるわけである.ただし,この機構はマイクロソフト独自の 実装となるので,ILを生成するコンパイラなどを開発するにはマイクロソフト のライセンシーになる必要がある.


   言語目標

C# と Java が目指すゴールにも,多くの共通点がある. Java は,最初そのC++との関係から,別名 'C++--' とも言われた. C++から余分な(複雑なあるいはエラーを発生させやすい)機能を取り去ったという意味である. C# は,# が + 4つに見えることから,'C++++' の意味と解釈できる. あるいは,音程を示すC(ド)を半音上げたという意味かもしれない. このネーミングもやはり C++ を意識している.

ここでは,C# のゴールを Java と比較してみる.


  C#/Java に共通するゴール

C# と Java の両方に共通するゴールを挙げてみる.

  • 学びやすい言語

    Java も C++ の複雑さを回避している.C# も同様である. ポインタや多重継承と言った機能を意識的に排している.

  • エラーを発生させにくい言語

    ポインタをなくし,ガベージコレクションによってメモリ管理をシステムの責任としている. アプリケーションのメモリーエラーを極力少くしている点で,C# は Java に非常に近い.

  • 様々なコンピュータ上で動作すること

    このC# のゴールは Java と共通しており,それを実現する手法も似ている. Java はこのゴールを達成するために JVM を各種マシンで動作させ, Java プログラムを JVM で動作するバイトコードへと一旦コンパイルする手法を採った. 実行時にバイトコードが JVM によって解釈されて実行される. C# も,IL(Immediate Language) という.NET プラットホーム上で動作するバイトコードを生成する. ただし,IL が言語非依存であること(C# 以外の言語でも作成できる), また,.NET ランタイム(CLR)とのリンクを「インストール時」に行うことができる点が異なる. デフォルトでは,C# プログラムをコンパイルすると.EXE 形式のバイナリとなる. C# の実行形式は .NET ランタイムをサポートする各種マシン上で動作する. マイクロソフトは,現時点ではいくつかの携帯端末用OSにCLRを移植することを表明しているが, UNIXなどWindows以外のPC/サーバ・プラットフォームに対してもCLRが移植されるかどうかは定かではない。


  C#/Java に共通しないゴール

  • ターゲット資源に直接アクセスできる.

    Java は JVM を介してしかターゲット環境の資源にアクセスできない. 今度は,C# に特有のゴールを挙げ,Java はこの問題にどう対処している かを考察する.

  • RAD を強力にサポートする

    C# は,Visual Basic や Delphi といったRAD(Rapid Applicatoin Development)に 適した言語を目指しており,コンポーネント開発を推進する.また,Visual Studio という 開発環境と密接に連携する.すこし余談になるが,C# の設計者である Anders Hejlsberg は, ボーランド(現インプライズ)で Turbo Pascal を Object Pascalに発展させ,Delphi へと 進化させた言語設計者である. Java は,言語仕様そのものは RAD, コンポーネントへの対応を明確には打ち出していない. ただし,JavaBeans をはじめとする言語の周辺仕様によって,自然に RAD,コンポーネン ト開発環境へ適応できる仕組みを提供できる.Hejlsberg は,C# を

    "The first component-oriented language in the C/C++ family"
    C/C++ ファミリで最初のコンポーネント指向言語

    と紹介している.

    一方C#では,通常は直接実行可能なバイナリ・ファイルが生成されるので, ターゲット環境の資源に直接アクセスすることができる. また,マイクロソフト独自の実装である中間言語 IL からも. CLRを介したターゲット環境のWin32 APIや .NET用APIへの直接アクセスが可能だ. この点は、Javaと大きく異なる部分と言えるだろう.

    また,C#ではポインタが完全に廃止されているわけではない. 必要な場合には,ポインタを使った非安全(unsafe)なコードを使うことも可能となっている(後述). Javaでは、セキュリティ上の理由およびクロスプラットフォーム性の確保の理由から, マシンのリソースに直接アクセスすることは許されていない.


   様々な言語機能と共通点,相違点

次に,C# が提供する様々な言語仕様を一通り眺めていこう. Java との共通点,相違点の比較を行いながら各機能を紹介する. 対応するコードが書けるものはできるだけ両言語のコードを示した. C# のコードのみを記したものもある.


  ガベージコレクション

C# では,C/C++ の様にヒープ(自由領域)に生成されたオブジェクトのメモリ解放をプログラマが行わなくて良い. これは,Java も同じである.ヒープに生成されたオブジェクトは, プログラムのどの部分からも参照されなくなった時点で,ガベージコレクション(ごみ集め)の対象となり, 時期を見て自動的に解放される.

よく知られる通り,プログラマによる手動メモリ管理はバグを発生させる確率が非常に高い. ガベージコレクションにより,プログラマはメモリ管理から解放され, 本来の仕事であるビジネスロジックやドメインの問題に専念することができる.


  値型と参照型

C# の変数の型はそのセマンティクスの違いから,「値型」と「参照型」の2つに分類される. また,定義が言語に組み込まれているかどうかによって, 「定義済型」(組み込み型)と,「ユーザ定義型」に分類される.これは Java と同様である.


C# 定義済型ユーザ定義型
値型 int, long, decimal,... struct, enum で定義されるもの
参照型 object, string class, interface で定義されるもの

Java 定義済型ユーザ定義型
値型 int, long, ... なし
参照型 Object, String,... class, interface で定義されるもの

値型の変数はスタック上にその領域が取られ,代入によって値がコピーされる. int, long, double といったおなじみの型は定義済の値型である. これに対して,string,object は定義済の参照型,ユーザ定義の構造体は値型, ユーザ定義のクラスは参照型である. 参照型の変数はヒープ上のオブジェクトを参照しており, 変数の代入時には参照先のオブジェクトが共有される.

C#に特徴的な定義済の値型は,decimal という型である. 10進で28桁を正確に保持することができる.金融アプリケーションでの 大きな金額のような,整数の丸め誤差が許されない量を扱うのに適している. これは,RAD サポートを強く意識した仕様であろう.

decimal income = 1.23M;  // 1,230,000

強いて言えば,Java でこれに対応するものとして java.math.BigDecimal クラスを挙げることができる.

また,C# 型システムの大きな特徴として,値型と参照型に共通の基底クラスが存在することが挙げられる. 値型も参照型も,すべての型はobject と呼ばれるクラスから派生している. よって,例えば int のリテラルでさえ,object に定義された ToString()メソッドを持っているし, object 型を引数に取るコレクションクラスに直接値を格納することも可能だ.

using System;
public class Stack
{
  public object Pop() { ... }
  public void Push(object o) { ... }
}
class Test
{
  static void Main() {
    Console.WriteLine(3.ToString()); // 整数リテラル 3 の ToString() メソッド
    object o = 2;          // ボクシング(object に箱付め)
    int i = (int)o;        // アンボクシング(object から値を復元) (i == 2)
    Stack s = new Stack();
    s.Push(3);               // 整数リテラル 3 を直接プッシュ
    i = (int)s.Pop();        // ポップすると値が復元 (i == 3)
  }
}

値型を object に代入することをボクシング(boxing) - 箱詰め - と呼び, 逆に object をキャストして値型に戻すことをアンボクシング(unboxing)と呼ぶ.

Java は,コレクションクラスを利用する際にint 型の代わりに Integer ラッパークラスを使った オブジェトを用いる必要がある. C# では上記に示すように,値型と参照型の型統一(type system unification)によって, 値型をそのままコレクションに入れることができる.


クラスの継承とインターフェイスの継承

まず,クラスやインターフェイスの継承に関する用語に注意する.C# はほとんど C++ と同じ用語を用いる.

C# Java
継承先のクラスの呼び方 派生クラスサブクラス
継承元のクラスの呼び方 基底クラススーパークラス
クラスの継承 継承する拡張する
記述方法 class C : B { } class C extends B { }
インターフェイスの実装 継承する実装する
記述方法 class C : I { } class C implements I { }
継承元クラスのメソッド起動 base.method() super.method()

C++ と異なり,C# ではクラスの単一継承しか許されない.しかし,Java と同様に,interface と呼ばれる実装やデータを持たないシグニチャの集合を 宣言でき,クラスはこれを継承することができる.

C#
public interface Scaleable
{
  void Scale(int factor);    
}
public class Shape
{
  public virtual void Move(int x, int y) {
    // Shape クラスの move
  }
}
public class Circle: Shape, Scaleable
{
   public void Scale(int factor) {
     // Scaleable インターフェイスの実装
   }
   override public void Move(int x, int y) {
     // Shape の move をオーバーライド
   }
}
Java
public interface Scaleable {
  void scale(int factor);    
}

public class Shape {
  public void move(int x, int y) {
    // Shape クラスの move
  }
}

public class Circle extends Shape implements Scaleable {
   public void scale(int factor) {
     // Scaleable インターフェイスの実装
   }
   public void move(int x, int y) {
     // Shape の move をオーバーライド
   }
}

両言語とも,インターフェイスとクラスの両方を継承することができる. ただし,Java ではインターフェイスは「継承する」と言わずに「実装する」と言う.

Java では,クラスの継承は extends, インターフェイスの実装は implementsというそれぞれ別のキーワードを用いる. C# では,共にクラス名の後の":" に続けて書くことで,継承を意味する. また,C++ にあるような,privateや protected な継承はなく, 継承と言った場合はすべて public な継承である点も,Java と同じである.

Java との大きな相違は,基底クラスのメソッドをオーバーライドする際には, 明示的な virtual/override キーワードが必要である点だ. これにより,次のような起こりがちなミスを防いでいる.

  • 基底クラスとその派生クラスの開発者が違ったり,バージョンのずれにより, 基底クラスの意図しないメソッドが偶然派生クラスでオーバーロードされてしまう.
  • 派生クラスで,あるメソッドをオーバーライドしたつもりが, メソッド名をタイプミスしてオーバーロードしそこなう.(別のメソッドを定義していることに気付かない)

この override キーワードの機能は,おそらく Delphi から引き継いだものであろう. 基底クラスは明示的に virtual としてオーバーライドされる用意があることを示し, 派生クラスも明示的に override としてオーバーライドしているつもりであることを示す. この対応が崩れれば,コンパイラがエラーや警告としてプログラマに意図のずれを報告できる.


  struct と enum

C# は,Java には存在しない enum と struct をサポートしている.

C# Java
public enum Color
{
  Red, Blue, Green
}

public struct Point
{
  public int x, y;
}
public interface Color {
  int RED  = 0; // 自動的に public static final
  int GREEN = 1;
  int BLUE  = 2;
}
public class Point { // 対応物なし
  public int x, y;
}

enum は,種類を列挙する場合に便利な機能である. C++/C と同様,enum の各要素には,コンパイラにより自動的に整数に対応する数値が振られ, enum 値によって分岐が可能である.enum は,すべてSystem.Enum から派生した値型となる. Java ではenum の替わりにクラスやインターフェイスのpublic static final フィールドを使う などすることが多い.この場合,対応する整数値は自分で与える必要がある.

C# では構造体(struct)は値型として扱われる. よって class を使うよりもメモリ効率が良く,特に大量のオブジェクトを生成する場合などに 利用される.Java にはこれに対応するものはない. 実は,C# で予約語になっている int という型は,System.Int32 構造体の別名となっている.


  print 文のフォーマット

C# は,Java より少し分かりやすい print 文のフォーマット機能を有している.


C#
string name = "Aska";
int age = 5;
Console.WriteLine("{0} is {1} years old.", name, age);
Java
String name = "Aska";
int age = 5;
System.out.println(name + " is " + age + " years old.");

C# の方が,全体の文章が見えて読みやすい.Java は文章が途切れ途切れに なる.C# は,後述する可変長引数の機能を使って,C 言語の printf 形式 に似たフォーマット記述が可能である.余談だが,言語が オブジェクト指向に近付くにつれて print 文の記述が見にくくなる, と指摘した方がいた. 読みやすさの順では,COBOL > C >> C++ > Java ではないだろうか. C# のフォーマットは,C 言語よりも見やすい.

  多次元配列

C# の配列のシンタクスは java とほぼ同じである.配列を組み合わせて, 「配列の配列」を作ることもできる.さらに,Javaにはない機能として, 多次元配列がサポートされている.これは,配列の添え字を","(カンマ) で区切ることにより,複数の添え字で要素にアクセスできる機能である.

  // C# でも Java でも正しいコード
  // 多次元配列の例
  int [] a1 = new int[] { 1, 2, 3 };  // 1次元配列の生成と初期化

  // 配列の配列も可能
  int [][] aa =  new int[3][];
  aa[0] = new int[] {1, 2, 3};
  aa[1] = new int[] {11, 12, 13, 14};
  aa[2] = new int[] {21, 22, 23, 24, 25};

  // ここからは,C# のみ正しいコード
  // 多次元配列の例
  int [,] a2 = new int[3,3];           // 3x3 2次元配列の生成
  int [,] a3 = new int[,] {{1, 2, 3}, {11, 12, 13}}; // 3x2 2次元配列の生成と初期化

  メソッド引数

C# のメソッドの仮引数には,

  • 値引数
  • 参照引数
  • 出力引数
  • 可変長引数

がある.値引数は通常の入力引数に使われるもので,引数の変更が メソッド呼び出し側に影響しない.Java の通常の引数と同じ である.参照引数は,引数の変更が呼び出し元に伝わる形式である. 出力引数は,参照引数とセマンティクスは同じだが, 引数を渡す時点で変数が初期化されていなくてもよいという特徴がある.

class Test {
 // 交換(うそ)
 static void FakeSwap(int x, int y) {
  int t = x; x = y; y = t;
 }
 // 交換
 static void Swap(ref int x, ref int y) {
  int t = x; x = y; y = t;
 }

 // 割算
 static void Divide(int x, int y, out int ans, out int rem) {
  ans = x / y; // 商
  rem = x % y; // 余
 }

 // 可変長
 static void Print(params int[] args) {
   for (int i = 0; i < args.Length; i++)
     Console.WriteLine("arg[{0}] = {1}", i, args[i]);
 }

 static void Main() {
    int a = 1;
    int b = 2;

    FakeSwap(a, b);  // 何事も起らない(a == 1, b == 2)
    Swap(ref a, ref b);  // 今度は交換(a == 2, b == 1)

    int c, d;
    Divide(10, 3, out c, out d);  // c, d に値が入る(c == 3, d == 1)

    Print(a, b, c, d); // 全部の変数をプリント
    Print(new int[] {1, 2, 3}); // 配列をプリント
 }
}

上記コードで特徴的なのは,引数のタイプによってメソッドを呼び出す側 が明示的に,ref, out というキーワードを付けて呼び出さなければならない 点である.これは,呼び出す側の勘違いを減らす利点がある.

可変長引数も,場合によっては便利なケースがある.例えば, Console.WriteLine が可変長引数を持つメソッドの代表例である.

イベントとデレゲート

C# はイベント伝達にユニークな手法を提供している.これは おそらく RAD を意識しながら Java のイベントモデルを 言語サポートしたもとの思われる.Delphi からも大きな影響が 見える.この手法によれば,Java のイベントモデルと同様のこと が,ライブラリやコーディングコンベンションではなく言語によって 強くサポートされる.デレゲートにより,C# では開発環境も含めた ビジュアル GUI プログラミングが促進されることになるだろう.

delegate キーワードによって,「関数ポインタ」のような実行対象の カプセル化が可能になる.デザインパターン的にはCommandパターン と言って良いし,Java 風に言えばメソッド1つのみを持つ interface に対応する. delegate は,その実行対象がインスタンスメソッドであっても, クラスメソッドであっても,呼び出しシグニチャを満たすものであれば それをカプセル化することができる.デレゲートのインスタンスを 引き数として渡すことで,実行を委譲することが 可能になる(delegateは委譲と和訳されることが多い).

この仕組みによって,GUI 部品であるボタンが押されたときの ような処理を,GUI 部品側とクライアント側にうまく分割することが できる.Java はイベントモデルによってこれを実現しているが, C# は一歩これを進めて言語仕様に取り込んでいるように見える. Java のイベントモデルに於けるイベントリスナー・インターフェイス の役割として,デレゲートを用いることができる.


C#
// GUI 部品側

// ボタンクラス
public class Button
{
  public event ButtonEventHandler Click;  // イベントハンドラ
  // event には,自動的に delegate の取り外し
  // +=, -= 演算が定義されている.
}




// ボタンイベント
public struct ButtonEvent {
   // ....
}

// デレゲートの宣言
public delegate void ButtonEventHandler(object sender, ButtonEvent e);



// クライアントコード
public class MyForm1
{
  // ボタンの生成
  Button Button1 = new Button();

  // ボタンが押された時のコールバック
  void Button1ClickCallback(object sender, Event e) {
     Console.WriteLine("Button1 がクリックされた");
  }

  // コンストラクタ
  public MyForm1() {
    // コールバックメソッドの登録
    Button1.Click += new ButtonEventHandler(Button1ClickCallback);
  }

  // イベント伝播の切断
  public void Disconnect() {
    // コールバックメソッドの削除
    Button1.Click -= new ButtonEventHandler(Button1ClickCallback);
  }
}
Java
// GUI 部品側

// ボタンクラス
public class Button {
  List listeners = new Vector();
  void addButtonEventListener(ButtonEventListener l) {
     listeners.add(l);
  }
  void removeButtonEventListener(ButtonEventListener l) {
     listeners.remove(l);
  }
}

// ボタンイベント
public class ButtonEvent {
  // ....
}

// ボタンイベントのリスナ・インターフェイス
public interface ButtonEventListener {
  void buttonClicked(ButtonEvent e);
}

// クライアントコード
public class MyForm1 implements ButtonEventListener {
  // ボタンの生成
  Button button1 = new Button();

  // ボタンが押された時のコールバック(リスナ・インターフェイスの実装)
  public void buttonClicked(ButtonEvent e) {
     System.out.println("Button1 がクリックされた");
  }


  // コンストラクタ
  public MyForm1() {
    // コールバックメソッドの登録
    button1.addButtonEventListener(this);
  }

  // イベント伝播の切断
  public void disconnect() {
    // コールバックメソッドの削除
    button1.removeButtonEventListener(this);
  }
}

C# では,イベントソース側は,デレゲート型のシグニチャ(ButtonEventHandler)を宣言し, Buttonクラスに event 型のデータメンバを用意する. リスナーの列を自前で保持する必要がなく,event 型の +=, -= 演算によってリスナの取り外しが行える. クライアント側(MyForm1 側)も,任意の名前のメソッドをコールバックに設定することができ, さらにリスナー・インターフェイスを実装する必要もない. Java に比べて,イベントソース側/リスナ側のいずれものコードがすっきりする. より簡便にイベントの授受を表現していると言えよう. さらに,Event の型を struct にすることによって,ガベージコレクトのオーバーヘッドも防ぐ, というパフォーマンス上の利点もある.


プロパティ

C#には,プロパティとよばれる特殊なメンバがある. JavaBeans のプロパティと対応関係がある.イベント同様, Java の考え方を言語によってサポートしようとしている.


C#
// ボタンクラス
public class Button
{
  private string caption;
  public string Caption {
    get {
      return caption;
    }
    set {
      caption = value;
      Repaint();
    }
  }
}

// クライアントコード

  Button b = new Button();
  b.Caption = "Example Button";  // set が呼ばれる
  string caption = b.Caption;    // get が呼ばれる
  b.Caption += "(1)";            // get/set が呼ばれる
Java
// ボタンクラス
public class Button {
  private String caption;
  public String getCaption () {
     return caption;
  }
  public void setCaption(String value) {
     caption = value;
     repaint();
  }
}




// クライアントコード

  Button b = new Button();
  b.setCaption("Example Button");
  String caption = b.getCaption();
  b.setCaption(b.getCaption() += "(1)");

C# のプロパティは,クライアントからは public なインスタンス変数に見え, アクセスのためのシンタクスもインスタンス変数と同じだ. しかし,上記ボタンクラスの Caption のように,get/setと名前の付いたコードが書け, 自動的にこのコードが値の取得・設定時に呼び出される. set 時の仮引数は,value という名前に予約されている. JavaBeans においても,set/get メソッドの命名規則によりプロパティが定義されており, リフレクション機能を用いて実行時に外部からプロパティの一覧を取得することができる. C# では,これを一歩進め,言語によってプロパティをサポートしている.Visual Basic などでサポートされている機能であり,これも RAD を強く意識している.

インデクサ

C# には,もう1つ,通常のメソッドとはシンタクスを異にするメソッド定義の機能がある. それは,添え字をサポートするもので,インデクサと呼ばれる. 上述のプロパティと少し似ており,get/set の組で定義を行う.


C#
// ベクトルクラス
public class Vector {
  private object[] objs;
  // ....
  public Vector(int size) {
     objs = new objects[size];
  }
  public object this[int index] {
     get {
       if (objs.Length <= index) {
          throw new Exception("Index out of range.");
       return objs[index];
     }
     set {
       if (objs.Length <= index) {
          throw new Exception("Index out of range.");
       objs[index] = value;
     }     
  }
}

// クライアントコード

  Vector v = new Vector(3);
  v[0] = 1;         // set が呼ばれる
  v[1] = v[0] + 1;  // get/set が呼ばれる v[1] == 2
  v[1] += 2;        // get/set が呼ばれる v[1] == 4
 
Java
// ベクトルクラス
public class Vector {
  private Object[] objs;
  // ....
  public Vector(int size) {
    objs = new Objects[size];
  }
  public Object get(int index) {
    if (objs.length <= index) {
       throw new ArrayOutOfBoundException("Index out of range.");
    return objs[index];
  }
  public void set(int index, Object o) {
    if (objs.length <= index) {
       throw new ArrayOutOfBoundException("Index out of range.");
    objs[index] = value;
  }
}



// クライアントコード

  Vector v = new Vector(3);
  v.set(0, new Integer(1));   // v[0] = 1
  v.set(1, new Integer((Integer)(v.get(0)).intValue()+1)); // v[1] = v[0]  + 1
  v.set(1, new Integer((Integer)(v.get(1)).intValue()+2)); // v[1] += 2;

インデクサを使うと,オブジェクトに対する添え字シンタクスがサポートされる. 場合によってはクライアントコードの可読性を向上することができる.

C# では,int などの値型も object クラスから派生しているため, 特にコンテナ系のクラスではインデクサと共に利用することでクライアントコードが大幅に読みやすくなる.

マルチスレッド

C# でも,マルチスレッドは言語に取り入れられている. Java の synchronized に良く似た lock キーワードが定義されており, 次のような使用が可能である.

C# Java
public class Point
{
  private int x, y;
  public void Set(int x, int y) {
    lock(this) {
       this.x = x;
       this.y = y;
    }
  }
  // ...
}
public class Point {
  private int x, y;
  public void set(int x, int y) {
    synchronized(this) {
      this.x = x;
      this.y = y;
    }
  }
  // ...
}

クラスコンストラクタ

C# には,通常のコンストラクタ(インスタンスコンストラクタと呼ばれる)以外に, クラスコンストラクタと呼ばれるメソッドが定義できる. これは,クラスが最初にロードされる際に1度だけ実行されるメソッドであり, Java の static イニシャライザに相当する.

C# Java
class MyClass
{
  static MyClass() {
    //...クラスロード時に一度だけ走るコード
  }
  public MyClass() {
    //...インスタンス生成時に走るコード
  }   
}
class MyClass {
  static {
    //...クラスロード時に一度だけ走るコード
  }
  public MyClass() {
    //...インスタンス生成時に走るコード
  }   
}

共に機能は同じだが,C#の方がネーミングとシンタクスに一貫性が あるかもしれない.

unsafe コード

C# では,C 言語を直接 C# 中に埋め込むことができる.これは, 丁度 C 言語がアセンブリ言語を asm キーワードによって インラインに埋め込めるのと似ている. 下記のコードは,配列要素のアドレスを書き出すものだ.

class UnsafeTest {
  unsafe static void PrintAddress(byte[] array) {
      fixed (byte *p_array = array) {
         byte *p_elem = p_array;
         for (int i = 0; i < arr.Length; i++) {
            byte value = *p_elem;
            string addr = int.Format((int) p_elem, "X");
            Console.WriteLine("array[{0}] at 0x{1} is {2}", i, addr, value);
            p_elem++;
         }
      }
  }
}

Java では,このようなネイティブなコードを呼び出す場合, JNI(Java Native Interface)を使う必要がある. C コードを直接C# テキスト中に埋め込むことができる, という言語設計は,Java とは全く違ったコンセプトである.

   おわりに

C# の言語仕様を,Java と比較しながら駆け足で紹介した. ここで紹介できなかった他の機能に,クラスのオペレータ多重定義機能, クラスに URL を結び付け,それを実行時に取り出せる attribute 機能などが用意されている. これらは Javaには無い機能である. また,C++ にある template 機能は,C# でも Java 同様用意されていない.

C# の言語仕様を概観すると,それが Java の仕様,および JavaBeansをはじめとする Java の周辺仕様をうまく取り入れ,言語としてより成熟したものとなっている点が評価される. 筆者は特にRAD を推進する機能として, デレゲートとプロパティを言語仕様として取り込んでいることを評価している.

しかし,これだけ Java と似た言語が, 現在のプログラマに受け入れられるかどうかは定かではない. おそらく,開発環境および.NET との組で考えなければならない言語であることは間違いがなさそうである.


   参照文献


   謝辞

この記事の構想および洗練に多くの方々からコメントとヒントを頂いた. 特に, 畑匠美さん,海内利之さん,矢崎博英さん, 上手裕さん, 森下民平さん, 友野晶夫さん, 山田健志さん, 藤本聖さん, 岡村敏弘さん, 瀬戸川教彦さん, 名須川竜太さん, 小野修司さん, 中村正さん, に感謝します.ありがとうございました.


Kenji Hiranabe <hiranabe@esm.co.jp> Last modified: Sun Nov 12 18:39:03 2000



この記事への評価にご協力をお願いします。

良かった 普通 イマイチ