C#クイズの答え

id:siokoshou:20070901#2 のクイズの答え。問題をまだ見てなければ、先に問題を見てからどうぞ。
実行するとこうなります。単純なコードに見えるけどクイズにするくらいなので、意外なほうが呼ばれます。

Derived.Method(string)
Derived.Method(object)
Hoge.Method(string)

1つめと3つめはフツー(比較のためにやってみただけ)、2つめが 工工エエエエ(´Д`)エエエエ工工
なんでこうなるんだか調べてるうちにもっと簡単な例を見つけたので、なんで?って話の前にそちらを。

using System;

public class A
{
    public void F( short s )
    {
        Console.WriteLine( "short" );
    }
}

public class B : A
{
    public void F( int i )
    {
        Console.WriteLine( "int" );
    }
}

public class Program
{
    public static void Main()
    {
        short val = 15;

        B b = new B();
        b.F( val );

        Console.ReadKey();
    }
}

これもエエーってほうの int が表示されます。きっとこんな言語はほかにないかも?
どのメソッドを呼ぶか決めるルールは、まずはそのクラスのメソッドから当てはまるものを探して、そこで見つかればそれでおしまい。見つからなかったときに初めて次のベースクラスを探す、って決まりなんだそうな。
short から int への暗黙の変換があるので、この例では short を引数にして呼んだのに、B.F( int ) が呼ばれてしまいます。
この例は ここ で見つけました。解説はこっち。死にそうなくらいお暇なら JISの仕様書 14.5.5.1 の備考とか、そのあたりもどうぞ。

で。仕様はわかったけど、なんで?ってところですよね。これも説明があって、バージョン管理のためにこうなっているそうな。バージョン管理は C# の特徴的な機能で、Java を改良した点の一つですね。C# はまだ若いので今はまだこの機能はたいして意識されないけど、そのうちじわじわ利いてきます、たぶん。

A クラスと B クラスを別の人が開発してたとして、A の最初のバージョンには A.F( short ) がなかったとします。このとき、誰かが B.F( int ) を使ってたとします。short で呼ぶなよってのはおいといて。で、後のバージョンで A.F( short ) が追加されたとします。でも、C# のルールだと B.F( int ) を使ってた人はやっぱり B.F( int ) をそのまま呼び続けることができます。めでたし!ってわけです。
理由を聞くとよく考えてあるなぁって思いますねぇ。

で、こないだのクイズに戻ってみると、この例とは override の部分が違います。C# の仕様書によれば override したメソッドは探し出す候補から外されます!最初はなんで?って思ったけど、考えてみれば一緒ですね。後からベースクラスに Base.Method( string ) が追加されて、ベースクラスに追加されたからこそ派生クラスでオーバーライドできるわけなので。
つまり、

1.最初は Base はメソッドなし。派生には Derived.Method( object ) があって、誰かがそれを利用してる。
2.Base.Method( string ) が追加された。でも Derived.Method( object ) を呼んでる人には影響なし。
3.Derived.Method( string ) としてオーバーライド追加。でもやっぱり Derived.Method( object ) を呼んでる人には影響なし。

って流れ。

どう思いますか、この仕組み?バージョン管理は必要かもしれないけど、それにしても見事な落とし穴って感じ。

クイズのネタ元はこちらでした。via http://blogs.msdn.com/nealho/archive/2006/01/19/515173.aspx