[ Home on 246net ] [ C# Top ]

JavaプログラマーのためのC#習得

C#、それはマイクロソフトの放つプログラミング言語である。
そこにはJavaプログラマーの想像を絶する新しい仕様、新しいオブジェクトが待ち受けているに違いない。

このページは、C#の習得に踏み込んだJavaプログラマー歴15年間の筆者の驚異に満ちた物語である・・・。

前書き

Javaプログラマーにとって、C#を習得することは簡単ではないかもしれません。確かに字面は似ている点も多いでしょう。しかし、似て非なることがあり、それはちゃんと理解していないと落とし穴となってしまいます。まったく違った方が幸いかもしれません。

また、Javaプログラマーから見ると、C#の言語仕様、命名規約、様々な慣習は、最初受け入れることが困難なことがあります。Javaがシンプルさを追求しているとしたら、C#は多様性を追求していると思います。Javaの世界から見ると、C#は実にごった煮な世界です。いいたいことも山ほどあります。しかし、言語は文化ですから、ある文化から異文化を見て、単に批判していても何の解決にもなりません。ここは、違いは違いとして文化のギャップをいかに乗り越えるかについて考えていきます。

開発環境について

C#もプログラミング言語ですから、エディタとコマンドラインコンパイラで開発できるはずですが、きっぱりそれはあきらめた方が、文化ギャップを乗り越える近道です。C#は、Microsoftが作る言語ですから、Microsoftの商用開発製品であるVisual Studioに密接に絡んでいます。Visual Studioのバージョンアップ毎にC#言語仕様もアップしています。Visual Studioの機能のために作ったと思える言語仕様もあります。ここは、Visual Studioを通してC#を見ることで、その言語を理解するのが早道と考えます。

幸い、現在は無償版のVisual Studio Express版が提供されているので、これを使ってC#プログラミングを習得します。

Hello Worldから

最初は、Hello Worldを通して、最初のC#を理解していきます。

まず、JavaでHello Worldを書き、それと対比してC#でHello Worldを書きます。ここでは、コンソール(コマンドプロンプト)から実行するCUIプログラムから入ります。

Hello Worldのソースコード

JavaにおけるHello Worldソースコード

package jp.gr.java_conf.torutk.hello;

/**
 * コンソールに挨拶文を表示するHelloWorldクラス。
 *
 * @author TAKAHASHI,Toru
 * @version 1.0
 */
public final class HelloWorld {

    /**
     * <code>main</code>メソッド
     *
     * @param args コマンドライン引数
     */
    public static void main(final String[] args) {
        System.out.println("Hello world");
    }

}

C#におけるHello Worldソースコード

namespace JavaConf.Torutk.Hello
{
    /// <summary>
    /// コンソールに挨拶文を表示するHelloWorldクラス。
    /// </summary>
    public sealed class HelloWorld
    {
        /// <summary>
        /// プログラムのエントリポイント
        /// </summary>
        /// <param name="args">コマンドライン引数</param>
        public static void Main(string[] args)
        {
            System.Console.WriteLine("Hello world");
        }
    }
}

ここで登場した機能は以下です。

Javaの文法要素  C#の文法要素  説明  備考 
 package namespace  C#では、クラスをグループ化する単位はnamespace
Microsoftが提唱する命名規約は
<会社名>.(<製品名>|<技術名>)[.<機能名>][.<細部名前空間>]
例) Microsoft.WindowsMobile.DirectX
http://msdn.microsoft.com/ja-jp/library/ms229026.aspx 
名前はPascal形式
 
 クラス修飾 final クラス修飾 sealed  C#では、サブクラスを許さないクラスにsealedを修飾する   
 mainメソッド Mainメソッド  C#ではプログラムのエントリポイントになるメソッドはMain   
 引数修飾 final 該当なし  C#では、引数(参照値)を書き換え不可にする手段がない   
 String string  C#では、System.Stringクラスに対してstringのエイリアスを持つ   
 System.out.println System.Console.WriteLine  C#では、標準入出力を管理するクラスSystem.Consoleに のWriteLineメソッドを呼ぶ  
 /** コメント */ /// コメント  C#では、ドキュメント化コメントは///で書くか、/** */で書くか選べる。
Javaではドキュメントコメントの先頭行が概要として扱われるが、C#では、<summary>タグで囲まれた部分となる。
著者(@author)、バージョン(@since)に対応するタグはC#にはない。
引数(@param)は、C#では<param>タグが対応する。
 

HelloWorldのコンパイルと実行

コンパイラコマンドによるコンパイル

Javaの場合

ソースファイルは、package名に対応するディレクトリに置きます。

C:\project\hello> javac -d classes src\jp\gr\java_conf\torutk\hello\HelloWorld.java
C:\project\hello> java -cp classes jp.gr.java_conf.torutk.hello.HelloWorld
Hello world
C:\project\hello> 

Javaは、コンパイルすると、クラスファイル(HelloWorld.class)が生成されます。
実行するときは、Java仮想マシンにそのクラス名を指定します。

C#の場合

この例では、コマンドラインでコンパイルと実行をします。ソースファイルの置き場所はどこでもいいようです。

C:\project\hello> csc /out:bin\HelloWorld.exe src\HelloWorld.cs
C:\Project\hello> bin\HelloWorld.exe
Hello world
C:\Project\hello>

C#は、コンパイルするとアセンブリとよぶファイルを生成します。アセンブリにはエントリポイントを持ち実行可能なEXEファイルと、他のEXEファイルから利用されるDLLファイルがあります。
実行するときは、EXEファイルをWindowsの実行ファイルと同様に指定します。
EXEファイルといっても、ネイティブなコードではなく、EXEを実行すると.NET Frameworkが起動され、EXEファイル内にある中間コードがロードされ、JITコンパイラでネイティブコードにコンパイルされて実行されます。

コーディングスタイルの違い

JavaとC#とのコーディングスタイルの違いをいくつか挙げておきます。スタイルなので、慣れの問題もありますが。

項目  Java  C#  備考 
 メソッド名の命名規約 Camel式  Pascal式   型とメソッドを一目で識別したいのでCamel式が好き
 パッケージ(名前空間)の命名規約 全て小文字  Pascal式   パッケージ名と型名が区別できる全て小文字が好き
 波括弧の位置 ブロック開始行の行末  ブロック開始行の次の行の先頭   慣れの問題も含め、コンパクトに見えるJavaの慣習が好き。
特にtry-catch-finallyが顕著。
 パッケージ(名前空間)内のインデント package文はブロックでないので、インデントなし  namespaceはブロックなので内部はインデント  namespaceによるインデントは余計なインデントを課すので気に入らない 

C#ここが嫌い

全体を通して

首尾一貫していないところ
書く手間を惜しむ機能を載せて、読む手間がかえって増えるような言語仕様追加が多い、しかも中途半端な追加
バージョンアップ毎に、言語として変わりすぎ

命名に関して

なぜObjectとobject、Stringとstringがあるのか

ObjectクラスとStringクラスに、別名でobjectとstringが用意されている点

名前空間と型名とメンバー名が命名規約では識別不可能

名前空間、型名、メソッド名/プロパティ名が、いずれもPascal式命名を規約としているため、表現上(字面上)では区別できない。

C#の例
System.Drawing.Drawing2D.GraphicsPath.StartFigure
Javaの例
java.awt.geom.GeneralPath.closePath

インタフェースの命名規約だけハンガリアン

インタフェースの名前は接頭辞'I'で始めるのが命名規約です。C#では、インタフェースの実装とクラスの継承を文法的に区別していないので、この規約がないと可読性は確かに大きく劣化します。

言語仕様に関して

アクセス制限

internalというアクセス制限

デフォルトが仮想でないメソッド

C#では、メンバー(メソッド等)がデフォルトでは仮想でないため、ポリモーフィズム(多態性)を活用するためには、明示的にvirtual宣言をする必要があります。なんとC++の亡霊がここに現れました。

また、非仮想メソッドをサブクラスで再定義するときは、new修飾子を付けないとコンパイル時に警告が出ます。なんですしょうか、このnewキーワードの脈絡ない使用は・・・。

基底クラスで実装したインタフェースのメソッドが派生クラスで遮蔽される

デフォルトが非仮想なので、基底クラスで実装したインタフェースのメソッドを派生クラスで再定義するとオーバーライドではなく遮蔽定義となってしまいます。("Effective C# 2nd Edition", Item 23)

 ケース1) デフォルト ケース2) new   ケース3) virtualとoverride
interface IMsg {
  void Message();
};
public class MyClass : IMsg {
  public void Message() {
    Console.WriteLine("MyClass");
  }
}
  
 
public class MyClass : IMsg {
  public virtual void Message() {
    Console.WriteLine("MyClass");
  }
}
 
public class MyDerivedClass : MyClass {
  public new void Message() {
    Console.WriteLine("MyDerivedClass");
  }
}
 
public class MyDerivedClass : MyClass, IMsg {
  public new void Message() {
    Console.WriteLine("MyDerivedClass");
  }
}
 
public class MyDerivedClass : MyClass {
  public override void Message() {
    Console.WriteLine("MyDerivedClass");
  }
}
var d = new MyDerivedClass;
d.Message();  // "MyDerivedClass"が印字
IMsg i = d as IMsg;
i.Message();  // "MyClass"が印字
MyClass b = d;
d.Message();  // "MyClass"が印字
 
var d = new MyDerivedClass;
d.Message();  // "MyDerivedClass"が印字
IMsg i = d as IMsg;
i.Message();  // "MyDerivedClass"が印字
MyClass b = d;
d.Message();  // "MyClass"が印字
var d = new MyDerivedClass;
d.Message();  // "MyDerivedClass"が印字
IMsg i = d as IMsg;
i.Message();  // "MyDerivedClass"が印字
MyClass b = d;
d.Message();  // "MyDerivedClass"が印字

それを避けるため、派生クラスでインタフェースを再実装宣言するか、基底クラスでvirtual宣言して派生クラスでoverrideするかをします。それぞれ少し動作が違う点があるので要注意です。

デリゲート

Javaプログラマーがデリゲートの存在を心から受け入れることはできるか?

プロパティ

結局のところ、アクセッサー・メソッドのシンタックスシュガーですが、トイプログラムレベルでしか簡潔にならないので、言語仕様にいらぬ複雑性を招いているように見えます。

全般

プロパティのメリットで語られるのは大よそ次の3つです。

  1. Getter、Setterを書かなくてよいので楽
  2. 構造体のメンバーに値を代入する書き方ができるのでうれしい
  3. ツール等でクラスのプロパティを拾い挙げるのに命名規約でなく言語仕様で取れる

1. については、C#3.0で追加された自動プロパティのお任せパターンを除けばメソッドとほぼ同じ記述をするので、別な文法・構造体系で記述しなければならない分負担です。

Java v.s. C# set/getメソッド v.s. プロパティ
 Java C# 
 
    private int remain;

    public int getRemain() {
        assert 0 <= remain && remain < 10 : 
            "remine should be [0,10) but " + remain;
        return remain;
    }

    protected void setRemain(int aRemain) {
        assert 0 <= aRemain && aRemain < 10 :
            "remine should be [0,10) but " + aRemain;
        remain = aRemain;
    }
private int _remain;

public int Remain
{
    get
    {
        Debug.Assert(0 <= _remain && _remain < 10,
                     "_remain should be [0,10) but " + _remain);
        return _remain;
    }

    protected set
    {
        Debug.Assert(0 <= value && value < 10,
                     "remain should be [0,10) but " + value);
        _remain = value;
     }
}

2. については、Getメソッド、Setメソッドで書くことを忌避する理由が分かりませんが、可読性の観点から言えば、メンバー変数への代入形式で書かれた式が、真にメンバー変数への代入なのか、プロパティにより裏でメソッド呼び出しに変換されているものか、クラス定義を見ないと判断できないというデメリットを感じます。

previousRemain = storage.Remain;
...
storage.Remain = previousRemain - consumed;

このコードを見て、Remainがpublicなメンバー変数かプロパティかは区別がつきません。クラス定義を見て初めて分かります。

別な観点として、オブジェクト指向プログラミングではないパラダイム(Visual Basicなど)からC#へ移行する場合にはプロパティの方が受け入れやすいのかもしれません。C#は、良いか悪いかはさておき、ごった煮的な面があります。

3. については、ツール作成者としては便利かなと思いますが、Javaでもアノテーションが利用できるようになったので、現時点では特に差はないと思います(JavaBeans仕様は互換性上残り続けますが)。

アクセス修飾子の矛盾

getとsetで異なるアクセス修飾子を指定すると、

public Name
{
    get;
    private set;
}

例のコードでは、プロパティ Name のpublicと、setのprivateと2つのアクセス制限がかかっており、汚い。

名前空間

usingで指定できるのは名前空間だけ

javaで言えば、import java.awt.*; のような記述しかできないので、コード中に直接記述される型がどの名前空間に属するかはコード上(字面上)からは判別できず、記憶に頼るか、Visual StudioのC#エディタ上で型名の上にカーソルを持っていってポップアップを確認しないとならない。

using Console = System.Console; と書けば、型名まで指定できるが、これはエイリアス的機能で本来の使い方ではないし、冗長である。

ドキュメントコメント

ビルド・実行に関して

結局デバッグビルドとリリースビルドでバイナリを2種類用意しないと駄目なところ

Visual Studio C#について

Express版では制約が多すぎ、個人では有償版はよほど意気込みがないと買えないなぁ

情報源

JavaとC#の比較