torutkのブログ

ソフトウェア・エンジニアのブログ

C#言語仕様の驚異(new)

無謀にも、C#歴2週間でいきなりEffective C#(2nd Edition)から読みはじめています。この本のItem 23、"Understand How Interface Methods Difer from Virtual Methods"を読んで、メソッドの修飾子に「new」を使うという凄い例を初めて見たのでびっくりしました。

以下Effective C#からのコードの抜粋です。

interface IMsg
{
    void Message();
}

public class MyClass : IMsg
{
    public void Message()
    {
        Console.WriteLine("MyClass");
    }
}

public class MyDerivedClass : MyClass
{
    public new void Message()
    {
        Console.WriteLine("MyDerivedClass");
    }
}

C#はメソッドが仮想(virtual)でないというのが言語仕様のポリシーなので、基底クラスでvirtual修飾子を付けないメソッドを派生クラスで再定義すると、基底クラスのメソッドが隠蔽されます(virtualとoverrideとは別)。しかし、このとき派生クラスのメソッド定義で修飾子としてnewを記述していないと、コンパイラが警告を発します(エラーではないので、気づかなかった)。

なんというか、C++の複雑な仕様をC#ではそのまま引き継いでしまったような感触を受けます。
さらにC#では、newというインスタンス生成のための予約語を、ぜんぜん違う意味に転用してしまっています*1C#の言語仕様センスっていったいなんだろうか、ちょっと驚愕です。

ちなみに上記コードは、

MyDerivedClass d = new MyDerivedClass();
d.Message();

と実行するときは、"MyDerivedClass"と表示されますが、続いて

IMsg m = d as IMsg;
m.Message();

と基底クラスが実装するインタフェース型にas演算子で変換した場合は、"MyClass"とIMsgを実装した基底クラスのMessage()が実行されます。

Effective C#では、MyDerivedClassでインタフェースIMsgを再実装宣言することで、上記のインタフェース型に変換したとき派生クラスのMessage()を実行されるようにできると解説していました。

public class MyDerivedClass : MyClass, IMsg
{ ... }

ただし、基底型の変数に代入してMessage()を呼び出すと、基底型のMessage()が呼び出されます。これを、ポリモーフィズムに振舞わせるには、メソッドをvirtualにする必要があります。

(追記)new修飾子はメンバーどれにでも適用可能

基底クラスのvirtualなメソッドを派生クラスでnew付きでvirtualメソッドを再定義するとか、プロパティもnewで再定義するとか、メンバー変数にもnewで定義するとか、newを使えばなんでもあり、みたいな、あまりに強力かつ困惑する機能です。

*1:他にも、usingという予約語を、名前空間の導入のusing宣言と、IDisposable型オブジェクトに対してスコープから抜けるときににDispose()メソッドを呼ぶtry-finallyのシンタックスシュガー構文に使われている例があります。