[ Home on 246net ] [ C# Top ]
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を通して、最初のC#を理解していきます。
まず、JavaでHello Worldを書き、それと対比してC#でHello Worldを書きます。ここでは、コンソール(コマンドプロンプト)から実行するCUIプログラムから入ります。
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");
}
}
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>タグが対応する。 |
ソースファイルは、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:\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によるインデントは余計なインデントを課すので気に入らない |
首尾一貫していないところ
書く手間を惜しむ機能を載せて、読む手間がかえって増えるような言語仕様追加が多い、しかも中途半端な追加
バージョンアップ毎に、言語として変わりすぎ
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. については、C#3.0で追加された自動プロパティのお任せパターンを除けばメソッドとほぼ同じ記述をするので、別な文法・構造体系で記述しなければならない分負担です。
| 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つのアクセス制限がかかっており、汚い。
javaで言えば、import java.awt.*; のような記述しかできないので、コード中に直接記述される型がどの名前空間に属するかはコード上(字面上)からは判別できず、記憶に頼るか、Visual StudioのC#エディタ上で型名の上にカーソルを持っていってポップアップを確認しないとならない。
using Console = System.Console; と書けば、型名まで指定できるが、これはエイリアス的機能で本来の使い方ではないし、冗長である。
Express版では制約が多すぎ、個人では有償版はよほど意気込みがないと買えないなぁ