以下の説明は2001年1月くらいに作成されたので、あちこちヘンみたい。 とりあえずVS.NETβ2でもコンパイルできるようにちょこっと直したけど、仕様については間違っている可能性があるので注意してね。 (22 Oct 2001)
Microsoft社の新言語 C# (C Sharp) の雑感。とりあえず今現在はWindows 2000 Professional, PentiumIII 766MHz, Memory 256MBなんて環境で実行しているので、パフォーマンスについては適当に勘案してね。
なにはともあれまず.NET Frameworkをインストールする必要があるぞ。これは MSDN Online .NET Information から入手できる。111MBもあるから、常時接続かテレホーダイなヒトじゃないとイタいかもね。
取ってきた自己解凍EXEを実行するとSetup.exe他にバラけるので、このSetup.exeを実行する。こいつは生意気なことに IE5.5 がインストールされていないとセットアップを中断してしまうので、必要があれば先に入れておく。途中、環境変数に登録するか確認されるので、チェックしたままにしておく。これが嫌なヒトはあとでPATHを通した環境を用意する必要がある。別に再起動するでもなしにインストール完了。
そうそう。MDAC2.6 というのも入れておく必要がある。これはなくても一見インストールが成功したように見えるんだけど(単体では CSC.EXE も起動できる)、実際にはコンパイルしてみるとクラスライブラリを見つけられなくて困る。その場合、MDAC2.6 をインストールした後、再度 .NET SDK をインストールしないといけないみたい。なんで IE はチェックして、MDAC はチェックしないんだ?
じゃあ早速、簡単なアプリケーションを書いてみよう。栄えある最初のプログラムは例のパターンでこれだ。
using System;
class Yahoo {
public static void Main() {
Console.WriteLine("やっほう");
}
}
テキストエディタで書いたこれを"Yahoo.cs"というファイル名で保存する。涙がでるほど単純だけど、まあ実行してみよう。コマンドプロンプトを開いて、今のファイルを保存したディレクトリに移動する。で。
> csc Yahoo.cs Microsoft (R) C# Compiler Version 7.00.8905 [NGWS runtime 2000.14.1812.10] Copyright (C) Microsoft Corp 2000. All rights reserved. > Yahoo やっほう
をお。あっさり実行したぞ。簡単だ。何の衒いもなく日本語が通るし。
この csc.exe が VC++ の cl.exe に当たるコンパイラ本体。csc /? をインチキ訳してみるとこんな感じ。嘘があるかも。cl.exe の複雑怪奇なオプションに比べるとあっさりしてるよね。
| - OUTPUT FILES - | |
|---|---|
| /out:<file> | 出力ファイル名を指定する。(無指定の場合は最初のソースファイル名からとってつける) |
| /target:exe | コンソール用実行ファイルを生成する。(デフォルト) (短縮形: /t:exe) |
| /target:winexe | Windows実行ファイルを生成する。(短縮形: /t:winexe) |
| /target:library | ライブラリを生成する。(短縮形: /t:library) |
| /target:module | 別の構成に追加可能なモジュールを作成する。(短縮形: /t:module) |
| /win32icon:<file> | 出力ファイルにこのアイコンを使用する。 |
| /nooutput[+|-] | コードのエラーをチェックするだけ。実行ファイルは出力しない。 |
| /define:<symbol list> | シンボルを設定する。(短縮形: /d) |
| /doc:<file> | XML文書を生成するためのファイルを指定する。 |
| - INPUT FILES - | |
| /recurse:<wildcard> | ワイルドカードでサブディレクトリ内のファイルを指定する。 |
| /main:<type> | エントリポイントが含まれるタイプを指定する。(それ以外のすべてのエントリポイントは無視される) (短縮形: /m) |
| /reference:<file list> | 指定された構成ファイルから参照するメタデータを指定する。 (短縮形: /r) |
| /addmodule:<file list> | モジュールをリンクする。 |
| - RESOURCES - | |
| /resource:<resinfo> | リソースを埋め込む。(短縮形: /res) |
| /linkresource:<resinfo> | リソースをリンクする。(短縮形: /linkres) |
| - CODE GENERATION - | |
| /debug[+|-] | デバッグ情報を出力する。 |
| /optimize[+|-] | オプティマイズを有効にする。(短縮形: /o) |
| /incremental[+|-] | インクリメンタルコンパイルを有効にする。(短縮形: /incr) |
| - ERRORS AND WARNINGS - | |
| /warnaserror[+|-] | 警告をエラーとして扱う。 |
| /warn:<n> | 警告を指定する。(0-4) (短縮形: /w) |
| /nowarn:<warning list> | 指定した警告メッセージを無効にする。 |
| - LANGUAGE - | |
| /checked[+|-] | オーバーフロー/アンダーフローチェックを生成する。 |
| /unsafe[+|-] | 'unsafe'なコードを許可する。 |
| - MISCELLANEOUS - | |
| @<file> | ファイルからオプションを読む。 |
| /help | 使用法を表示する。(短縮形: /?) |
| /nologo | コンパイラの著作権メッセージを表示しない。 |
| - ADVANCED - | |
| /baseaddress:<address> | ライブラリを生成するためのベースアドレスを指定する。 |
| /win32res:<file> | バージョンとアイコンの WIN32 リソースファイルを指定する。 |
| /bugreport:<file> | 'Bug Report'ファイルを生成する。 |
| /codepage:<n> | ソースファイルを開く際に使用するコードページを指定する。 |
| /fullpath | 完全修飾パスにコンパイルするように指定する。 |
| /nostdlib[+|-] | 標準ライブラリを参照しないように指定する。(mscorlib.dll) |
次にWindowsアプリケーションを書いてみる。GUIが簡単に書けないと最近はウケが悪いからね。で、結論から言うと、今のところそんなに恵まれた状況とは言えない。じきにちゃんとしたリソースエディタが提供されるとは思うけど、今は WinDes.exe とかいうちょっと不安定なツールしかないからね。使い方は直感で結構イケるんだけど、今試したリビジョンだとちょっとでもソースをエディットしちゃうと再び WinDes.exe で編集することができなくなる。まあしばらくはこれで我慢するしかないね。んで、WinDes.exe で作ったソースをエディットしてそれっぽいコードを作ってみる。ちょっと長いよ。
// YahooWin.cs
namespace Septigram {
using System;
using System.Drawing;
using System.ComponentModel;
using System.Windows.Forms;
/// <summary>
/// フォームを使ったサンプル。
/// </summary>
public class YahooWin : System.Windows.Forms.Form {
/// <summary>
/// 内部コンポーネント。
/// </summary>
private System.ComponentModel.Container components;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
// 手。
private static string[] hands = { "ぐー", "ちょき", "ぱー" };
// 乱数生成器
private Random rnd = new Random();
// 遊んだ回数。
private int times = 0;
// 勝った回数。
private int wins = 0;
/// <summary>
/// コンストラクタ。内部コンポーネントを構築する。
/// </summary>
public YahooWin() {
InitializeComponent();
}
/// <summary>
/// 使い終わったリソースを破棄する。
/// </summary>
public override void Dispose() {
base.Dispose();
components.Dispose();
}
/// <summary>
/// アプリケーションのためのエントリポイント。
/// </summary>
public static void Main(string[] args) {
Application.Run(new YahooWin());
}
/// <summary>
/// コンポーネントを構築/配置する。
/// WinDes.exe の出力をもらうのであれば書き換える必要はない。
/// </summary>
private void InitializeComponent() {
this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
label1.Location = new System.Drawing.Point(8, 8);
label1.Text = "じゃーんけん";
label1.Size = new System.Drawing.Size(180, 24);
label1.Font = new System.Drawing.Font("MS UI Gothic", 21f,
System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.World);
label1.TabIndex = 0;
label2.Location = new System.Drawing.Point(170, 40);
label2.Text = "--%";
label2.Size = new System.Drawing.Size(180, 24);
label2.Font = new System.Drawing.Font("MS UI Gothic", 10f,
0, System.Drawing.GraphicsUnit.World);
label2.TabIndex = 0;
button1.Location = new System.Drawing.Point(8, 32);
button1.Size = new System.Drawing.Size(48, 24);
button1.TabIndex = 1;
button1.Text = "ぐー";
button1.Click += new EventHandler(ButtonClickHandler);
button2.Location = new System.Drawing.Point(64, 32);
button2.Size = new System.Drawing.Size(48, 24);
button2.TabIndex = 2;
button2.Text = "ちょき";
button2.Click += new EventHandler(ButtonClickHandler);
button3.Location = new System.Drawing.Point(120, 32);
button3.Size = new System.Drawing.Size(48, 24);
button3.TabIndex = 3;
button3.Text = "ぱー";
button3.Click += new EventHandler(ButtonClickHandler);
this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
this.Text = "やっほうういん";
//@design this.TrayLargeIcon = true;
//@design this.TrayHeight = 0;
this.ClientSize = new System.Drawing.Size(192, 64);
this.Controls.Add(button1);
this.Controls.Add(button2);
this.Controls.Add(button3);
this.Controls.Add(label1);
this.Controls.Add(label2);
}
/// <summary>
/// ボタンクリックのハンドラ。ディスパッチしてるけど許して。
/// </summary>
private void ButtonClickHandler(object sender, EventArgs e) {
int r = (int)(rnd.NextDouble() * 3);
if ((sender == button1 && r == 0) ||
(sender == button2 && r == 1) ||
(sender == button3 && r == 2)) {
this.label1.Text = hands[r] + " あいこー";
} else if ((sender == button1 && r == 2) ||
(sender == button2 && r == 0) ||
(sender == button3 && r == 1)) {
this.label1.Text = hands[r] + " かったー";
times++;
} else if ((sender == button1 && r == 1) ||
(sender == button2 && r == 2) ||
(sender == button3 && r == 0)) {
this.label1.Text = hands[r] + " まけたー";
times++;
wins++;
}
if (times != 0) {
label2.Text = (int)((double)wins / (double)times * 100) + "%";
}
}
}
}
これをコンパイルするのは手打ちでもいいんだけど、いろいろリソースをくっつけないといけないので下のような makefile を用意すると楽。いやこれだけじゃあんまりありがたみはないけど、将来必ずいいことがあるから(なんかの勧誘みたい)。これは本当にただ makefile って名前で保存する。メモ帳とかだと勝手に.txtって拡張子がついちゃうので注意が必要。
all: YahooWin.exe YahooWin.exe: YahooWin.cs csc.exe /t:winexe /r:System.Windows.Forms.DLL,System.DLL,System.Drawing.DLL $<
で、メイクしてみる。nmakeコマンドはVisualStudioとかに入っている。持っていなくても、サンプルを展開するとそのなかにある。このサンプルは、.NET FrameworkをインストールしたディレクトリのSamplesサブディレクトリの中に圧縮されているよ。終わったら実行。
> nmake Microsoft (R) Program Maintenance Utility Version 6.00.8168.0 Copyright (C) Microsoft Corp 1988-1998. All rights reserved. > YahooWin
とすると、こんな画面がでる。

JDKとテキストエディタで戦うよりは簡単かな。
System.WinFormsをSystem.Windows.Formsに変更。Microsoft.Win32.Interop.DLLを削除。(22 Oct 2001 修正)
レベルを想定するのが難しいんだけど、個人的な覚え書きなんだからいいか、と割り切った。Javaをある程度知ってるヒト向け。
| 型の種類 | 型名 | エイリアス型 | 値の範囲 | ||
|---|---|---|---|---|---|
| 値型 | 単純型 | 整数型 | sbyte | System.SByte | [8bit] -128 〜 +127 |
byte | System.Byte | [8bit] 0 〜 +255 | |||
short | System.Int16 | [16bit] -32,768 〜 +32,767 | |||
ushort | System.UInt16 | [16bit] 0 〜 +65,535 | |||
int | System.Int32 | [32bit] -2,147,483,648 〜 +2,147,483,647 | |||
unit | System.UInt32 | [32bit] 0 〜 +4,294,967,295 | |||
long | System.Int64 | [64bit] -9,223,392,036,854,775,808 〜 +9,223,392,036,854,775,807 | |||
ulong | System.UInt64 | [64bit] 0 〜 +18,446,744,073,709,551,615 | |||
char | System.Char | [16bit] Unicode キャラクタ。 | |||
| 浮動少数点型 | float | System.Single | [32bit] 1.5 × 10-45 〜 3.4 × 1038 精度 7 桁 | ||
double | System.Double | [64bit] 5.0 × 10-324 〜 1.7 × 10308 精度 15 〜 16 桁 | |||
| 真偽値型 | bool | System.Boolean | true あるいは false |
||
| 10進固定少数点型 | decimal | System.Decimal | [128bit] 1.0 × 10-28 〜 7.9 × 1028 精度 28 〜 29 桁 | ||
| struct型 | (任意) | – | – | ||
| 列挙型 | (任意) | – | – | ||
| 参照型 | クラス | (任意) | – | – | |
| インターフェイス | (任意) | – | – | ||
| デリゲート | (任意) | – | – | ||
| 配列 | (任意) | – | – | ||
| 文字列 | string | – | – | ||
| ポインタ型 | (任意) | – | – | ||
値型と参照型を必要に応じて相互変換する機能。これをC#では boxing と unboxing と呼んでいる。boxingが値から参照へ、unboxingがその逆方向への変換。Javaではプリミティブタイプとそのラッパークラスの変換を明示的にする必要があったけど、C#ではコンパイラがそれを自動でしてくれる。
// BoxingTest.cs
class BoxingTest {
static void Main() {
int n = 1;
object o = n; // boxing
System.Console.WriteLine("n:{0}\no:{1}", n, o);
int m = (int)o; // unboxing
o = 2; // m は影響を受けない。
System.Console.WriteLine("m:{0}\no:{1}", m, o);
}
}
unboxingする際に格納されている値と型互換性がなかった場合は、InvalidCastExceptionが発生する。
> BoxingTest n:1 o:1 m:1 o:2
それにしても誰か訳を教えてくれないかな?ボクシングじゃ通じないよねえ。
すべての単純型はビットパターンが 0 となるようなデフォルトコンストラクタを持つ。
char型は'\x0000', bool型はfalseとなる。
列挙型の初期値は 0 となる。
struct型は参照型フィールドも含め null で初期化される。
Javaで言う基本型にもコンストラクタがあるので、int i = new int();というような書き方も文法上はできるが、意味はない。
単純型は初期化にリテラルを用いることができる。構文はこう。decimal型の接尾辞mは新しい。
int i = 0; char c = '\x0020'; float f = 0.5f; double d = 0.0d; decimal m = 10.0m; bool b = true;
Javaの整数型とあんまり違うところはなさそう。
ただ、char型は文字コード専門で、他の整数型と暗黙の変換がなされない、という特徴がある。char型のリテラルにはエスケープ表現が使える。Javaにはなかった垂直タブもある。
| エスケープ表現 | 意味 |
|---|---|
\' | シングルクォート |
\" | ダブルクォート |
\\ | バックスラッシュ(円記号) |
\0 | ヌル文字 |
\a | ビープ |
\b | バックスペース |
\f | 改ページ |
\n | 改行 |
\r | キャリッジリターン |
\t | 水平タブ |
\v | 垂直タブ |
Javaのbooleanと特に違うところはなさそう。C/C++にあった非0をtrueとする変換もない。
Javaの浮動小数点型と特に違うところはなさそう。
財務計算などに使える高精度の10進演算を提供する型。java.math.BigDecimalのようなものだけど、値型なので軽いはず。精度が違うのでdoubleとの相互変換にはキャストが必要。
データメンバ以外にもクラスと同じように関数メンバ、ネスト型を定義できる。クラスとの違いは値型であること。C++のクラスはこっちに近いのかな。構文はこんな感じ。
struct Point
{
int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
エントリポイント public static void Main() も定義できるよ。
使用できる型は long, int, short, byte の4つ。構文はこう。
enum Color: int { Red, Green, Blue }
Cと同じで初期値や値を直接指定できる。
enum Months : byte { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }
enum Channel : int { NHK1 = 1, NHK2 = 3, NTV = 4, TBS = 6, CX = 8, ANB = 10, TX = 12 }
また、Javaと同じく無名配列(という名前じゃないかも)も使える。
class Test {
static void F(int[] arr) {}
static void Main() {
F(new int[] {1, 2, 3});
}
}
すべての参照型はobject型を継承している。Javaのjava.lang.Objectのようなもの。boxingがあるのでCOMのVARIANT型のような使い方ができる。そうするとあたりまえだけど、型チェックが実行時になるのでバグの温床にもなりうる。
内部にデータメンバ、関数メンバ、ネスト型を定義できる型。C/C++よりJavaのクラスに近く、すべて参照型になる。単一継承しかできないのもJavaと同じ。
Javaのインターフェイスと同じ。クラスはインターフェイスを複数実装できるのも同じ。
イベントハンドラなどの目的に特化したメソッドへの参照を提供する型。コールバック用の関数ポインタが進化した感じ。Javaの匿名クラスよりシンプル。
public class Test {
// 委譲するための関数ポインタ型宣言、という理解であってるかな?
public delegate void MessageEventHandler(string message);
// イベント型宣言
public event MessageEventHandler Update;
public static void Main() {
Test test = new Test();
// イベント型の引数としてその実装を与える
test.Update += new MessageEventHandler(test.Message_Update);
// 二つ書けば両方に配信される
test.Update += new MessageEventHandler(test.Message_Update);
// イベントを送ってみる
test.Update("hello");
}
// イベント受信してコンソールに出すというイベントハンドラの実装
void Message_Update(string message){
System.Console.WriteLine(message);
}
}
サンプルが二つ配信してるのは、C#MLのそういう話題から出てきたコードだから。戻り値がある(voidではない)関数の場合は、二つ以上イベントハンドラを追加できないそうだ。
配列も基本的にJavaと同様だけど、Javaにない多次元配列用の構文が用意されている。
char[] ns = { 'A', 'C', 'F' };
int[,] ps = { { 0, 2 }, { 1, 1 }, { 3, 4 } };
string[,,] cells = new int[ 5, 5, 5 ];
cells[1, 2, 1] = "猫";
実体はSystem.Arrayクラスなので、Lengthなどのプロパティ以外にもSortやBinarySearchというメソッドが利用できる。多次元配列の次数はRankというプロパティで参照できる。
文字列は string型 で扱う。java.lang.String と同じようなもの。java.lang.StringBuffer にあたるクラスは System.Text.StringBulder という。
unsafe指定されたメモリ空間に対してのみ許されるC/C++のポインタと同様の型、ならしい。よくわからない。
Java特有の変な演算子 >>> と >>>= はない。型情報演算子とオーバーフロー例外制御演算子、間接参照/アドレス演算子が Java にはなかった演算子。他はだいたい同じ。
| 演算子の分類 | 演算子 |
|---|---|
| 算術演算子 | + - * / %
|
| 論理演算子 (真偽値およびビット値) | & | ^ ! ~ && || true false
|
| 文字列連結演算子 | +
|
| インクリメント/デクリメント演算子 | ++ --
|
| シフト演算子 | << >>
|
| 関係演算子 | == != < > <= >=
|
| 代入演算子 | = += -= *= /= %= &= |= ^= <<= >>=
|
| メンバアクセス演算子 | .
|
| インデキシング演算子 | []
|
| キャスト演算子 | ()
|
| 三項演算子 | ?:
|
| デリゲート結合/除去演算子 | += -=
|
| オブジェクト生成演算子 | new
|
| 型情報演算子 | is sizeof typeof
|
| オーバーフロー例外制御演算子 | checked unchecked
|
| 間接参照/アドレス演算子 | * -> [] &
|
あ。C/C++にあった , (カンマ)演算子がないね。
各演算子の評価順序は次のように定められているらしい。出所がECMAへの提案資料なのでちょっと不安。だいたい順序は C と同じ。ビット演算子より比較演算子の優先順位が高いという問題は引き継がれたようだ。ちぇ。
| # | 演算子 |
|---|---|
| 1 | (x) x.y f(x) a[x] x++ x-- newtypeof sizeof checked unchecked
|
| 2 | + - ! ~ ++x --x (T)x
|
| 3 | * / %
|
| 4 | + -
|
| 5 | << >>
|
| 6 | < > <= >= is as
|
| 7 | == !=
|
| 8 | &
|
| 9 | ^
|
| 10 | |
|
| 11 | &&
|
| 12 | ||
|
| 13 | ?:
|
| 14 | = *= /= %= += -= <<= >>= &= ^= |=
|
Javaとまったくいっしょ。条件がbool型であることが求められるので、C/C++とはちょっと違う。
評価対象にstringが使える。
breakしないで次のcase文に入る動作(fall throughというらしい)が禁止されている。つまり..
switch (n) {
case 1:
何か操作をする
// ここでbreakしない
case 2:
別の操作をする
break;
default:
}
..というJavaで許されたような処理はダメだということ。ただし..
switch (n) {
case 1:
case 2:
何か操作をする
break;
default:
break;
}
..こういうふうに間に処理が入らなければ大丈夫。他にはbreakの位置でgotoが使えて、そのラベルにcaseが使える。こんなふうに書けるけど、カンタンにスパゲッティプログラムを作れるから多用は禁物だ。
switch (n) {
case 1:
何か操作をする
goto case 3;
case 2:
別の操作をする
goto default;
case 3:
さらに別の操作をする
break;
default:
もっと別の操作をする
break;
}
C/C++/Javaでは省略が許されていたswitch節末尾のbreakは、C#のβ2あたりから許されなくなったらしい。(22 Oct 2001 追記)
Javaとほとんど同じだけど、ラベルつきbreakは使えないみたい。ただ、goto文がどこでも使えるのでそれで同じことができる。
昔のC++と違って、イニシャライザで定義された変数のスコープはそのfor文の中だけ。これはJavaといっしょ。
VBにあった集合要素を列挙するループ文。こんなふうに書く。
// ShowEnv.cs
using System;
using System.Collections;
class ShowEnv {
public static void Main() {
IDictionary envs = Environment.GetEnvironmentVariables();
foreach (string env in envs.Keys) {
Console.WriteLine("{0}={1}", env, envs[env]);
}
}
}
この反復処理の値の取り出しは実行時に評価され、コンパイル時に型誤りなどが検出できないので注意が必要。foreachに渡す集合クラスは、GetEnumerator()メソッドで、次があるかどうかをbool型で返すMoveNext()とCurrentプロパティをサポートしたクラスを返す必要がある。
どちらもJavaとまるっきり同じ。
ラベルが事実上どこでもつかえるので、まったく自由にgoto文が使える。
void F() {
Console.WriteLine("reachable");
goto Label;
Console.WriteLine("unreachable");
Label:
Console.WriteLine("reachable");
}
こんなときに使う。
void F() {
...
if (done) goto exit;
...
exit: ;
}
例が悪いか。
Javaのtry文/throw文といっしょ。Javaだとtry節だったような気もするけど。
値型の演算結果についてオーバフローをチェックするかどうかを指示する文。
Javaのsynchronizedと同じ。
クラスには2通りの修飾子がつけられる。ひとつが abstract で、これは abstract メソッドをもつ Java の同名のクラスと同じモノ。必ず abstract メソッドをオーバーライドしないと使用できない。もうひとつが sealed クラスで、これは Java の final クラスと同じもので、これ以上の継承を禁止する。
継承するクラスやインターフェイスは、クラス名の後に : (コロン)を置き、その後に , (カンマ)で区切って羅列する。クラスとインターフェイスの両方を継承する場合、先頭にクラス名を記述する。
クラス定義のブロック末尾には C/C++ 流に ; (セミコロン)を置いても置かなくてもコンパイルできる。
アクセスコントロールのキーワードは以下のようなものがある。無指定の場合は private 指定と同様になる。
| キーワード | 範囲 |
|---|---|
public
| アクセスは制限されない。 |
protected internal
| メンバのアクセス修飾子の定義に依存する。 |
protected
| 包含クラスか、包含クラスを継承した型からしかアクセスできない。 |
internal
| このプロジェクトに含まれたメンバからしかアクセスできない。 |
private
| 包含された型からしかアクセスできない。 |
クラス以外の各型の取りうるアクセス修飾子は以下のようになる:
| メンバが所属する型 | アクセス修飾子の初期値 | 指定可能なアクセス修飾子 |
|---|---|---|
enum
| public
| なし |
class
| private
| publicprotectedinternalprivateprotected internal
|
interface
| public
| なし |
struct
| private
| publicinternalprivate
|
static フィールドは、Java と同様インスタンス情報とは切り離された型依存のフィールドになる。
readonly フィールドは、Java の final 修飾子と同様に値の変更が許されないフィールドになる。
public static readonly Color Black = new Color(0, 0, 0);
フィールド初期化には(暗黙の参照を含め) this が使用できないので、次のような初期化は無効。
class A {
int x = 1;
int y = x + 1; // エラー
}
static メソッドはインスタンス情報とは切り離された型依存のメソッドとなり、その内部で this キーワードおよびインスタンスデータメンバが使用できなくなるのは Java と同様。呼び出し方も Java と一緒。
virtual メソッドは、C++でいう仮想関数になり、サブクラスの override メソッドによりオーバライドされる。 override メソッドはスーパークラスの virtual, abstract あるいは override メソッドをオーバーライドする。 abstract メソッドは、C++でいう純粋仮想関数になり、サブクラスで必ず override する必要がある。abstract メソッドを含むクラスは必ずクラス宣言で abstract クラスであることを明示する必要がある。
extern メソッドは、Java の native メソッドのようなものだと思うけど、今のところまだよく判らない。SDKにはこんな例が載っている。なんとなく想像はつくかな?
class Path {
[DllImport("kernel32", setLastError=true)]
static extern bool CreateDirectory(string name, SecurityAttributes sa);
[DllImport("kernel32", setLastError=true)]
static extern bool RemoveDirectory(string name);
[DllImport("kernel32", setLastError=true)]
static extern int GetCurrentDirectory(int bufSize, StringBuilder buf);
[DllImport("kernel32", setLastError=true)]
static extern bool SetCurrentDirectory(string name);
}
メソッドのパラメータには ref, out, params がオプション指定できる。
無指定のパラメータを値パラメータと呼び、これは呼び出し元の値がコピーされてメソッドに渡される。 つまり普通の Java のメソッドはこの値パラメータしか扱えない。
ref パラメータはそのパラメータへの参照を渡し、呼ばれたメソッド側で変更することができる。 ref パラメータには、定数や初期設定されていない変数を渡すことはできない。 渡された値を変更するという用途の他に、コピーにコストがかかる値型を引数に指定する場合についても有効かもしれない。 呼び出し側でも ref と書かないといけないのがちょっと煩雑。 まあ ref が明示されていないとひどいバグになるのは判るけど。
class Test {
static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
static void Main() {
int i = 1, j = 2;
Swap(ref i, ref j);
System.Console.WriteLine("i = {0}, j = {1}", i, j);
}
}
out パラメータは ref と似ているけれど、出力専用なので、初期化されていない変数を渡すことができる。 さらに、メソッドから戻るまでの間に必ず値が設定されている必要がある。 されていない場合はコンパイラが警告を出す。
params パラメータはまた毛色の違う指定子で、1次配列のパラメータを呼び出し側があたかも可変長引数のように記述できるようにするもの。可変長引数と同じく引数並びの末尾要素にしか指定できない。
class Test {
static void F(params int[] values) {
foreach (int value in values) {
System.Console.WriteLine("{0}", value);
}
}
static void Main() {
int i = 1, j = 2, k = 3;
F(new int[] {i, j, k});
F(i, j, k);
}
}
あれ、そういえばデフォルトパラメータは指定できないみたい。
これもJavaにはなかった機能で、get/setメソッドのペアで、フィールド値をラップするような構造を言語機能で提供している。get/setについても virtual 指定してオーバーライドすることがができる。
public interface IObserver {
void Update(object o, object value);
}
public class ObservableData {
private System.Collections.ArrayList observers = new System.Collections.ArrayList();
private object data;
public object Data {
virtual get {
return data;
}
virtual set {
if (data != value) {
data = value;
foreach (IObserver observer in observers) {
observer.Update(this, data);
}
}
}
}
public void AddObserver(IObserver ovserver) {
observers.Add(ovserver);
}
}
public class Test : IObserver {
static void Main() {
Test t = new Test();
ObservableData od = new ObservableData();
od.AddObserver(t);
od.Data = "ABC";
}
public void Update(object o, object value) {
System.Console.WriteLine("{0}", value);
}
}
get だけしか定義しなかったプロパティは、読み出しのみ可能なプロパティになる。
重要かもしれないけど、イマイチ判ってないので後回し。 Java の Event とは随分違う。 簡単な使い方の例はWindowsアプリケーションを書いてみるを参照してね。
using System;
class BitArray {
int[] bits;
int length;
public BitArray(int length) {
if (length < 0) {
throw new ArgumentException();
}
bits = new int[((length - 1) >> 5) + 1];
this.length = length;
}
public int Length {
get { return length; }
}
public bool this[int index] {
get {
if (index < 0 || index >= length) {
throw new IndexOutOfRangeException();
}
return (bits[index >> 5] & 1 << index) != 0;
}
set {
if (index < 0 || index >= length) {
throw new IndexOutOfRangeException();
}
if (value) {
bits[index >> 5] |= 1 << index;
} else {
bits[index >> 5] &= ~(1 << index);
}
}
}
}
class Test {
static void Main(string[] args) {
BitArray ba = new BitArray(16);
ba[8] = ba[10] = true;
for (int i = 0; i < ba.Length; i++) {
System.Console.Write("{0}", ba[i] ? "1" : "0");
}
}
}
Java と違い、オペレータもオーバーロードできる。
| 演算子 | オーバーロードの可否 |
|---|---|
+, -, !, ~, ++, --, true, false
| これらの単項演算子はオーバーロードできる。 |
+, -, *, /, %, &, |, ^, <<, >>
| これらのニ項演算子はオーバーロードできる。 |
==, !=, <, >, <=, >=
| 比較演算子はオーバーロードできる。ただし、==と!=、<と>、<=と>=はそれぞれセットでオーバーロードする必要がある。
|
&&, ||
| 論理条件演算子はオーバーロードできない。ただし、それらが & と | として評価される場合に限ってはオーバーロードできる。 |
[]
| インデキシング演算子はオーバーロードできない。ただし、新たなインデクサを定義することはできる。 |
()
| キャスト演算子はオーバーロードできない。ただし、新たな型変換演算子は定義できる。 |
+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=
| 代入演算子はオーバーロードできない。ただし例えば += は + がオーバーロードされていればその演算子を使用する。
|
=, ., ?:, ->, new, is, sizeof, typeof
| これらの演算子はオーバーロードできない。 |
コンストラクタは型名と一緒。これは Java と一緒。デストラクタは型名の前に ~ (チルダ)をつけたもの。これは Java と違う。継承可能なクラスのデストラクタは virtual にしておけっていう Know-How が C# にも成立するのかどうかは知らない。
Java では、コンストラクタの先頭で super(...) や this(...) として親クラスや自分自身のコンストラクタを呼んでいたが、C#ではコンストラクタの後ろにイニシャライザとして : base(...) や : this(...) を記述する。
class A {
int x, y;
public A(int x, int y) { this.x = x; this.y = y; }
}
class B : A {
public B(int x, int y) : base(x + y, x - y) {}
public B() : this(1, 1) {}
}
スコープをコントロールするためのしかけ。Java のパッケージとほぼ一緒。ただ、Java の場合はパッケージ構造をディレクトリ/ファイルにマッピングしてしまったので、複数の public クラスを1ファイルにまとめられなかった。これが何故そうなっているかは知らないけど、小さなインタフェイスが乱立するようなアプリケーションだとかなりメンドウだった。その苦労が C# の名前空間では必要ない。まあ C++ でもそうだったんだけどさ。
Java の package の代わりに namespace 、import の代わりに using を使う。C++では using namespace だったので、短くなったのはありがたいけど、混乱するかも。それと、Java の package では、そのファイル全体に適用されたけど、C# では直後のブロックに対して適用される。
あいまいな名称(usingしている名前空間中の同じ名前)が複数ある場合は、コンパイル時にチェックされて、完全な名前で指定することが要求されるのもJavaと一緒。
// a.cs
using SideA;
namespace SideA {
class X {}
}
namespace SideB {
class X {}
}
class A {
public static void Main() {
System.Console.WriteLine(new X());
System.Console.WriteLine(new SideA.X());
System.Console.WriteLine(new SideB.X());
}
}
> csc /nologo a.cs > a.exe SideA.X SideA.X SideB.X
これで with があれば、もう言うことなしだったんだけどねえ。なぜ with が採用されなかったのかは知らない。
using 文は、名前空間を指定するブロックの先頭に書かないといけない。無名(トップレベル)の名前空間を使っているときは注意が必要だろう。CS1529エラーが出ているときはこれが原因。
namespace x {
namespace subspace {
using System.Windows.Forms;
class MyButton : Button {};
using System.Windows.Forms; // エラー! CS1529, クラス定義の前に配置する必要がある。
}
using System.Reflection; // エラー! CS1529, 'subspace'名前空間の前に配置する必要がある。
}
using System; // エラー! CS1529, ファイルの先頭に配置する必要がある。
| abstract | enum | long | stackalloc |
| as | event | namespace | static |
| base | explicit | new | string |
| bool | extern | null | struct |
| break | false | object | switch |
| byte | finally | operator | this |
| case | fixed | out | throw |
| catch | float | override | true |
| char | for | params | try |
| checked | foreach | private | typeof |
| class | goto | protected | uint |
| const | if | public | ulong |
| continue | implicit | readonly | unchecked |
| decimal | in | ref | unsafe |
| default | int | return | ushort |
| delegate | interface | sbyte | using |
| do | internal | sealed | virtual |
| double | is | short | void |
| else | lock | sizeof | while |
MSDNマガジンには毎号のように何かしら載っているらしいのでパス。
http://dennou.gihyo.co.jp/books/cnet/faq.html から2001/01/30採録。しかし最近ちょっとアレな会社になっちゃったと思ってたけど、そんなに技術が判るヒトがいなくなっちゃったのかなあ。ちょっと悲しいね。が、まあこんな OOP 言語の基本機能をこんな本で勉強するようなヒトはいないと思うので、たいした害はないんじゃないのかな?
----ここから
2.問い合わせの多かった質問への回答
Q.
P.48「オーバーロードを使う」のオーバーロードはオーバーライドの間違いではないでしょうか?
A.
オペレータはoverride(オーバーライド)ですが、C#では、このことをoverload(オーバーロード)と呼びます。
実は、オーバーロードとオーバーライドは、動作は等価なのですが、全く異なるものです。オーバーライドとは、旧来のオブジェクト指向言語で、オペレータなどをソースコードレベルで置き換えてコンパイルし、ファンクションを変更する技術をいいます。一方、オーバーロードとはCOMのコンテイメントのことで、バイナリファンクションをvTableの変更により変更することを指します。ただ、C#では、文法的に、また、知識的に、C++を利用している人にとって抵抗のないように、「方便」をいたるところで使用しており、そのひとつがoverrideキーワードです。overloadとすると、このような技術的な違いをご存知ない方が引っかかる可能性があるからだと思われます。他にも、デストラクタなどC++と機能的には等価なものですが、実は全く異なるものがあります。
本書の用語は、現時点の用語の定義に準拠していますので、そのまま「オーバーロード」といたしましたが、マイクロソフトはマーケティングの観点で用語を変更する場合が多いです。将来はオーバーライドと呼ぶ日があるかもしれませんが、現時点では技術的に全く異なることを明らかにするために、オーバーロードと呼んでいます。C#は、全く新しい言語であり、矛盾の無い判りやすい言語です。
そこで、本書のサンプルは、あえてstructにファンクションメンバーを実装するようにして、C++と全く異なるテイストであることを強調するようにいたしました。
オーバーロードはオーバーライドと異なり、コンパイルされていても動作するわけですから、旧来の技術より進化した最新の技術であることをおわかりいただけると思います。
--ここまで