RSS RSS feed | Atom Atom feed

.NET の基礎知識

Common Language Infrastructure (CLI; 共通言語環境仕様)

言語に依存しない,ソフトウェアの実行環境を仕様化したもの。標準化されてヨーロッパ電子計算機工業会の規格である ECMA-335 となった。この仕様を Windows 上で実装したものが CLR である。

  • CLS (共通言語仕様) は,言語に依らない型,命名規約,継承の仕組み,などを仕様として規定したもので,CLI の一部である。

共通言語ランタイム (CLR)

  • IL コード仮想マシン,JIT を含む,実行時エンジン。
  • ガベージコレクションを含むメモリ管理。
  • 安全性への配慮。
  • CTS (共通型システム) は,CLS のサブセットである。これは CLR が CLI の機能制限版なのではなく,CLS よりもゆるい制約に基づいたプログラムを許容するという意味で,機能拡張となっているとも言える。

クラスライブラリ

  • Windows アプリケーション
  • Web アプリケーション
  • Web サービス

Mono

  • Linux 等の UNIX 系 OS,および Windows をターゲットとする。
  • オープンソース。
  • C#,C,C++,Pascal,COBOL のサポートを目指している。

C# とは?

時代的には C→C++→Java→C# という流れであるが,C# は C++ と Java の中間的な機能を持っていると言われるようである。また,プロパティ,インデクサ,イベントやデリゲートなどといった,コンポーネント指向寄りの機能を備えているという点は,おそらく Visual Basic や Delphi の影響と思われる。

タイプセーフで,オブジェクト指向で,ガベージコレクションを持つという点は Java と同様であるが,COM (Active-X) などの既存のコンポーネントとの親和性では明らかに優れている。

Microsoft は,C# を .NET Framework の主力開発言語であると考えているらしい。

C# プログラミング概要

Hello World

伝統的な Hello, World は,C# では次のようになる。

1: // Hello World
2: class Hello
3: {
4:     static void Main()
5:     {
6:         System.Console.WriteLine("Hello, World.");
7:     }
8: }

コメント

「//」から行末まで, または「/*」から「*/」まではコメントである。

Main メソッド

プログラム内のどこかにプログラムの実行を開始から終了まで制御する Main メソッドを定義する必要がある。Main メソッドはクラス (class) または構造体 (struct) 内の静的 (static) なメソッドとして定義する。返値型は void または int, 引数は string[] または引数なしとすることができる。

// こんにちは、世界。
class Hello
{
    static int Main(string[] args)
    {
        System.Console.WriteLine("こんにちは、世界。");
        return 0;
    }
}

C や C++と違い,引数を string[] としたとき,プログラム名は渡されない。

返値型を int としたとき,Main メソッドが返した値はプログラムの終了ステータスとして扱われる。

入出力

入出力は通常 .NET Framework の機能を利用する。上例では,名前空間 System の Console クラスが持つ,WriteLine メソッドを利用することになる。

C# プログラムの構造

C#プログラムは 1 個以上のファイルからなる。各ファイルは 1 個以上の名前空間を含み, 各名前空間はクラス,構造体,インターフェース,列挙型,デリゲートといった型定義や,他の名前空間を含む。以下は,このすべてを含むファイルの例である。

// C#プログラムの骨組み
using System;

namespace MyNamespace1 
{
    class MyClass1 
    {
    }

    struct MyStruct 
    {
    }

    interface IMyInterface 
    {
    }

    delegate int MyDelegate();

    enum MyEnum 
    {
    }
 
    namespace MyNamespace2 
    {
    }

    class MyClass2 
    {
        public static void Main(string[] args) 
        {
        }
    }
}

C#プログラムの開始と終了

  • Main メソッドは, プログラムのエントリポイントであり, プログラムの開始から終了までを制御する。
  • Main メソッドは, クラスまたは構造体内の静的メソッドである。
  • Main メソッドの返値型は void または int である。
  • Main メソッドは引数を持たないか, または string[] 型の引数を持つ。
  • Main メソッドが引数を持つ場合, 引数にはコマンドライン引数が渡される。
  • プログラム名は引数として渡されない。

アセンブリとグローバルアセンブリキャッシュ

  • アセンブリとは .NET Framework アプリケーションを構成する基本的な単位である。
  • アセンブリにはメタデータと呼ばれる内部バージョン番号やデータ,型の情報が含まれている。
  • アセンブリは,必要となった時点でロードされる。使用されないアセンブリはロードされない。
  • アセンブリには複数のモジュールを含ませることができる。
  • アセンブリは .EXE や .DLL として実現される。
  • グローバルアセンブリキャッシュ (GAC) を用いて,アセンブリを他のアプリケーションと共有することができる。
  • GAC へ配置するアセンブリは,厳密名を定義しなければならない。

データ型

データ型の概要

  • int,char 等の組込みデータ型とクラス,構造体等のユーザ定義データ型がある。
  • 値型と参照型がある。

組込みデータ型

組込みの値型
型名 概要 範囲 サイズ
sbyte 符号付整数 -128~127 1
byte 符号なし整数 0~255 1
short 符号付整数 -32768~32767 2
ushort 符号なし整数 0~65535 2
int 符号付整数 -231~231-1 4
uint 符号なし整数 0~232-1 4
long 符号付整数 -263~263-1 8
ulong 符号なし整数 0~264-1 8
bool 論理型 (false または true) false,true 1
char 文字型 (UCS-2) '\u0000'~'\uFFFF' 2
float IEEE 754 単精度浮動小数点数 ±0, ±1.5×10-45~±3.4×1038,±∞,NaN (有効数字7桁) 4
double IEEE 754 倍精度浮動小数点数 ±0, ±5.0×10-324~±1.7×10308,±∞,NaN (有効数字15~16桁) 8
decimal 10進浮動小数 0,±1.0×10-28~±7.9×1028 (有効数字28~29桁) 16

 

組込みの参照型
型名 概要
object あらゆる型の基底クラス
string 文字列型 (UTF-16)。object から直接派生したクラス

ユーザ定義データ型

ユーザ定義の値型
名称 概要
構造体 キーワード struct を使用して定義する
列挙 キーワード enum を使用して定義する

 

ユーザ定義の参照型
型名 概要
クラス キーワード class を用いて定義する
インターフェース キーワード interface を用いて定義する
配列 同じ型のデータの並びを表現する
デリゲート キーワード delegate を使用して定義する

演算子

演算子
演算子のカテゴリ 演算子
算術 +   -   *   /   %
論理 (ブールおよびビットごと) &   ^   !   ~   &&   ||   true   false
文字列の連結 +
インクリメント、デクリメント ++   --
シフト <<   >>
関係 ==   !=   <   >   <=   >=
代入 =   +=   -=   *=   /=   %=   &=   |=   ^=   <<=   >>=
メンバ アクセス .
添字 []
キャスト ()
条件 ?:
デリゲートの連結と削除 +   -
オブジェクトの作成 new
型情報 as   is   sizeof   typeof
オーバーフローの例外制御 checked   unchecked
間接およびアドレス *   ->   []   &

ボックス化 (boxing) とボックス化解除 (unboxing)

ボックス化とボックス化解除と呼ばれる機能は,値型を参照型として扱うための機能である。ボックス化とは,値型を参照型オブジェクトのインスタンスに詰込むことである。こうすることで値型はガーベージコレクションの対象となるヒープへコピーされる。逆にボックス化解除とはボックス化によって参照型オブジェクトに詰込まれた値型を取出すことである。

int i = 123;
object o = (object)i;        // ボックス化
o = 123;                     // ボックス化。特にキャストは必要ない。
i = (int)o;                  // ボックス化解除。キャストが必要。

ボックス化は単純な代入と比べて新しいオブジェクトを生成するなどの複雑な処理が必要であり,時間がかかる。ボックス化解除は,ボックス化ほどではないが,キャスト処理の分だけ単純な代入よりも時間がかかる。

名前空間

名前空間は,複数のクラスライブラリを同時に使用する場合などで,名前の衝突を防ぐための機能である。

名前空間のメンバにアクセスするには,using キーワードを使用して省略可能にするやり方と,明示的に名前空間を指定してアクセスするやり方がある。

using System;
...
Console.WriteLine("Hello, World");
System.Console.WriteLine("Hello, World");

using キーワードを使用して,名前空間の別名を宣言することもできる。

using com = System.Data.Common;
using sql = System.Data.SqlClient;
...
sql.SqlConnection cn = new sql.SqlConnection(...);
com.DbTransaction tx = cn.BeginTransaction();

名前空間を定義するには namespace キーワードを用いる。名前空間はネストすることができ,その場合 2 通りの書き方がある。

namespace N1
{

    // 名前空間 N1 のメンバ (クラスなど) の宣言

    namespace N2
    {
        // 名前空間 N1.N2 のメンバ
    }
}

namespace N1.N2
{
    // 名前空間 N1.N2 のメンバ
}

オブジェクトとクラス (class) と構造体 (struct)

C# はオブジェクト指向言語であり,ウィンドウやコントロールやデータ構造といったオブジェクトを表現するためにクラスや構造体を使うことになる。

  • オブジェクトとは特定のデータ型の実体を言う。データタイプは実行時に生成されるオブジェクトの設計図である。
  • クラスや構造体を利用して新しいデータ型を定義できる。
  • クラスや構造体は C# アプリケーションのコードやデータを含む塊である。C# アプリケーションは常に最低でも 1 個のクラスを含んでいる。
  • 構造体は特に小さなデータを格納するときには理想的な,手軽なクラスと考えられる。
  • C# のクラスは継承をサポートする。すなわち,既存のクラスをベースに,新しい別のクラスを定義できる。

オブジェクト

オブジェクトとは,データ,振舞い,アイデンティティを持ったプログラミング上の構造である。オブジェクトのデータはフィールド,プロパティ,イベントに含まれ,オブジェクトの振舞いはメソッドとインターフェースで定義される。

オブジェクトがアイデンティティを持つとは,2 つのオブジェクトが全く同じデータを含んでいるからといって,必ずしも「同一」のオブジェクトとは見なされないということである。

C# ではクラスと構造体を通してオブジェクトを定義する。クラスと構造体は,その型のすべてのオブジェクトの役割の設計図を構成する。

  • ウィンドウやコントロールを含めて,C# で扱うすべてのものはオブジェクトである。
  • オブジェクトは,そのクラスや構造体によって定義された鋳型から実体化 (生成) される。
  • オブジェクトが含む情報の取得や変更にはプロパティを使用する。
  • オブジェクトの動作を表すためには,メソッドやイベントを使用する。
  • あらゆる C# のオブジェクトは Object を継承する。

クラス (class)

クラスによってデータ型を定義することができる。クラスを定義すると,そのインスタンス (実体) を生成することが可能となる。クラスの実体を生成すると,その参照が返される。オブジェクトへ割当てられた変数には,オブジェクトのデータそのものではなく,このオブジェクトへの参照が格納される。オブジェクトへの参照は,複数の変数へコピーすることができるが,コピーされた参照はすべて元の同一のオブジェクトを表現することになる。このようなわけでクラスは「参照型」であるという。すべての参照型は System.Object から派生する。

クラスはキーワード class を使って定義する。

public class MyClass : MyBaseClass
{
    // メソッド,プロパティ,フィールド,イベントの定義
}

上例で,クラスの名前が「MyClass」であること,アクセスレベルが public であること,基底クラス MyBaseClass から派生していること,を定義している。アクセスレベルが public であるというのは,どこからでもこのクラスを指定することができることを意味している。基底クラスが MyBaseClass から派生しているとは,基底クラス MyBaseClass のすべてのメンバ (メソッド,プロパティ,フィールド,イベント) を派生クラス MyClass も受継いで持っていることを意味する。この機能のことを「継承」という。基底クラスもまた基底クラスを持てるため,一つのクラスには基底クラスがいくつでもあり得る。派生クラスのオブジェクトは基底クラスのオブジェクトの性質をすべて受継いでいるため,派生クラスのオブジェクトは基底クラスのオブジェクトとして使用することができる。このため,派生クラスへの参照は,基底クラスへの参照へ変換できるようになっている。

基底クラスを指定せずにクラスを定義すると,暗黙のうちに基底クラスが System.Object となる。あらゆるクラスは,その基底クラスを遡って行くと最後には System.Object を基底クラスとして持っている。したがって,すべてのクラスオブジェクトは,System.Object 型であるとして扱うことができる。

public class MyPersonClass
{
    // フィールド
    private string name;
    // コンストラクタ
    public MyPersonClass()
    {
        name = "unknown";
    }
    // メソッド
    public void SetName(string newName)
    {
        name = newName;
    }
}

上例は,フィールドとコンストラクタとメソッドをそれぞれ 1 個ずつ持つパブリックなクラスの例である。このクラスのオブジェクトは次のように new キーワードを用いて生成し,使用できる。

MyPersonClass person1 = new MyPersonClass();
person1.SetName("尚遠");

クラスの概要

  • クラスは参照型である。
  • 単一継承のみをサポートしている。(Java と同様。C++ は多重継承をサポートしている。) つまり,直接の基底クラスは 1 個だけに限定される。
  • インターフェースはいくつでも実装できる。
  • クラス定義は,複数のファイルに分割して記述できる。(C# 2.0)
  • 静的なクラスは,静的なメンバのみを持つ。(C# 2.0)

構造体 (struct)

構造体はキーワード struct を使用して定義する。

public struct MyStruct
{
    // フィールド,プロパティ,メソッド,イベントの定義
}

構造体定義の構文は,クラス定義の場合とほとんど同様であるが,クラスにはない制限がある。

  • インスタンスフィールドの宣言に初期化子は書けない。ただし静的フィールドには初期化子が書ける。
  • デフォルトコンストラクタ (引数なしのコンストラクタ) およびデストラクタを宣言できない。

コンパイラが自動的に生成するデフォルトコンストラクタは,構造体が含むすべてのフィールドをデフォルト構築する。構造体はクラスや他の構造体から派生することはできない。

構造体は値型である。構造体のオブジェクトを生成し,変数へ代入すると,構造体の実体が変数へと格納される。変数をコピーすると構造体自身がコピーされ,コピー元オブジェクトとコピー先オブジェクトの一方に対する変更は他方には影響を与えない。構造体はアイデンティティを持たない。構造体への参照を扱う手段はないため,全く同じ値を持つ構造体同士を区別する手段もないからである。C# のすべての値型は,System.ValueType を継承する。System.ValueType は System.Object を継承する。

値型は参照型へいつでも変換 (ボックス化) できる。

構造体の概要

  • 構造体は値型である。(クラスは参照型である。)
  • 構造体をメソッドの引数として渡す場合はその参照ではなく値が渡される。
  • クラスとは異なり,構造体を実体化するのに new 演算子は必要ない。
  • 構造体にはコンストラクタを宣言できるが,必ず引数を持たなければならない。
  • 構造体は他の構造体やクラスからは派生できないし,構造体からクラスを派生することもできない。すべての構造体は System.ValueType を直接継承し,System.ValueType は System.Object を継承している。
  • 構造体はインターフェースを実装できる。
  • インスタンスフィールドの初期化子は書けない。

構造体の使用方法

構造体は「位置」,「長方形」や「色」といった,小さなオブジェクトを表現するのに適している。このような,オブジェクトをクラスを用いて表現することもできるが,しばしば構造体の方が効率がよくなる。たとえば,「位置」クラスの 1000 個のオブジェクトからなる配列を生成する場合,各位置オブジェクトのためにメモリを確保することになる。

public struct Point
{
    public int x;
    public int y;

    public Point(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

構造体にはデフォルトコンストラクタ (引数を持たないコンストラクタ) は定義できない。デフォルトコンストラクタはコンパイラが勝手に用意し,常にすべてのメンバをデフォルト値で初期化する。また,インスタンスフィールドを初期化子で初期化することもできない。

構造体を new 演算子で生成すると,適切なコンストラクタを使用して初期化することができる。クラスとは違い,new 演算子を使わずに生成することができるが,この場合は構造体は初期化されず,すべてのメンバを明示的に初期化するまでは参照できないことになる。

構造体はクラスと違って継承を利用できない。構造体は他の構造体やクラスから派生させることはできないし,クラスの基底クラスとすることもできない。しかしながら,構造体は Object を継承しているし,クラスと同様にインターフェースを実装することもできる。

C++ とは違って,struct キーワードを使いクラスを宣言することはできない。C# では,クラスと構造体は本質的に異なるものである。構造体は値型であるがクラスは参照型である。

参照型が必要な場面はともかく,小さなクラスは構造体で置き換えることで効率が良くなることがある。

using System;
public struct Point
{
    public int x, y;

    public Point(int p1, int p2)
    {
        x = p1;
        y = p2;
    }
}

class MainClass
{
    public static void Main()
    {
        Point myPoiont = new Point();
        Point yourPoint = new Point(10, 10);
        Point hisPoiont;

        hisPoint.x = 20;
        hisPoint.y = 20;

        Console.Write("My Point:    ");
        Console.WriteLine("x = {0}, y = {1}", myPoint.x, myPoint.y);
        Console.Write("Your Point:  ");
        Console.WriteLine("x = {0}, y = {1}", yourPoint.x, yourPoint.y);
        Console.Write("His Point:   ");
        Console.WriteLine("x = {0}, y = {1}", hisPoint.x, hisPoint.y);
    }
}
My Point:    x = 0, y = 0
Your Point:  x = 10, y = 10
His point:   x = 20, y = 20

上例で, myPoint はデフォルトコンストラクタによって, yourPoint は宣言したコンストラクタによって, それぞれ初期化し, hisPoint はメンバ毎に初期化している。

クラスおよび構造体のメンバ

クラスおよび構造体のオブジェクトの持つデータや振舞いを定義するメンバとしては,以下のものがある。

  • メソッド
  • コンストラクタ
  • デストラクタ
  • プロパティ
  • フィールドと定数
  • インデクサ
  • 型 (クラスや構造体など)

クラスおよび構造体のメソッド

C# では,クラスと構造体のメソッドはオブジェクトの動きを定義する。メソッドは本質的には一連の文を含むコードブロックである。メソッドは値を計算し,オブジェクトデータに対する操作を実行し,オブジェクトデータに基づく動作を実行するのに利用される。またこれだけにとどまらず,メソッドは C# 言語で表現できるどんな操作も実行することができる。

メソッドは,クラスブロックの中でアクセスレベル,戻り値の型,メソッド名,それにもしあればメソッドパラメータの型と名前を指定することによって宣言される。メソッド引数は,括弧で囲み,カンマで区切られる。空の括弧は,メソッド引数がないことを示す。

class MyClass
{
    void MyMethod1() {}
    void MyMethod2(int param1) {}
    int MyMethod3(int param1, int param2) {}
}

特定のオブジェクトについてメソッドを呼出すのは,フィールドにアクセスする方法と似ている。オブジェクト名の後にドット,メソッド名,それに括弧で引数を書けばよい。

MyClass myObj = new MyClass();

int MyNumber1 = 1;
int MyNumber2 = 2;

// メソッドの呼出し
myObj.MyMethod1();
myObj.MyMethod2(MyNumber1);
myObj.MyMethod3(MyNumber1, MyNumber2);

メソッド引数

メソッドパラメタはメソッドのブロックの中で利用可能である。デフォルトでは,メソッドパラメタはメソッドを呼出す側によって渡されたオブジェクトのコピーである。つまり,パラメタへの変更は,呼出し元のオブジェクトに反映されない。メソッドのコードブロックでは,パラメタだけでなく,同じクラスで定義された他のメソッド,フィールド,プロパティ,イベントに,直接アクセスすることができる。

class MyClass
{
    int myInt;

    void MyMethod1(int param1)
    {
        myInt = param1;
    }

    void MyMethod2(int myInt)
    {
        this.myInt = myInt;
    }
}

クラスのメンバとパラメタを明示的に区別するには,現在のインスタンスを示す this キーワードが使用できる。上例の MyMethod2 がその例である。このようにパラメタ名がメンバ名と同じ場合に利用できる。

メソッドは値を呼出し元に返すことができる。戻り値型 (メソッド名の前に指定する型) が void でない場合,メソッドは return キーワードを使用することで値を返すことができる。キーワード return の後に戻り値型に適合する値を続ければその値が返され,メソッドの実行はそこで終了する。戻り値型が void であっても,値のない return 文をメソッドの実行を終了させるために利用できる。個の場合,return キーワードがなくても,コードブロックの終端に達すると,メソッドの実行は終了する。一方,void 以外の戻り値型のメソッドは,値を返すために必ず return キーワードを使用する必要がある。

class MyClass
{
    int AddTwoNumbers(int param1, int param2)
    {
        return param1 + param2;
    }

    void MyMethod2(int param)
    {
        System.Console.WriteLine(param);
        // 戻り値型が void であるため, return は不要
    }
}

呼出し元は,その型の値が利用できるどこにでもメソッド呼出しを記述できる。

MyClass myObj = new MyClass();

int result = myObj.AddTwoNumbers(1, 2);     // 戻り値を result に格納
myObj.MyMethod(result);                     // 別のメソッドの引数として渡す

myObj.MyMethod(myObj.AddTwoNumbers(1, 2));  // このように使うこともできる

仲介役の変数を用いて複雑な式を読みやすい単純な式に変形することができる。また,メソッドの戻り値を複数回利用する場合には,必要となることもある。

メソッドパラメータで使用するキーワード

ref,out,param の 3 つのキーワードはメソッドパラメータの振舞いを変えるために用意されている。

デフォルトでは,メソッドに渡されるパラメータ値はコピーであり,メソッド本体での変更は呼出し元には影響を与えない。パラメータが値型 (構造体) の場合,メソッド側が変更した構造体の値を呼出し元が利用する手段はない。パラメータが参照型 (クラス) の場合,パラメータはオブジェクトの実体ではなく,オブジェクト実体への参照であるため,メソッドが変更したオブジェクトの実体を,呼出し元から利用することは可能である。しかしながら,別のオブジェクトへの参照をパラメータに代入するなどのパラメータ自体の変更は,値型のときと同様に呼出し元には影響を与えない。

パラメータに ref キーワードを適用することで,パラメータ値をコピーせずに直接渡し,メソッドの実行が終了する際に,呼出し元に戻すように指示することができる。つまり,メソッド側で変更した後のパラメータを,呼出し元が利用できることになる。ref キーワードはメソッドの宣言時と,そのメソッドを呼出すときに使用する。

class SwapSample
{
    static void SwapInts(ref int i, ref int j)
    {
        int t = i;
        i = j;
        j = t;
    }

    public static void Main()
    {
        int a = 123;
        int b = 456;
        System.Console.WriteLine("SwapInts 実行前: {0} {1}", a, b);
        SwapInts(ref a, ref b);                                     // ref を使用して呼出す
        System.Console.WriteLine("SwapInts 実行後: {0} {1}", a, b);
    }
}

out キーワードは,パラメータがデータを呼出し元へ返すためのパラメータであることを示す。呼出し元はパラメータを指定し,メソッドがそのパラメータに新しい値を代入することになる。out パラメータは 1 つ以上の値を返す必要があるメソッドに使用する。ref パラメータとは異なり,out パラメータの初期値をメソッド側で使用することはできない。out キーワードはメソッドの宣言時,そのメソッドの呼出し時に使用する。

class DivideSample
{
    static int DivMod(int dividend, int divisor, out int modulo)
    {
        modulo = dividend % divisor;
        return dividend / divisor;
    }

    public static void Main()
    {
        int quo;
        int mod;
        quo = DivMod(7, 3, out mod);                                // out を使用して呼出す
        System.Console.WriteLine("7 ÷ 3 = {0} ... {1}", quo, mod);
    }
}

param キーワードはパラメータ配列を作成する際に用いる。パラメータ配列は,メソッドの最後の 1 次元配列型のパラメータでなければならない。呼出し元は,パラメータ配列を 2 通りの方法で利用することができる。すなわち,最後のパラメータとして配列型を渡すこともできるし,配列の要素型として扱える一連のパラメータを渡すこともできる。パラメータを渡さないことも可能である。

class Summary
{
    static int Sum(params int[] list)
    {
        int sum = 0;
        for (int k = 0; k < list.Length; ++k)
            sum += list[k];
        return sum;
    }

    public static void Main()
    {
        int sum = Sum(1, 2, 3);                           // 3 個の引数
        System.Console.WriteLine("1 + 2 + 3 = {0}", sum);
        int[] data = {4, 3, 2};
        sum = Sum(data);                                  // 1 個の配列
        System.Console.WriteLine("4 + 3 + 2 = {0}", sum);
        sum = Sum();                                      // 引数なし
        System.Console.WriteLine("          = {0}", sum);
    }
}

メソッドの修飾子

アクセスレベルは,public,private,protected,internal,protected internal のいずれかである。

アクセスレベル修飾子 通用範囲
public 制限なし
private メソッドを宣言したクラス内のみ
protected メソッドを宣言したクラスと,その派生クラス
internal 自アセンブリ内のみ。通常は,同一 EXE (DLL) 内
protected internal protected の範囲 + intenral の範囲

以下にその他の修飾子を挙げる。

修飾子 意味
static インスタンスについてではなく,インスタンスを指定せずに呼出せるメソッドとする。
virtual 派生クラスで実装をオーバライドできるメソッドとする。
override 基底クラスのメソッドをオーバライドする。
sealed virtual なメソッドを,派生クラスでオーバライドできないようにする。
abstract そのクラスではメソッドの実装を行わず,派生クラスで実装する必要があることを示す。
extern アンマネージドなコードで実装されているメソッドを宣言する。
unsafe メソッド内で,ポインタ演算などの安全でないコードをサポートすることを示す。

コンストラクタ

コンストラクタは,クラスまたは構造体名を名前とする特殊なメソッドである。

インスタンスコンストラクタは,new キーワードを用いてインスタンスを生成する際,オブジェクトの実体を生成した直後に呼出される。インスタンスコンストラクタの主な役割は,インスタンスの初期化である。引数を持たないインスタンスコンストラクタをデフォルトコンストラクタという。

また,静的 (static) コンストラクタは,プログラムの実行時,クラスが最初にアクセスされる前に呼出される特殊なメソッドである。静的コンストラクタの呼出しを制御する方法は用意されていない。

デストラクタ

C# はガベージコレクションをサポートしている。ガベージコレクションとは,生成したオブジェクトが必要なくなったときに特に意識してオブジェクトを解放しなくても,自動的に不要なオブジェクトを検出して解放する仕組みのことである。しかし,リソースの獲得と解放について気をつけなければならないケースも存在する。デストラクタはそのような場合に役に立つことがある。

  • デストラクタは構造体にはない。デストラクタが利用できるのはクラスに対してのみである。
  • 1 つのクラスは 1 つまでしかデストラクタを持てない。
  • デストラクタは継承したりオーバロードしたりできない。
  • デストラクタは自動的に呼出される。明示的に呼出すことはできない。
  • デストラクタは修飾子やパラメータを持てない。

プロパティ

プロパティは,クラスや構造体の保持する情報にアクセスする際に利用する。フィールドに直接アクセスするよりも柔軟で安全である。

class Rectangle
{
    private double width;

    public double Width
    {
        get
        {
            return width;
        }
        set
        {
            if (value < 0)
                throw new System.ArgumentException("負の幅を設定しようとした");
            width = value;
        }
    }

    public double Height;

    public double Area
    {
        get
        {
            return width * Height;
        }
    }
}

class MyMain
{
    Rectangle r = new Rectangel();
    r.Width = 6;                         // Width プロパティにアクセス
    r.Height = 7;                        // Height はフィールドを直接アクセス
    System.Console.WriteLine("Area = {0}", r.Area);

    try
    {
        r.Width = -3;                    // Width プロパティに不正な値を入れてみる
    }
    catch (System.ArgumentException)
    {
    }
}

インデクサ

インデクサは,クラスや構造体を配列のようにインデックスで参照するための機能である。インデクサはプロパティと同様の機能であるが,パラメータを持つところが異なっている。

class IntCollection
{
    private int[] myArray = new int[100];

    public int this[int i]
    {
        get
        {
            return myArray[i];
        }
        set
        {
            myArray[i] = value;      // セットする値は「value」で参照できる
        }
    }
}

class MyMain
{
    public static void Main()
    {
        IntCollection c = new IntCollection();
        c[0] = 1;
        c[1] = 2;
        System.Console.WriteLine("{0} {1}", c[0], c[1]);
    }
}

  • オブジェクトを配列のようにインデックスしてアクセスするために用いる。
  • get インデクサは値を返す。set インデクサは値を代入する。
  • インデクサのコードは this キーワードを使用したブロック内に記述する。
  • set インデクサで設定する値は value キーワードで参照する。
  • インデクサは必ずしも整数でインデックスする必要はない。
  • インデクサはオーバロードできる。
  • インデクサは多次元にもできる。たとえば 2 次元配列のようにアクセスできるインデクサも可能である。

class DayCollection
{
    string[] days = {"Sunday", "Monday", "Tuesday", "Wednesday",
                     "Thursday", "Friday", "Saturday"};

    // 曜日の名前を探し,0~6 または -1 を返す。
    private int getday(string testday)
    {
        // 馬鹿サーチ
        int i = 0;
        foreach (string day in days)
        {
            if (day == testday) return i;
            i++;
        }
        return -1;
    }

    // 日曜日~土曜日を 0~6 にして返すインデクサ (読出しのみ)
    public int this[string day]
    {
        get
        {
            return (getday(day));
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        DayCollection week = new DayCollection();
        Console.WriteLine(week["Friday"]);
        Console.WriteLine(week["Made-up Day"]);
    }
}

配列

配列は同じ型の変数の並びを表現するデータ構造である。配列には 1 次元配列,多次元配列,配列の配列 (jagged 配列) がある。

// 1 次元配列の生成と使用
int[] myIntArray1 = new int[3];
myIntArray1[0] = 1;
myIntArray1[1] = 2;
myIntArray1[2] = 3;

// 1 次元配列を生成し,同時に初期化
int[] myIntArray2 = new int[3]{ 1, 2, 3 };
// 上のコードと全く同じ省略形
int[] myIntArray3 = { 1, 2, 3 };

// 2 次元配列の生成と使用
int[,] myMultidimensionalArray1 = new int[2, 3];
// 同時に初期化
int[,] myMultidimensionalArray2 = { { 1, 2, 3 }, { 4, 5, 6 } };

// jagged 配列
int[][] myJaggedArray = new int[3][];
// jagged 配列の最初の要素 (これは 1 次元配列) を生成
myJaggedArray[0] = new int[4] { 1, 2, 3, 4 };

  • 配列には 1 次元,多次元,jagged の種類がある。
  • 配列の要素は配列を生成した直後は数値型であればすべて 0,参照型であれば null となる。
  • jagged 配列は,配列の配列である。配列は参照型であるため,jagged 配列の生成直後はすべての要素が null である。
  • 配列のインデックスは常に 0 から始まる。したがって要素数が n であれば,インデックスは 0 ~ n-1 の範囲となる。
  • 配列要素の型は,値型でも参照型でも良い。配列型でも良い。
  • 配列は System.Array から派生する参照型である。

継承

クラスは別のクラスを継承して定義することができる。継承はクラス宣言のクラス名の後ろに「:」に続けて継承したいクラス (基底クラス) 名を記述することで表現する。

public class MyBaseClass
{
    int a;

    public MyBaseClass() {}
    public void DoSomething() { ... }
}

public class MyDerivedClass: MyBaseClass
{
    int b;

    public MyDerivedClass() {}
    public void DoAnother() { ... }
}

上例では MyBaseClass から派生する MyDerivedClass を定義している。派生して定義したクラスを元のクラスの派生クラス,元のクラスを派生クラスの基底クラスという。

派生クラスは,通常基底クラスのすべての性質 (データや動作) を受継ぎ,さらに基底クラスにはない独自の性質を持つことができる。派生クラスは基底クラスとしての性質も兼ね備えているため,基底クラスとして振舞うことができる。このことから,派生クラスのオブジェクトは,基底クラス型として参照することができる。逆に,派生クラスのオブジェクトを参照している基底クラス型の参照は,キャストによって元の派生クラス型の参照に変換することができる。キャストされた基底クラス型の参照が実際に派生クラスのオブジェクトを参照していなかった場合は,キャストは失敗し,System.InvalidCastException が発生する。

MyBaseClass x = new MyDerivedClass();
x.DoSomething();

MyDerivedClass y = (MyDerivedClass)x;

x = new MyBaseClass();
y = (MyDerivedClass)x;                  // InvalidCastExceptioin 発生!

基底クラス型の変数に代入して型変換したからといって,派生クラスの実体が変更されるわけではない。一旦派生クラスのインスタンスとして生成された実体は,不要になって破棄されるまで常に派生クラスのインスタンスとしてのアイデンティティを保ち続ける。

ポリモーフィズム

ポリモーフィズムとは,日本語では多態性または多形性とよばれる概念である。広い意味では,一見同一のコードが,さまざまな意味を持つことを言う。たとえば「x = a + b;」というコードは,a と b が数値型なら「加算」を意味しているが,a と b が文字列型であれば「連結」を意味すというのも,広い意味ではポリモーフィズムである。

一番狭い意味では,オブジェクトの共通のインターフェースを通して処理する手続きが,実行時に確定する実際のオブジェクトの性質によって,多様な動作を行うことができることを言う。

 1: // 抽象図形クラス
 2: class abstract Shape
 3: {
 4:     double positionX;  // 位置 X
 5:     double positionY;  // 位置 Y
 6: 
 7:     // この図形の位置を変更する (移動する)
 8:     void MoveTo(double x, double y)
 9:     {
10:         positionX = x;
11:         positionY = y;
12:     }
13: 
14:     // この図形の面積を計算して返す
15:     abstract double CalcArea();
16: }
17: 
18: // 正方形クラス
19: class Square : Shape
20: {
21:     double side;     // 辺の長さ
22: 
23:     Square( ... ) { ... }
24: 
25:     override double CalcArea()
26:     {
27:         return side * side;
28:     }
29: }
30: 
31: // 長方形クラス
32: class Rectangle : Shape
33: {
34:     double sideA;     // 一辺の長さ
35:     double sideB;     // 他の辺の長さ
36: 
37:     Rectangle( ... ) { ... }
38: 
39:     override double CalcArea()
40:     {
41:         return sideA * sideB;
42:     }
43: }
44: 
45: // 複数の図形集合をひとつの図形として扱う図形
46: class MultiShape : Shape
47: {
48:     Shape[] shapes;
49: 
50:     MultiShape( ... ) { ... }
51: 
52:     override double CalcArea()
53:     {
54:         // すべての図形の総面積を求める
55:         double totalArea = 0;
56:         foreach (Shape shape in shapes)
57:             // この単純なコードは,実はさまざまなメソッドを呼び分ける
58:             totalArea += shape.CalcArea();
59:         return totalArea;
60:     }
61: }
62: 
63: class Canvas
64: {
65:     Shape[] shapes;
66: 
67:     void SomeMethod()
68:     {
69:         ...
70:         foreach (Shape shape in shapes)
71:         {
72:             ...
73:             // このコードは常に Shape クラスの MoveTo メソッドを呼出す
74:             shapes.MoveTo(x, y);
75:             ...
76:         }
77:     }
78: }

インターフェース

インターフェースは,オブジェクトの使われ方のみを定義し,オブジェクトが具体的に持つデータや,動作については関知しない。データや動作はインターフェースを実装するクラスや構造体に完全に任される。

C# は単一継承だけをサポートしているが,実装するインターフェースについては制限がないため,複数の基底クラスから継承する代わりにインターフェースを利用することもある。

デリゲート

C# のデリゲートは,C や C++ の関数ポインタと同じようなものである。デリゲートを使用すると,メソッドへの参照を抽象化でき,実際に呼出されるメソッドを動的に選択することができる。

 1: delegate void MyDelegate(string s);
 2:
 3: class YourClass
 4: {
 5:     public string x;
 6:     public void YourMethod(string s)
 7:     {
 8:         System.Console.WriteLine("YourClass {0}: {1}", x, s);
 9:     }
10: }
11:
12: class MyClass
13: {
14:     static void MyMethod(string s)
15:     {
16:         System.Console.WriteLine("MyClass: {0}", s);
17:     }
18: 
19:     public static void Main()
20:     {
21:         YourClass a = new YourClass();
22:         a.x = "A";
23:         MyDelegate d = new MyDelegate(a.YourMethod);
24:         d("Hello");                             // YourClass A: Hello
25:         d = new MyDelegate(MyMethod);
26:         d("Yahoo");                             // MyClass: Yahoo
27:     }
28: }

上例で,YourClass の YourMethod を呼出す際は YourClass のインスタンスが必要である。デリゲートは,メソッドのコードの位置情報だけでなく,インスタンスの情報も格納されていることが分かる。

デリゲートはイベントと組合わせて利用することが多い。

 

イベント

C# のイベントは,コンポーネントが検出した特定の事象を,クライアントに通知するための機能である。たとえば GUI のボタンコンポーネントは,ボタンが押されたときに何をすれば良いのか分からないため,ボタンが押されたことをクライアントに通知し,処理を委譲する必要がある。その場合,C# ではボタンが押されたことを通知するためのデリゲート型を宣言し,クライアントはイベントにそのデリゲートのインスタンスを登録するという方法を取ることを想定している。

delegate void ButtonClickedEventHandler(object sender, EventArgs e);

class Button
{
    event ButtonClickedHandler Clicked;

    void OnClick(EventArgs e)
    {
        if (Clicked != null)
            Clicked(this, e);
    }

    ... いろいろ ....
}

class Client // 画面アプリケーション
{
    Button cancelButton;

    Client()
    {
        cancelButton = new Button();
        // ボタンのイベントに処理メソッドを接続
        cancelButton.Clicked += new ButtonClickedEventHandler(OnCancel);
    }

    void OnCancel(object sender, EventArgs e)
    {
        ... キャンセル処理を実行する ...
    }
}

安全でないコードとポインタ

C# では,unsafe コンテキストを使用して,ポインタを扱うことができる。

  • OS の API や既存の COM などを利用する場合に必要となることがある。
  • C# コンパイラの unsafe を許可するオプションスイッチを指定しないと,unsafe は使用できない。
  • unsafe を利用して作成したアセンブリは「安全でない」と見なされ,インターネットから直接実行することはできなくなる。

nullable 型 (C# 2.0)

ある型に基づく nullable 型の変数は, 元の型のすべての値に加えて「空値」を表現することができる。この機能は特にデータベースを扱う際など, 保持するべき値がないもしくは存在しないことがあるような状況で有用である。

// nullable 型の例
int? x;
...
if (x != null)
    Console.WriteLine(x.Value);
else
    Console.WriteLine("未定義");

  • 未定義状態を持つ変数を生成する際に用いることができる。
  • 「T?」は「System.Nullable<T>」の省略形である。(Tは何らかのデータ型)
  • HasValue プロパティは,変数が値を保持しているとき true,null であるとき false を返す。
  • Value プロパティは,変数が値を保持しているときその値を返す。変数が値を保持していなければ,System.InvalidOperationException を投げる。
  • nullable 型の変数のデフォルト状態は HasValue が false を返す状態である。

nullable 型の使用方法

nullable 型は,元の型のすべての値に加えて「空値」を表現することができる。その宣言には 2 通りの書き方がある。「T」が元の型とすると,

System.Nullable<T> 変数名

または

T? 変数名

のどちらの表現も同じ意味である。

nullable 型の例

値型なら何でも,それを元の型とした nullable 型を作成できる。

int? i = 10;
double? x = 3.14;
bool? flag = null;
char? letter = 'a';
int?[] myArray = new int?[10];

値型でない型は nullable 型にできない。

string? message = "Hello, World!";    // コンパイルエラー。string は参照型
MyClass? myInstance = new MyClass();  // コンパイルエラー。クラスは参照型

as 演算子は参照型にしか使用できないため,nullable 型と共に利用することはできない。

int a = 5;
object b = a;
int? c = b as int;                    // コンパイルエラー。int は値型

nullable 型のメンバ

HasValue
  bool 型の読出し専用プロパティ。null でない値を保持しているとき true。
Value
  元の型の読出し専用プロパティ。null でない値を保持しているときその値,保持していないときは System.InvalidOperationException を投げる。

型変換

int? x = null;
int y = x;         // コンパイルエラー

int y = (int)x;    // 元の型にキャストできる。しかし x == null の場合は例外が発生する
int y = x.Value;   // OK。しかし x == null の場合は例外が発生する

x = null;          // OK。null はいつでもセットできる。
x = 10;            // OK。元の型の値は,nullable 型に暗黙のうちに変換できる

演算

元の型に対する演算は,nullable 型に対しても適用できる。ただし null 値に対する演算は結果も null となる。(bool 型については特別)

int? x = 10;
x++;                             // x は 11 となる
x = x * 10;                      // x は 110 となる
int? y = null;
x = x + y;                       // x は null となる

nullable 型の比較については注意が必要である。nullable 型が null であるときの大小比較は,常に false となる。つまり,nullable 型の大小比較が false であった場合に,必ずしも逆の大小関係が成り立っているとは限らない。

int? x = null;
if (x >= 3)
    Console.WriteLine("x は 3 以上です。");
else
    Console.WriteLine("x は 3 未満です。");     // ウソ

null 値テスト

これまでの例に何度も見てきたとおり,HasValue プロパティを調べる方法と,null と等値比較する方法が使える。

int? x = 10;
if (x.HasValue) Console.WriteLine("x は値を保持しています。");

int? y = null;
if (y != null) Console.WriteLine("y は値を保持しています。");

?? 演算子

nullable 型の既定値を与えたいとき ?? 演算子が利用できる。

int? x = null;
int i = x ?? -1;    // i に x の値を設定する。ただし,x が null なら,-1 を設定する

int? y = 10;
int? z = x ?? y;    // z に x の値を設定する。ただし,x が null なら,y を設定する

bool?

bool? 型に関する & および | 演算は,やや特殊な規則となっている。

x y x&y x|y
true true true true
true false false true
true null null true
false true false true
false false false false
false null false null
null true null true
null false false null
null null null null

& 演算子の場合は false→null→true の順で「強」く,| 演算子の場合は true→null→false の順で「強」い,と考えると容易に理解できる。

今回取り上げなかった重要な話題

  • 文と式と演算子
  • 新デリゲート (C# 2.0)
  • イテレータ (C# 2.0)
  • ジェネリクス (C# 2.0)
  • リフレクション
  • 属性
  • スレッド
  • インタオペラビリティ
  • XML コードコメント
  • アプリケーションドメイン
  • セキュリティ
  • パフォーマンス
  • クラスライブラリ
  • Visual Studio (Windows Forms,Web サービス,Web アプリケーション)



コメント追加 トラックバック送信