C# で gcc のようにファイルを指定してビルドする方法
以前、 Visual C++ で gcc のようにファイルを指定してビルドする方法を紹介しました。 今回はその C# 版です。
ファイルを指定したビルドというのは、 Visual Studio のソリューションファイル(sln)やプロジェクトファイル(csproj)を使わずにコマンドラインでコンパイルすることです。ちょっとしたサンプルプログラムを試す場合などに便利です。
また、 define を使用した条件コンパイルやエントリーポイントを指定することによって、 クラスの動作確認のコードを実行するといったことにも使えます。
C# には gcc や cl.exe(Visual C++) に相当する csc.exe という C# コンパイラーがあります。 これを使ってビルドを行います。
なお、 プロジェクトファイルを使ってコマンドライン上でビルドする場合は MsBuild を使用します。こちらの使い方については以前の記事をご覧下さい。
環境設定
csc.exe は .NET フレームワークのフォルダー にあります。これは以下のようなパスです。 最後のフォルダー名はフレームワークのバージョンによって変わります。
c:\Windows\Microsoft.NET\Framework\v4.0.30319このフォルダーを環境変数 Path に追加します。
ビルド方法
ビルドする場合には次のようなコマンドを実行します。csc ソースファイル [...]
> csc hello.cshello.cs :
using System; namespace Hello { class Program { static void Main() { Console.WriteLine("Hello World!"); } } }実行すると "ソースファイルのベース名.exe"(hello.exe) がカレントフォルダーに作成されます。
複数のファイルを指定した場合にはエントリーポイント(Main メソッド) のあるソースファイルの名前が使われます。
ただし、ファイルを指定する際に一つ注意点があります。
それは csc.exe がパス区切りとして /(スラッシュ) を認識しない点です。 相対パスなどで別フォルダーのファイルを指定する場合には必ず \ を使って下さい。
コマンドオプション
よく使いそうなオプションをいくつか挙げておきます。詳細は /? オプションのヘルプを見て下さい。
オプション | 短い形式 | 説明 |
---|---|---|
/help | /? | ヘルプを表示 |
/out:<ファイル> | 出力ファイル名を指定 | |
/target:exe | /t:exe | コンソール アプリケーションをビルド(既定) |
/target:winexe | /t:winexe | GUI アプリケーションをビルド |
/define:<シンボルリスト> | /d | 条件付きコンパイルシンボルを定義 |
/main:<型> | /m | エントリーポイントを含む型を指定 (他のエントリーポイントはすべて無視) |
/warn:<n> | /w | 警告レベル (0-4) を設定 |
/codepage:<n> | ソース ファイルを開くときに使用するコードページを指定 | |
/utf8output | UTF-8 エンコードでコンパイラーのメッセージを出力 |
C# インターフェース - IComparable, IComparable(T)
インターフェース | 対象 | 使えるようになる機能 |
---|---|---|
IComparable, IComparable<T> | コンテナーに格納するクラス | 格納したコンテナーのソート、検索など |
IEnumerable | コンテナークラス | foreach |
IEnumerable<T> | LINQ の各種メソッド | |
IDisposable | ファイルなどの後処理のタイミング管理が必要なクラス | using |
今回は IComparable, IComparable<T> インターフェースについての説明です。
用途
配列(Array)などのコンテナークラスでは数値を格納した場合に、 ソートや二分探索(ソート済みのデータから高速に検索)などができます。IComparable または IComparable<T> インターフェースを継承しておけば、自作のクラスでもこれらの機能が使えるようになります。
また、 SortedList のような比較を必要とするコンテナーでは継承していないと格納することができません。
コンテナーに格納するようなクラスでは実装しておいた方がいいでしょう。
IComparable
まず IComparable から説明します。実装するメソッド
IComparable インターフェースを継承した場合、CompareTo() メソッドを実装する必要があります。int CompareTo(Object obj);これは C 言語で言えば、文字列の strcmp() に相当する比較用メソッドです。
比較対象(obj)を受け取り、自身(this)と比べた結果を数値として返します。
比較 | 戻り値 |
---|---|
obj < this | -1 以下の整数 |
obj == this | 0 |
this < obj | 1 以上の整数 |
サンプル
サンプルとして点クラスを作成しました。 コンパイルする場合は以下のコマンドを実行します。> csc IComparableSample.cs Point_IComparable.cscsc.exe の使用したコンパイル方法については以前の記事を見て下さい。
点クラス Point は x, y のプロパティを持たせています。
CompareTo() メソッドはまず x で比較し、 同じ場合に y で比較するという仕様にしました。
Point_IComparable.cs(抜粋) :
class Point : IComparable { public int x { get; set; } public int y { get; set; } public Point(int x, int y) { this.x = x; this.y = y; } /// <summary> /// 比較メソッド /// </summary> public int CompareTo(Object obj) { if (obj == null) return 1; Point other = (Point)obj; if (other.x == x) { return y - other.y; } return x - other.x; } }作成した Point クラスを使った例は IComparableSample.cs に記述しています。
ここで、BinarySearch() の例を後にしているのは、ソートしたデータでなければ検索できないためです。
IComparableSample.cs(抜粋) :
class Program { static void Main() { // Point を格納したデータを準備 Point [] ary = { new Point(1, 2), new Point(3, 5), new Point(1, 9), new Point(4, 1), new Point(3, 1) }; DumpArray("Original", ary); // データのソート Array.Sort(ary); DumpArray("Sorted", ary); // 二分探索 Console.WriteLine("## Binary Search ##"); var target = new Point(3, 1); int pos = Array.BinarySearch(ary, target); Console.WriteLine("{0} @ {1}", target, pos); } }実行結果 :
## Original ## (1, 2) (3, 5) (1, 9) (4, 1) (3, 1) ## Sorted ## (1, 2) (1, 9) (3, 1) (3, 5) (4, 1) ## Binary Search ## (3, 1) @ 2
IComparable<T>
IComparable<T> は IComparable のジェネリック版です。 どちらを使っても同じように使うことができます。わかりやすいように先に IComparable の説明を行いましたが、 実際に使うのは ジェネリック版の IComparable<T> が良いでしょう。
CompareTo() 実装時にキャストしなくて済みますし、若干処理が速いようです。 IComparable<T> の方がいいなら、なぜ IComparable があるのかというと、 IComparable が先にできていたためです。 ジェネリックは C# 2.0 から追加されています。
実装するメソッド
IComparable<T> の場合はジェネリックな CompareTo() を実装します。int CompareTo(T other);
サンプル
点クラスを使用する側のコードは IComparable と同じ IComparableSample.cs を使用します。> csc IComparableSample.cs Point_IComparable_T.cs
Point_IComparable_T.cs(抜粋) :
class Point : IComparable<Point> { public int CompareTo(Point other) { if (other == null) return 1; if (other.x == x) { return y - other.y; } return x - other.x; } }実行結果も同じです。
ジェネリックな Point
ついでに Point をジェネリックにしたサンプルも紹介します。前のサンプルは Point の x, y の型は int 固定でしたが、 double などの任意の型を仕様できるようになります。
ただし、 CompareTo() で比較する必要があるので、 完全に任意ではなく、x, y の型は IComparable を継承しているなければなりません。
クラスのジェネリックと IComparable がジェネリックかどうかは関係ありません。 サンプルでは IComparable<T> で実装していますが、 IComparable でも同じように実装できます。
サンプルは今度は 1 つのファイルに記述しています。
> csc GenericPointSample.cs
GenericPointSample.cs(抜粋) :
class Point<Type> : IComparable<Point<Type> > where Type : IComparable { public Type x { get; set; } public Type y { get; set; } public Point(Type x, Type y) { this.x = x; this.y = y; } public int CompareTo(Point<Type> other) { if (other == null) return 1; if (x.CompareTo(other.x) == 0) { return y.CompareTo(other.y); } return x.CompareTo(other.x); } } // Point実行結果は他のサンプルと同じです。
C# インターフェース - IEnumerable と yield
また、 yield の使用方法も説明をあわせて行なっています。
用途
IEnumerable は自作のコンテナークラス などで継承します。IEnumerable を継承していると foreach で要素にアクセスすることができるようになります。
foreach を使えるようになるだけでは、大したメリットではないかも知れません。 実際には IEnumerable を継承するのであれば、ジェネリック版の IEnumerable<T> を継承した方がいいでしょう。
ただし、わかりやすさのために先に IEnumerable を説明しています。 IEnumerable<T> については次回説明したいと思います。
実装するメソッド
IEnumerable インターフェースを継承した場合、GetEnumerator() メソッドを実装する必要があります。IEnumerator GetEnumerator()
戻り値の IEnumerator もインターフェースで、 GetEnumerator() を実装するにはさらに IEnumerator を継承したクラスを用意しておく必要があります。しかし、この IEnumerator を実装したクラスを用意するのは結構面倒です。
これを簡単にする yield という機能があります。 この yield を使う場合、使わない場合の 2 通りの方法を説明します。
IEnumerator の使用
まずは yield を使わずに IEnumerator の継承クラスを使う実装について紹介します。ただし、ちゃんと実装するのは面倒なので、 サンプルでは既存のものを使って少し楽をすることにします。
このサンプルとして前回作成した 点クラス(Point) を複数持つ 多角形クラス(Polygon) を作成しました。 コンパイル:
> csc IEnumerableSample.cs Point.cs Polygon_Enumerator.cs多角形クラスをただ実装するのであれば、 ArrayList などのクラスを継承した方が簡単です。
しかし、まじめに作成しようとした場合、図形の抽象クラスから継承するといったことになるかと思います。 その際には IEnumerable と抽象クラスを多重継承します。
サンプルはそういった場合と考えて下さい。
GetEnumerator() の実装には点を格納している Array メンバーの GetEnumerator() を利用しています。
Polygon_Enumerator.cs (抜粋) :
class Polygon : IEnumerable { private Point[] _points; public Polygon(Point[] points) { if (points != null) { _points = new Point[points.Length]; for (int cnt = 0; cnt < points.Length ; cnt++) { _points[cnt] = new Point(points[cnt]); } } } public IEnumerator GetEnumerator() { return _points.GetEnumerator(); } }foreach を使ったアクセス例は IEnumerableSample.cs に記述しています。
IEnumerableSample.cs (抜粋) :
class Program { static void Main() { Point[] points = { new Point(0, 0), new Point(5, 0), new Point(0, 5) }; Polygon poly = new Polygon(points); Console.Write("Polygon = {\n "); foreach (Point pos in poly) { Console.Write("{0} ", pos); } Console.WriteLine("\n}"); } }実行結果 :
Polygon = { (0, 0) (5, 0) (0, 5) }
yield の使用
前のサンプルではメンバーの GetEnumerator() を委譲して使いましたが、 実際に自作のコンテナー等で IEnumerator を継承したクラスから作成するのは面倒です。Ruby では似たようなことを行うとき、 yield を使った内部イテレーターの仕組みがあるので、簡単に作成することができます。 Ruby の経験があると C# のものは非常に面倒くさいと感じてしまいます。
しかし、 C# にも yield の機能が後から追加されました。 C# でも簡単に作成できるようになっています。(実際には再帰が書きづらいなど、完全に同じとも言えないのですが...)
今度は yield を使った方法を紹介します。
先ほどのサンプルを yield を使って書き直してみます。 Polygon_yield.cs 以外は同じファイルを使用しています。 コンパイル:
> csc IEnumerableSample.cs Point.cs Polygon_yield.csyield return を使って、返したい要素を返します。
戻り値が IEnumerator になっていませんが、その辺は C# のコンパイラーが補ってくれます。
Polygon_yield.cs (抜粋) :
public IEnumerator GetEnumerator() { foreach (Point pos in _points) { yield return pos; } }実行結果は前のサンプルと同じです。
なお、条件で返す作業を中断したい場合には yield break を使用します。
抽象図形クラスを継承したサンプル
多角形クラスのサンプルだとあまり yield のメリットが感じられないかもしれないので、 もう一つサンプルを紹介します。もう少しちゃんと作って図形の抽象クラスから四角形と多角形を継承することにします。
コンパイル:
> csc Shape.cs Point.cs四角形で GetEnumerator() を実装する場合には、 yield で 4 回、点を返しています。
このようにループを回すような処理でなくても yield を使うことができます。
Shape.cs(抜粋) :
class Rectangle : Shape { override public IEnumerator GetEnumerator() { yield return new Point(x, y); yield return new Point(x+w, y); yield return new Point(x+w, y+w); yield return new Point(x, y+w); } }使用方法のサンプルでは、四角形、多角形オブジェクトを Shape の配列として格納し、 ともに foreach でアクセスしています。
Shape.cs(抜粋) :
static void Main() { Shape[] shapes = { new Rectangle(10, 5, 10, 5), new Polygon(new Point[] { new Point(0, 0), new Point(5, 0), new Point(0, 5) }) }; foreach (Shape fig in shapes) { Console.Write("{0} = [ ", fig); foreach (Point pos in fig) { Console.Write("{0} ", pos); } Console.WriteLine("]"); } }実行結果 :
Geometry.Rectangle = [ (10, 5) (20, 5) (20, 15) (10, 15) ] Geometry.Polygon = [ (0, 0) (5, 0) (0, 5) ]
yield ブロック
yield の利用方法をもう一つ紹介します。先程は自作のクラスに対して yield を使って IEnumerable の実装を行いましたが、 今度は IEnumerable を実装したコンテナーを戻り値として返す関数を yield で作成します。
サンプルは多角形から頂点以外の点の集合を返す関数にします。
折れ線(polyline)には始点と終点が一致した閉じた折れ線(close polyline) と一致しない開いた折れ線(open polyline)があります。
多角形クラスの頂点を順にアクセスした場合には、開いた折れ線となります。
しかし、計算の時などで閉じた折れ線が欲しい時があります。 この 各頂点 + 始点 を返す関数を作成します。
コンパイル:
> csc YieldBlockSample.cs Point.cs戻り値の型と最後にもう一度始点を返しているところを除いて、実装はほぼ GetEnumerator() と同じです。
YieldBlockSample.cs (抜粋) :
class Polygon : IEnumerable { public IEnumerator GetEnumerator() { foreach (Point pos in _points) { yield return pos; } } /// <summary> /// 閉じた折れ線の取得 /// </summary> public IEnumerable ClosedPolyline() { foreach (Point pos in _points) { yield return pos; } yield return _points[0]; } }戻り値の使用例と実行結果です。
static void Main() { Point[] points = { new Point(0, 0), new Point(5, 0), new Point(0, 5) }; Polygon poly = new Polygon(points); Console.Write("Polygon = {\n "); foreach (Point pos in poly) { Console.Write("{0} ", pos); } Console.WriteLine("\n}"); Console.Write("Closed Polyline = {\n "); foreach (Point pos in poly.ClosedPolyline()) { Console.Write("{0} ", pos); } Console.WriteLine("\n}"); }実行結果 :
Polygon = { (0, 0) (5, 0) (0, 5) } Closed Polyline = { (0, 0) (5, 0) (0, 5) (0, 0) }
遅延評価
"一旦コンテナーを作って貯めるのは、点が多い場合にはメモリーがもったいない"と思われた方もいるかも知れません。しかし、 C# では遅延評価という機能があるため、 無駄にメモリーを確保したりはしていません。
これを確認するためにサンプルを少し修正します。 コンパイル:
> csc YieldBlockSample_lazy.cs Point.cs今度は閉じた折れ線を返すときに要素の参照をそのまま返すのではなく、 新しいオブジェクトを作って返すようにしています。
YieldBlockSample_lazy.cs (抜粋) :
public IEnumerable ClosedPolyline() { foreach (Point pos in _points) { yield return new Point(pos); } yield return new Point(_points[0]); }使用時の処理を次の手順に変更しています。
- ClosedPolyline() の戻り値を変数に格納
- 元の多角形の要素に変更を加える
- 戻り値のコンテナーに対して foreach でアクセス
static void Main() { // 遅延処理の確認 var polyline = poly.ClosedPolyline(); Console.WriteLine("polyline = {0}", polyline); poly[1].x = 2; Console.WriteLine("Change poly[1].x = {0}", poly[1].x); Console.Write("Closed Polyline = {\n "); foreach (Point pos in polyline) { Console.Write("{0} ", pos); } Console.WriteLine("\n}"); }実行結果 :
Polygon = { (0, 0) (5, 0) (0, 5) } polyline = IEnumerableSample.Polygon+<ClosedPolyline>d__6 Change poly[1].x = 2 Closed Polyline = { (0, 0) (2, 0) (0, 5) (0, 0) }実行結果では ClosedPolyline() の戻り値のクラス名を表示しています。 C# が自動的に作成したものなので、よくわからない名前になっていますが、 実はこれは単なるコンテナーではありません。
実行結果を見ると ClosedPolyline() を実行し、 戻り値をとった後に値を変えているにもかかわらず、 戻り値のコンテナーにまで影響をあたえています。
yield で返すときに新しくオブジェクトを作っているので、 参照だからというわけでもありません。
すなわち、 foreach の度に ClosedPolyline() の内部処理を行なっている ということです。
このようにすぐに関数内の処理を行わず、使用される時に処理が実行されることを遅延評価と言います。
ただし、一旦コンテナーの内容を確定させたいこともあります。 この場合には ToArray() などのメソッドで別のコンテナーに変換することによって処理を行なっておくこともできます。