Home>

C++プログラマのためのC#入門

更新日:2003/08/10

概要

本ページはC++でプログラムを組める人を対象にしています。 既にC++が組めるのに一からC#を覚えるのはかったるい、要点を掻い摘んでさっさとC#でプログラムを組み始めたい、という人のために書かれています。 書いている本人が結構いい加減なので、内容もそんな感じです。つっこみがいれられるようになれば卒業です。

Hello, World

最初くらいは由緒正しい手順ってことで、"Hello, World"です。

// hello.cs
public class MyApp {
	public static void Main() {
		System.Console.WriteLine("Hello, World");
	}
}

上のコードをhello.csという名前で保存してます。 C#ではソースファイルの拡張子は.csです。因みにヘッダファイル(.h)は無く、ソースファイル(.cs)1つに全部詰め込みます。 コンパイラが面倒を見てくれるので、プロトタイプ宣言は不要です。

ソースコードを説明するよりも、まずは、コンパイルしてモジュールを作ってしまいましょう。

csc /t:exe /out:hello.exe /r:System.dll hello.cs

hello.exeが出来ましたでしょうか?実行すると、お約束通り"Hello,World"が表示されます。

ここでコンパイラオプションについて説明します。/tはターゲットで、 コンソールアプリ(exe)なのか、ウィンドウアプリ(winexe)なのか、それともDLL(library)なのかが決定されます。 他にもモジュール(module)というのがありますが、とりあえず無視です。 上の例はコンソールアプリを作っていたことが分かりましたね。

/out:hello.exeは出力ファイル名です。そのまんまです。

/r:System.dllは、System.dllを参照することを意味しています。System.dll内にあるクラスを使用しているので指定しています。 複数のdllを参照している場合は、/r:System.dll /r:System.IO.dllというように羅列させます。 ここでは、System.Console.WriteLineを使っているので、System.dllが必要になりました。 (実はSystem.dllなどよく使うアセンブリは自動的に参照されるので/rで指定する必要はないのですが、説明のためにワザとやってます。)

C++の場合、プログラムのエントリーポイントはmain関数でしたが、C#の場合は、static void Mainとなります。C#はグローバル関数を許していないため、 プログラム内にあるクラスのどこかにMain関数を書く必要があります。ただし、Mainが複数ある場合はコンパイルエラーになります。

名前空間

C++にあったnamespaceそのまんまです。先ほどのSystem.Console.WriteLineは、 名前空間"System"にあるクラス"Console"のメソッド"WriteLine"を呼び出していたのでした。ところでC++で使っていた名前空間はstdくらいでしたが、 C#はもっと積極的に使っています。今使ったSystemの他にも、ファイルIO関係のSystem.IOやネットワーク関連のSystem.Netなど多数存在します。 イメージ的にはヘッダファイルの論理版ってとこです。C++でもstring.hは文字列操作関連などヘッダファイル毎に分類されていましたが、 C#ではヘッダファイルという物理的な構造は取らずに論理的な名前空間を積極的に使っています。 名前空間は便利ですが、タイプ量が増える傾向にあるので、C++のusing namespace同様、タイプ量を減らす構文が存在します。

using System;

class MyClass {
	public static void Main() {
		Console.WriteLine("Hello, World");
	}
}

usingによってソース内で指定した名前空間を使用するが宣言されると、名前空間を省略することが出来ます。 この辺はC++と同様です。もちろん問題点も同様です。

値型と参照型

C#にある型は値型と参照型の2つに分類されます。値型は変数がそのまま値である型で、 C++でのスタックに積まれた変数みたいなモノです。参照型はC++の参照に近いのですが、 イメージ的にはアドレス演算が出来ないポインタって方がより適切だと思います。(C#の参照はnullに出来るが、C++は出来ないなど)

C++の場合、newで生成するとヒープに作成され、それをポインタで参照するし、それ以外はスタックに積まれますが、 C#の場合は、newで生成されたとかは関係なく、型によってそれが値型なのか参照型なのかが決まります。 short,char,int,long,float,double,bool...などの組込型(objectやstringは参照型だけど例外だと思って下さい)や、 構造体、列挙型(これは任意の値型が指定できるので当然)は値型で、 それ以外は参照型です。ところで、C#では配列は配列という型となっています。つまり配列も参照型となるわけです。

イメージがわかないと思うので具体例など。

int a = 10;	// intは値型
int b = new int();	// 例えnewで生成しても値型
string c = "abc";	// stringは参照型

struct ABC {
	public string name;
	public int age;
}

ABC d;	// dには値が設定されていません。メンバにアクセスする前に値を割り当てる必要がある。
ABC e = new ABC();	// 組込型以外はnewで生成します。
e.name;	// C#ではアロー演算子(->)は使いません。値型だろうが参照型だろうが.(ピリオド)を使います。

C++を知っていると逆にこんがらがってしまいそうですが、 整理すると「すべてのオブジェクトはnewで生成する。ただし、組込型はnewを省略しても構わない」って覚えると良いかも。

フロー制御

プログラムを書くにはクラスも重要ですが、まずはフロー制御でしょう。C#では、C++でお馴染みのif,switch,for,while,do-whileや新参者のforeachなどがあります。 もちろん、gotoもありますし、try-throw-catchもあります・・・ってこれはフロー制御じゃないな。 (世の中には例外処理でばりばりフロー制御をやる剛の者もいるらしいが)

if

C++とほとんど変わりませんが、条件に論理式しか書けなくなっているのに注意です。

if (i = 0) 
とかやらかすと、コンパイルエラーになります。

switch

かなり変わっています。基本的に便利になっているけど、一部、思いっきり不便なので一長一短。

まずはイカス例から。

switch (str) {
	case "赤
		break;
	case "青:
		break;
	case "緑":
		break;
}

数値だけでなく文字列も使えます。

次にイマイチな例。

switch (n) {
	case 1:
	case 2:
		break;
	case 3:
		break;
}

これをやろうとするとコンパイルエラーになります。C#ではフォールスルーは禁止なので、 caseとbreakは対応させないと駄目です。じゃ、C++っぽいフォールスルーはどうするのかというと、

switch (n)
	case 1:
		goto case 2;
	case 2
		break;
	case 3:
		break;
	}

ってな具合にgoto caseを使います。breakを入れ忘れてバグるのを予防しているってことなのですが、それは、breakを忘れたことに気づかないような巨大なswitch文を書く方が悪いというか・・・、 そのフォローのためによく使う用法を禁止するのは、ちとアレだと思います。

for

基本的にC++と一緒。ただ、マイクロソフト独自forとは異なっています。forで宣言した変数はfor内スコープでのみ有効です。

(Visual)C++でよく使う、次のような書き方はNGです。

for (int i = 0; i < cnt; i++) {
	if (xxx) 
		break;
}
if (i == cnt) 
	// 見つからなかった

なので、for内のifのところで処理してreturnとか、iをforの外側で宣言するとかしましょう。 蛇足ですが、forの継続条件にも論理式しか書けません。

while, do-while

これも基本的にC++と一緒、ただし、条件には論理式しか(以下略)

while (1)
ではなく、
while(true)

で永久ループさせます。

foreach

C#で新登場。ただし、VBやPerl,Pythonあたりを知っていればお馴染み。

foreach (int n in arr)
	sum += n;

の様に配列を走査するのに、ループカウンタを用意しなくても良くなります。

inの後に置けるのは配列だけではなく、IEnumerableを実装している型ならOKです。 なんのこっちゃ?ってなるかも知れませんが、C++風に言うと、IEnumerableという抽象クラスから継承して、 純粋仮想関数を実装するってこと。

配列

よく使うデータ構造の1つである配列ですが、C#ではC++と考え方が異なっています。 例えば、C++の場合ですと、

int a[10];

このような配列があった場合、int型の配列と捉えています。一方、C#では配列も型(クラス)として扱います。

int[] a;

書き方が変わっているので注意です。型であることを強調した書き方通り、int配列型となります。 もちろん、配列型も他の型同様、newを使って領域を確保する必要があります。

int[] a = new int [10];

当然のごとく、

int[10] a;

とは書けません。

C++では配列のサイズを指定せずに、配列の初期値を使って宣言することが出来ましたが、C#でも可能です。

int [] a = new int[] {0, 1, 2, 3, 4, 5, 6, 7,  8, 9};

設定されている値から配列のサイズが決まります。ところで、先ほど配列は型(クラス)と説明しました通り、 プロパティやメソッドを持っています。中でもよく使うのが配列の長さを取得するための、Lengthプロパティです。

int[] a = new int [10];
for (int i = 0; i < a.Length; ++i) 
	a[i] = i;

配列のサイズは配列クラスで管理されますので、配列のサイズを管理する変数を用意する必要は無くなります。

C++には無かったモノに、多次元配列というのがあります。くどくどと説明するよりも見た方が早いと思います。

int [,] a = new int [3, 4];
for (int i = 0; i < a.GetLength(0); ++i) {
	for (int j = 0; j < a.GetLength(1); ++j) {
		a[i, j] = i * a.GetLength(0) + j;
	}
}

また、多次元配列としては次の配列の配列というものもあります。

int[][] a = new int[3][4];

int[,]形式の多次元配列を矩形配列、int[][]形式の多次元配列をジャグ配列とかいうみたいです。

クラス

説明が結構後回しになったけど、C#での最重要項目。これが無いとプログラムが書けない。

書き方の基本

まずは、簡単なクラスを見てみます。

public class MyClass {
	private int count;
	public MyClass() {
	}
	
	~MyClass() {
	}
	
	private void Method() {
	}
}

このクラスはコンストラクタ、デストラクタ、それとMethodというメンバ関数を1つ持っています。

ここから分かることを列挙します。

C++でヘッダファイルにテンプレートクラスを書くと思えばさほど違いはないかな。

コンストラクタ

親クラスのコンストラクタ呼び出しが変わったことと、クラス内の他のコンストラクタが呼び出せるようになっていることが新しいです。

class MyClass : Object {
	private string name;
	MyClass() : this("名無し") {
	}
	
	MyClass(string name) : base() {
		this.name = name;
	}	
}

引数無しのコンストラクタでは、C++でいう初期化子の位置にthis("名無し")と書いていますが、 これはクラス内の文字列を1つ引数に取るコンストラクタを呼び出すことをいみしています。 また、文字列を引数に取るコンストラクタではbase()とありますが、これは親クラスのコンストラクタを呼び出しています。 親クラスのデフォルトコンストラクタは自動的に呼び出されるので、例のように明示的に書く必要はありません。

まとめると、C++と違ってthisでコンストラクタが呼び出せるようになった、 親の呼び出しはBaseClass::XXXのように明示的に指定していたけど、親クラスを表すキーワードが出来たってことです。

C++プログラマへの注意:親クラスのコンストラクタで仮想関数を呼び出し、派生クラスで仮想関数をオーバーライドしても派生クラスの関数は呼ばれませんでしたが、C#では派生クラスの関数が呼ばれます。

デストラクタ

オブジェクトが消滅するときに呼び出されますが、 GC(ガベージコレクション)のおかげでいつ解放されるか不明になったので、ほとんど信用できません。 去勢されたデストラクタ。しかし、GCはメモリしか面倒を見ません。ファイルやらソケットやらは自前で何とかしなければなりません。 GCは便利ですが、個人的な経験では、メモリリークでシステムを止めたことは無いけど、 win32カーネルオブジェクトのハンドルを解放し忘れて、やってしまったことはありまくるので、 結局のところ、リソース管理は難しいまま?って気がする。

メモリ以外の重要なリソースを扱うクラスは、次のようなおまじないが必須。

public class MyResource : IDisposable {
	~MyResource() {
		Dispose(false);
	}
	// IDisposableインタフェースの実装
	public void Dispose() {
		Dispose(true);
		GC.SuppressFinalize(this);
	}
	
	protected void Dispose(bool flag) {
		if (flag) {
			// マネージドオブジェクトの参照を切る
		}
		// GC管理外のリソースを解放
	}
}

// 使うとき
using (MyResource res = new MyResource()) {
	...
	...
}

突然usingなんていうのが出てきましたが、このブロックで生成されたオブジェクト(この例ならres)は、 ブロックから抜けるときに自動的にDisposeメソッドを呼んでくれます。たとえ例外が発生しても大丈夫という優れものです。 もっと素晴らしい方法が思いつくまでは、愚直に守った方がよさげ。

メソッド

C++あがりだとメンバ関数と呼ぶ人が多い。 C#ではグローバル関数は作れないようになっているので、必ずどこかのクラスのメンバ関数になります。 グローバル関数っぽいことがしたい場合は、staticなメンバ関数にします。 C#では引数の書き方が変わっています。 引数の渡し方について、入力、参照、出力の3つを見てみましょう。

入力

C++と同じです。値渡しされているのですが、"何"を値渡ししているかが重要です。 C#やJavaでは値渡しはC++とはちょっと異なります。ここで、とても作為的な例を見てみます。


class Integer {
	public int val;
	public Integer(int val) {
		this.val = val;
	}
}

private void Swap(Integer a, Integer b) {
	Integer tmp = a;
	a = b;
	b = tmp;
}

private void Swap2(Integer a, Integer b) {
	int tmp = a.val;
	a.val = b.val;
	b.val = tmp;
}

Integer a(10);
Integer b(20);

// a, bは入れ替わらない
Swap(a, b);

// a, bは入れ替わる
Swap2(a, b);

分かりますでしょうか? この例では参照型を引数として渡しています。 値型と参照型のところでちょっと書きましたが、 参照型はポインタと考えた方がC++あがりの人にとっては理解しやすいと思います。 この例ですと、Integerクラスのポインタが渡されていると考えます。 Swap関数ではIntegerポインタが値渡しされているので、 コピーされた変数のポインタの値を入れ替えても呼び出し元のa,bにはなんの効果もありません。 Swap2関数の場合はポインタが値渡しされていても、そのポインタが指すオブジェクトは同じなので、 呼び出し元のポインタが指すオブジェクトは変更されます。

Javaでは、しきりにポインタが無くなったことを強調していますが、 ポインタとして理解しないと、かえって分かり難いと思います。参照とは安全になったポインタのことです。

参照(ref)

引数の先頭にrefを付けます。C++の参照渡しそのまんまですが、呼び出し元にもrefが必要なので注意。

void Method(ref object o) {
}

// 呼び出し側、refが必要
Method(ref o);

// これはNG
Method(o);

// 当然nullは渡せない
Method(ref null);

// nullを指す参照はOK
object o = null;
Method(ref o);

出力(out)

引数の先頭にoutを付けます。参照渡しの一種ですが、渡される値は信用できません。 参照だけでも事足りるのですが、よりソース上で意味をはっきりさせることが出来ます。 メンテナンス性を考えると、この辺はきっちり使い分けたいところです。 C++でも値を変更しない引数はconstをつけて意味をはっきりさせていましたよね。あと、constが出てきたついですが、 C#ではconst引数はありません。えー!っと思うかも知れませんが、私もそう思います。

void Method(out object o) {
	// out引数の中身はまったく信用出来ないので、参照はしない。
	// ↓こんなのは駄目駄目
	object p = o;
	
	// さっさと初期化してしまいましょう
	o = null;
}

// 呼び出しはoutが必要
// out引数なので、初期化もせずに容赦なく渡してOK
object o;
Method(out o);

可変引数

C++ではprintfなどを通じてよく使っていましたが、 可変引数な自作関数を書いた経験は少ないのではないか、と思います(va_listやらvfprintfとお友達の方ごめんなさい)。 可変引数にしなくても、引数を配列にすれば済むわけですが、呼び出し側でちょこっとだけ楽出来るのが魅力。 C#の場合、結構簡単に可変引数に応できます。

関数の最後の引数を配列にして、先頭にparamsを付けるだけです。

// 関数自体は最後の引数が配列になっているだけ
void Method(int a, int b, params object[] arr) {
}

// 呼び出し
void Method(10, 20, "ABC", "DEF", 30, 40);

// もし、関数が可変引数になってなかったら
// 呼び出し側がちょっと手間
void Method(10, 20, new object[] {"ABC", "DEF", 30, 40});

プロパティ

メンバ変数をprivateにし、Get/Set関数をつくって、"これがオブジェクト指向におけるカプセル化です"、 みたいなのを見たことがありますが、それはともかく、 ローカル変数以外の変数を間接アクセスにしておくことは、多くの場合、良いことです。 しかし、Get/Set関数を呼び出す側のソースがまどろっこしくなってしまいます。

// Get/Setを使った例
MyClass my = new MyClass();
// 現在の値を1増やす
my.SetCount(my.GetCount() + 1);

cntがpublic変数であれば、my.cnt += 1とかmy.cnt++とするだけで済むし、ソースもずっと見やすくなります。 実は、public変数の使いやすさとGet/Set関数による間接アクセスの安全性の両方を備えたのがプロパティです。

class MyClass {
	private int cnt = 0;
	public int Count {
		get { return cnt; }
		set { cnt = value; }
	}
}

MyClass my = new MyClass();
// 現在の値を1増やす
my.Count++;

set {...}の中でvalueというキーワードが出てきますが、これは予約語でプロパティに代入される値を意味します。 因みにget/setはどちらか片方だけでも良くて、getだけだと読み取り専用、setだけだと書き込み専用となります。

プロパティはとても便利ですが、valueというよく使う単語を予約語にしているのはちょっと・・・と思わなくはないです。

インデクサ

C++のoperator[]みたいなものです。コレクション型をラップしたりした場合に使うかも。

class MyArray {
	private int[] arr = new int [100];
	
	public int this [int index] {
		get { return arr[index]; }
	}
}

MyArray arr = new MyArray();
// オブジェクトに対して[]を使って配列のようにアクセス出来る
int n = arr[50];

他にも、public int this [string key] { ... }とかにして、連想配列っぽいアクセスをさせることも出来ます。

継承

C#はpublic継承しか出来なく、:の後にはpublicもいりません。 また、親クラスが指定されない場合はデフォルトでObjectクラスから継承されます。 つまり、どのクラスもObject型にキャスト出来ます。あと多重継承が出来ません。

仮想関数をオーバーライドするにはoverrideキーワードを付けます。 また、仮想関数を同名の関数で上書きする場合はnewキーワードを付けます。

public class MyBase {
	public virtual void Method1() {
	}
	
	public virtual void Method2() {
	}	
}

public class MyClass : MyBase {
	public override void Method1() {
	}
	public new void Method2() {
	}
}
MyClass my = new MyClass();
MyBase base = my;

// MyClassのMethod1が呼ばれる
my.Method1();
// MyClassのMethod2が呼ばれる
my.Method2();

// MyClassのMethod1が呼ばれる
base.Method1();
// MyBaseのMethod2が呼ばれる
base.Method2();

抽象クラス

C++での抽象基底クラスです。所謂ABC(Abstruct Base Class)。また、C++では純粋仮想関数(Method()=0)を書いていましたが、 C#ではabstractキーワードが用意されています。クラス、メソッド両方で指定可能です。

// 抽象メソッドを含むクラス
public class MyClass {
	// 抽象メソッド
	public abstract void Method();
}

// 抽象クラスなら抽象メソッドがなくてもいい
public abstract class MyClass {
	public virtual void Method() {
	}
}

継承禁止

C#では継承禁止がサポートされました。クラス、メソッド両方で指定可能です。 クラスの場合も、メソッドの場合もsealedキーワードを付けるだけです。

// クラスの例
// SubClassを継承することは出来ない
public sealed class SubClass : MyClass {
}
	
// メソッドの例
public class SubClass : MyClass {
	// SubClassの派生先ではMethod1をオーバーライドすることは出来ない
	public sealed override void Method1() {
	}
}

インタフェース

C#では多重継承が出来なくなりましたが、代わりにインタフェースの多重継承がサポートされています。 C++(COM)では純粋仮想関数だけのクラス(構造体)をつくってインタフェースと呼んでいましたが、 C#ではinterfaceキーワードが用意されています。

// インタフェース名には慣例として先頭にIを付ける
public interface IMyInterface {
	voie Method1();
	void Method2();
}

// クラスは1つしか継承出来ないが、インタフェースは複数継承出来る
// クラスとインタフェースの両方を継承する場合はクラスを先に書く
public class MyClass : MyBase, MyInterface1, MyInterface2 {
...
...
}

メソッドにpublicやprotectedなどのアクセス修飾子が指定できないことやabstractキーワードは不要ことに注意。

例外処理

C++と同様try-throw-catchですが、これにfinallyが追加されています。
// 例外を起こすメソッド
void Method() {
	// エラーが起こったら例外を投げる
	throw Exception();
}

// 例外処理の使用方法
try {
	// 例外が起こる可能性がある処理はtryで括る
	Method();
}
catch (Exception e) {
	// 例外に対応する処理を行う
}
finally {
	// 例外が起こっても起こらなくても必ず呼ばれる
	// リソースの解放処理とかに使える
}

キャスト

C++では、const_cast、dynamic_cast、static_cast、reinterpret_castやC形式のキャストがありましたが、 C#ではC形式のキャストとis、asが使えます。ただ、C形式のキャストはキャストに失敗すると例外を発生するので、 例外処理が必要になって面倒なので、あんまり使いません。isはキャスト可能かどうかをbool値で返し、 asはキャストはキャストが成功したら参照を、失敗したらnullを返します。で、結局どれを使えばいいかというと、 asを使えばいいです。isで変換可能かどうかチェックしても結局、キャスト処理が必要になるので、 最初からasで変換してしまえですね。

デリゲート/イベント

デリゲートについては色々議論があったりしますが、 C++な人からみれば型安全になった関数ポインタとかfunctorオブジェクトとか思っておけばOKです。 主な使い方はデリゲートとイベントを組み合わせたObserverパターン(MFCのDocument/Viewみたいなヤツ)の実装かな。

// objectを引数にとり、戻り値がvoidの関数ポインタもどきを定義
delegate void UpdateEventHandler(object param);

class Document {
	// UpdateEventHandler型のイベントUpdatedを定義
	public event UpdateEventHandler Updated;
	
	// ドキュメント更新用メソッド
	public void Update() {
		// Updatedイベントを発火、これでUpdatedに結びついているメソッドが呼び出される
		Updated(param);
	}	
}

class TextView {
	// ドキュメント変更時に呼ばれたい
	public void Notify(object param) {
	}
}

class GraphView {
	// ドキュメント変更時に呼ばれたい
	public void Notify(object param) {
	}
}

// アプリケーション
class MyApp {
	public static void Main() {
		Document doc = new Document();
		TextView txtView textView = new TextView();
		GraphView graphView = new GraphView();
		// Updatedイベントにハンドラを結びつける
		doc.Updated += UpdateEventHandler(textView.Notify);
		doc.Updated += UpdateEventHandler(graphView.Notify);
		// これでDocumentの更新時に、それぞれのNotifyが呼ばれる
	}
}

定数/読み取り専用

定数

C#ではプリプロセッサが限定されて、#defineなどが使えません。 なので定数の宣言にはconstを使います。EffectiveC++なんかを読んでいて、C++でもconstを使っている人も多そうですけど。 ただし、定数もクラスの中で書く必要があります。

読み取り専用

定数に近いのですが、違いとして実行時に1度だけ初期化でき、 その後はプログラムの終了まで書き換えることが出来ない変数です。

class MyClass {
	// コンパイル時に決定
	const int DEFAULT=256;
	// 実行時に設定可能
	readonly int MAX;
	
	MyClass(int max) {
		// 一度だけ書き込み可
		MAX = max;
	}
}

後半ちょっと早足になりましたがC#については、とりあえずこんなとこ。ただし、 C#はあくまでもCLRありきなので、最終的にはCLRについての知識も深める必要があります。

追記
そろそろ自分でも勉強不足のところが見え始めてきたので書き直したい・・・

Home>