< March 2008 | July 2008 | August 2008 >

July 18, 2008

アセンブリ参照

たとえば System.Net.Sockets を使用するプログラムを記述した場合、「型または名前空間名 'Net' は名前空間 'System' に存在しません。アセンブリ参照が不足しています。」「型または名前空間名 'Socket' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足しています。」などと表示されコンパイルできない。この場合、まず一番下位の識別子について、MSDN 等で調べてみる。

.NET Framework クラス ライブラリ
Socket クラス

Berkeley ソケット インターフェイスを実装します。

名前空間 : System.Net.Sockets
アセンブリ : System (System.dll 内)
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.socket.aspx

「アセンブリ」の記述を確認したら、Visual C# の開発環境より [プロジェクト] → [参照の追加] を選択、[.NET] タブ内の「System」を選択して [OK] をクリックすると、ソリューションエクスプローラーの「参照設定」ノードの下位に「System」が現れる。再度コンパイルしてやはりエラーになるようなら、解決できない識別子についてさらにアセンブリ参照を追加する。

コンパイル時のエラーメッセージにはいくつかバリエーションがある。

コンパイラ エラー CS0234
エラーメッセージ:型または名前空間名 '名前' は名前空間 '名前空間' に存在しません。アセンブリ参照が不足しています。
コンパイラ エラー CS0246
エラーメッセージ:型または名前空間名 'type/namespace' が見つかりませんでした。ディレクティブを使うかアセンブリ参照を使ってください。

デリゲートの非同期呼び出し

デリゲートを定義すると、自動的に BeginInvokeEndInvoke というメソッドが定義される。
System.IAsyncResult Delegate::BeginInvoke (orgparams, System.AsyncCallback callback, object @object);
orgtype Delegate::EndInvake (System.IAsyncResult result);
これらを呼び出すことによって、非同期の呼び出しを実現できる。
class MessageBox
{
    // デリゲートを宣言
    delegate System.Windows.Forms.DialogResult MessageBoxDelegate (string text, string caption, System.Windows.Forms.MessageBoxButtons buttons, System.Windows.Forms.MessageBoxIcon icon);

    // メッセージボックスを表示するメソッド
    System.Windows.Forms.DialogResult Show (string text, string caption, System.Windows.Forms.MessageBoxButtons buttons, System.Windows.Forms.MessageBoxIcon icon)
    {
        return System.Windows.Forms.MessageBox.Show (text, caption, buttons, icon);
    }

    static void Main ()
    {
        MessageBox msgBox = new MessageBox ();

        // デリゲート作成
        MessageBoxDelegate msgBoxDelegate = new MessageBoxDelegate (msgBox.Show);

        // 非同期に呼び出す
        System.IAsyncResult asyncResult = msgBoxDelegate.BeginInvoke ("ボタンが押されるのを待機します。", "待機中", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information, null, null);

        // 終了まち
        while (!asyncResult.IsCompleted)
        {
            System.Threading.Thread.Sleep (1000);
            System.Console.WriteLine ("マダー?");
        }
        // 戻り値を取得
        System.Windows.Forms.DialogResult result = msgBoxDelegate.EndInvoke (asyncResult);
    }
}

as

互換性のない型にキャストしようとすると実行時に例外が発生します。このとき、as 演算子を使用すると例外を発生させる代わりに null を返すようにすることができます。as 演算子は、変換先のデータ型が null を許容する型(つまり参照型)に限って使用できます。
// 動物クラス
class Animal
{
    protected string sName;
    public Animal () { sName = "unnamed"; }
    public string Name
    {
        get { return sName; }
        set { sName = value; }
    }
}

// 犬クラス
class Dog : Animal { }

// 猫クラス
class Cat : Animal
{
    static void Main ()
    {
        object obj = new Cat ();

        Animal animal = obj as Animal;  // obj は動物かな?
        if (animal == null)
            System.Console.WriteLine ("obj isn't animal.");
        else
        {
            System.Console.WriteLine (animal.Name);
        }

        Cat cat = obj as Cat;           // obj は猫かな?
        if (cat == null)
            System.Console.WriteLine ("obj isn't cat.");
        else
        {
            cat.Name = "Kajino";        // 名前をつけよう!
            System.Console.WriteLine (cat.Name);
            if (animal != null)
                System.Console.WriteLine (animal.Name); // animal も同じ実体を参照している
        }
        
        Dog dog = obj as Dog;           // obj は犬かな?
        if (dog == null)
            System.Console.WriteLine ("obj isn't dog.");
        else
        {
            System.Console.WriteLine (dog.Name);
        }
    }
}
上のコードの実行結果です。
unnamed
Kajino
Kajino
obj isn't dog.

obj は object 型の変数ですが、Cat クラスのオブジェクトへの参照を格納しています。Animal 型、Cat 型へのダウンキャストは成功しますが、Dog 型とは互換性がないため null が返されています。

ボクシング

C# では、値型は直接スタック上に格納されるが、参照型は参照のみスタック、実体はヒープ上に確保される。ところが C# ではすべてのデータ型が object 型(System.Object)を継承しているため、値型の値も object 型に代入が可能だったりする。
object 型は、.NET Framework の Object のエイリアスです。C# の統一型システムでは、定義済みの型やユーザー定義の型、参照型や値型など、すべての型が、Object から直接的または間接的に継承されます。任意の型の値を object 型の変数に代入できます。値型の変数をオブジェクトに変換することを "ボックス化" と言います。型オブジェクトの変数を値型に変換することを "ボックス化解除" と言います。詳細については、「ボックス化とボックス化解除」を参照してください。
http://msdn.microsoft.com/ja-jp/library/9kkx3h3c(VS.80).aspx
ボックス化はボクシングとも呼ばれる。こちらのほうが好みだな。パンチ!パンチ!
class Test
{
    static void Main ()
    {
        int num = 2000;                 // int は値型
        object box = num;               // ボクシング変換
        num += 2500;                    // 元の値型を変更する
        System.Console.WriteLine (num); // 元の値型は更新されている
        System.Console.WriteLine (box); // こちらはヒープ上にコピーが作られているため更新されない
        num = (int) box;                // アンボクシング変換
        System.Console.WriteLine (num); // ちゃんとキャストされたことがわかる
    }
}
上記のコードを実行すると出力結果は下のようになる。
4500
2000
2000

ボクシング変換を行うとヒープ上に object 型の実体が生成され、2000という値もヒープにコピーされる。元の変数を更新しても、ヒープ上の値は更新されない。

July 17, 2008

インデクサ

プロパティと似た機能でインデクサというものがある。クラスを配列として扱うことができる。
class Rainbow
{
    private string[] sColors;
    public Rainbow () { sColors = new string [] { "赤", "橙", "黄", "緑", "青", "藍", "紫" }; }
    public string this [int index]  // インデクサ宣言子
    {
        get { return sColors [index]; }             // 取得用のアクセサ
        set { sColors [index] = value; }            // 設定用のアクセサ
    }
    public int Length
    {
        get { return sColors.Length; }
    }
    static void Main ()
    {
        Rainbow rainbow = new Rainbow ();
        rainbow [1] = "orange";                     // set が呼び出される
        for (int i = 0; i < rainbow.Length; i ++)
            System.Console.WriteLine (rainbow [i]); // get が呼び出される
    }
}
こちらも set アクセサには暗黙の引数として value が渡される。代入された右辺値である。

プロパティ

C# は言語レベルでプロパティの概念をサポートしている。下記のようにアクセサを定義する。
class Cat
{
    private string sName;
    public string Name  // プロパティ
    {
        get // 取得用のアクセサ
        {
            return sName;
        }
        set // 設定用のアクセサ
        {
            sName = value;
        }
    }
    
    static void Main ()
    {
        Cat cat = new Cat ();
        cat.Name = "タマ";                      // set が呼び出される
        System.Console.WriteLine (cat.Name);    // get が呼び出される
    }
}
set アクセサには、暗黙の引数として value が渡される。代入された右辺値である。 get または set のいずれかを省略することも可能。その場合、書き込み専用または読み取り専用となる。

抽象クラス

クラス自体を abstract 宣言した場合は、実体を作れないだけ
abstract class Base
{
    public void Hello () {}
}

class Inherit : Base
{
    new public void Hello () {}
    
    static void Main ()
    {
        Inherit iObj = new Inherit ();
//        Base bObj = new Base ();    この行はコンパイルできない
        Base bObj = iObj;
        iObj.Hello ();  // Inherit のメソッドが実行される
        bObj.Hello ();  // Base のメソッドが実行される
    }
}
メソッドを abstract 宣言した場合は内容を定義できない。派生側でオーバーライドする必要がある。
abstract class Base
{
    public abstract void Hello (); // 実体はない
}

class Inherit : Base
{
    public override void Hello () {}
    
    static void Main ()
    {
        Inherit iObj = new Inherit ();
//        Base bObj = new Base ();    この行はコンパイルできない
        Base bObj = iObj;
        iObj.Hello ();  // Inherit のメソッドが実行される
        bObj.Hello ();  // Inherit のメソッドが実行される
    }
}

継承を禁止する

sealed キーワードで、これ以上継承することを禁止することができる
sealedclass Base {}

// class Inherit : Base {}    コンパイルエラーとなる

同じように継承を禁止したメソッドも定義できる
class Base
{
    public virtual void Hello () {}
}

class Inherit : Base
{
    public sealed override void Hello () {}
}

class Inherit2 : Inherit
{
//    public override void Hello () {}    コンパイルエラーとなる
}

オーバーライド

派生先で override キーワードをつけて宣言する
class Base
{
    public virtual void Hello () {}
}

class Inherit : Base
{
    public override void Hello () {}
    
    static void Main ()
    {
        Inherit iObj = new Inherit ();
        Base bObj = iObj;
        iObj.Hello ();  // Inherit のメソッドが実行される
        bObj.Hello ();  // Inherit のメソッドが実行される
    }
}

クラスメンバの隠蔽

  • 基底クラスのメンバを派生先で隠蔽するとコンパイル時に警告になる
  • 意図した隠蔽であることを明示するために new キーワードをつけて宣言する
class Base
{
    public void Hello () {}
}

class Inherit : Base
{
    new public void Hello () {} // new はなくても効果は一緒
    
    static void Main ()
    {
        Inherit iObj = new Inherit ();
        Base bObj = iObj;
        iObj.Hello ();  // Inheritのメソッドが実行される
        bObj.Hello ();  // Baseのメソッドが実行される
    }
}

C++との相違点3:クラスのデストラクタ

  • デストラクタは public にできない。コンパイルエラーになる。
    修飾子 'public' がこの項目に対して有効ではありません。
  • デストラクタが実行されるタイミングが予測できない。
    →要調査

C++との相違点2:構文上の相違

  • switch文では case ラベル内に break, goto, return のいずれかを記述する必要がある。
  • クラスの定義の最後にセミコロンが不要
  • (編集中)

C++との相違点1:データ型の相違

参照型と値型の型がある
  • 参照型は Visual Basic の Object みたいなイメージだが、ガベッジの管理は参照カウントでは行っていないようだ→要調査
  • 値型は従来どおり
  • クラスは参照型、構造体は参照型
デリゲート
  • 関数ポインタの代用となりうるものに delegate がある。参照型。
delegate void AnyMethod ();
class Test
{
    static void Main ()
    {
        Test test = new Test ();
        AnyMethod method = new AnyMethod (test.Hello);
        method += new AnyMethod (test.Bye);
        method ();
    }
    void Hello ()
    {
        System.Console.WriteLine ("Hello!");
    }
    void Bye ()
    {
        System.Console.WriteLine ("Bye!");
    }
}

エントリポイント

Hello World はこんな感じ
// Hello.cs
class Hello
{
    static void Main ()
    {
        System.Console.WriteLine ("Hello World!");
    }
}
Main
  • メソッド名先頭が大文字。
  • static なメソッド。
  • どこかのクラスにひとつだけ定義する。

July 8, 2008

nmake 用の Makefile

汎用 Makefile の覚書。Visual C++ 2005 Express Edition では動作確認済み。
TARGET = hello
OBJS = hello.obj hello2.obj

CC = cl.exe
CFLAGS = /EHsc
LD = link.exe
LDFLAGS = /OUT:$(TARGET).exe

.c.obj :
	$(CC) $(CFLAGS) /c $<

.cpp.obj :
	$(CC) $(CFLAGS) /c $<

all: $(OBJS)
	$(LD) $(OBJS) $(LDFLAGS)

clean:
	del *.obj
	del *.exe