none
オーバーロードの解決 C# v4.0 RRS feed

  • 質問

  • メソッドのオーバーロードの解決がうまくいきません。

    下記のコードで正常にコンパイルできることを期待したのですが、Test(null, A);でエラーになります。

    C#言語仕様 version 4.0 75ページを見ると型パラメーター制約をオーバーロードの解決に使っていないと読み取れるのですが、それですとTest(A);もエラーになるはずです。

    なぜこのようになるのでしょう。

    class A { }
    
    class B { }
    
    class TestClass
    {
      void Test(A p) { }
    
      void Test<T>(T p) where T : B { }
    
      void Test(A p1, A p2) { }
    
      void Test<T>(B p1, T p2) where T : B { }
    
      void Caller()
      {
        A A = null;
        Test(A); //エラーにならない
        Test(null, A); //次のメソッドまたはプロパティ間で呼び出しが不適切です
      }
    }



    http://systemartlaboratory.com/

    2012年11月23日 13:26

回答

  • かずき_okazuki さん、佐祐理さん、gekkaさんありがとうございました。

    Test((A)null, A)という逃げ方をすることも考えないといけないですね。というか素直にメソッド名を変えてしまうかですね。

    7.5.3 オーバーロードの解決法 を読べきであることがわかりました。

    読んでみますと、7.5.3.1 適用可能な関数メンバー  で2つ以上適用可能なオーバーロードがあれば初めてどちらが適切かを 7.5.3.2 より適切な関数メンバー で判断すると解釈しました。この場合 Test(null, A) に適用可能であるのは void Test(A p1, A p2) { }だけなので、コンパイルエラーになるのはおかしいと思いました。

    7.5.1.2 引数リストの実行時の評価Test(Console.ReadKey(), Console.ReadKey()) のように引数の評価に伴い副作用を生じる場合に気をつけなければいけない事柄で、オーバーロードの解決とは無関係と判断しました。


    http://systemartlaboratory.com/


    • 編集済み 三輪の牛 2012年11月24日 6:26 体裁を整える
    • 回答としてマーク 三輪の牛 2012年11月29日 3:42
    2012年11月24日 6:25

すべての返信

  • Test((A)null, A)みたいにするとコンパイル通りませんか?nullだと型がわかんないので、キャストで明示的に型を指定してやるってのはよくやります。

    A A = null;の変数Aの型は、明らかなので、オーバーライドの解決がされているのでTest(A)はコンパイルエラーにならないのだと思います。


    かずき Blog:http://d.hatena.ne.jp/okazuki/

    2012年11月23日 14:14
  • P.170の7.5 関数メンバーにオーバーロード解決と型推論について書かれています。が、書かれていることが難解で私にはわかりませんでした。

    経験的には、呼び出されるメソッドの決定において引数の数は考慮されないようです。以前からparamsもありましたし、C# 4.0からはデフォルト引数が用意されるようになったこともあり、そうなることを見越して当初からということでしょうかね。

    メソッドを作る際には、引数の意味も考慮して順序を決めると思いますが、オーバーロード解決・型推論のしやすさも基準に決められているものもあります。
    1つ思いつく例を挙げると、Enumerable.GroupBy()の引数comparerは第2引数keySelectorに使われるため引数の順序としては3番目の方が適切ですが最後に配置されています。

    2012年11月23日 14:40
    1. void Test(A p) { }
    2. void Test<t>(T p) where T : B { }
    3. void Test(A p1, A p2) { }
    4. void Test<t>(B p1, T p2) where T : B { }

    >>Test(A); //エラーにならない

    そもそも(2)はTest(A p)になれないので(1)しかありえない。 (2)に制約がない場合でも

    7.5.3.2 より適切な関数メンバー
    M<sub>P</sub>が非ジェネリック メソッドでM<sub>Q</sub>がジェネリック メソッドの場合、M<sub>P</sub> は<sub>MQ</sub> よりも適切である。

    により(1)が優先される。

    >>Test(null, A); //次のメソッドまたはプロパティ間で呼び出しが不適切です

    引数はより制約の強い型が引数の方が優先されますが、nullだけでは型情報がないので最初の引数の型が決まりません。なので場合(3)では型A,(4)では型Bとなっているように第一引数の型が2種類以上有り得る場合には、どちらとも確定できません。
    Test((A)null,null)のように型を確定してやれば確定可能になります。
    第二引数を使えば制約により判定できる可能性があるかもしれませんが、 </t></t>

    7.5.1.2 引数リストの実行時の評価
    関数メンバー呼び出し (7.5.4 を参照) の実行時の処理中には、引数リストの式または変数参照が、左 から右の順序で、次に示すように評価されます。

    により、第一引数の時点で失敗していると第二引数以降は評価されないようです。
    仕様では明確ではないですが「実行時」とあるので、コンパイル時点で第二引数が明確であってもダメなのでしょう。
    #制約が一つしかないなら、制約ではなく型が確定したオーバーロードをコンパイル時にC++のテンプレートのような形で自動で生成しておくことができれば回避はできるのかもしれませんが。

    引数の評価については7.5.3オーバーロードの解決方法をよく読めば、だいたいのことがわかると思います。
    引数の数についてはより長く引数の型が推論可能なメソッドが優先されると書かれていて、同じ長さでparamsがある場合については静的な型の方が優先されるとも書かれてます。

    #仕様書がなくて手さぐりで挙動をたしかめなくて済む分、仕様書があるって幸せですよね


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2012年11月23日 15:13
    2012年11月23日 15:04
  • 実行時の評価とは func( i+j, k+l ); とあったときに i+j と k+l のどちらを先に計算するかの話ですよ。

    # 昔々のC言語で、Pentium CPU向けに最適化を行うと式が右から評価されるとかそういう話があったような。

    2012年11月23日 23:52
  • かずき_okazuki さん、佐祐理さん、gekkaさんありがとうございました。

    Test((A)null, A)という逃げ方をすることも考えないといけないですね。というか素直にメソッド名を変えてしまうかですね。

    7.5.3 オーバーロードの解決法 を読べきであることがわかりました。

    読んでみますと、7.5.3.1 適用可能な関数メンバー  で2つ以上適用可能なオーバーロードがあれば初めてどちらが適切かを 7.5.3.2 より適切な関数メンバー で判断すると解釈しました。この場合 Test(null, A) に適用可能であるのは void Test(A p1, A p2) { }だけなので、コンパイルエラーになるのはおかしいと思いました。

    7.5.1.2 引数リストの実行時の評価Test(Console.ReadKey(), Console.ReadKey()) のように引数の評価に伴い副作用を生じる場合に気をつけなければいけない事柄で、オーバーロードの解決とは無関係と判断しました。


    http://systemartlaboratory.com/


    • 編集済み 三輪の牛 2012年11月24日 6:26 体裁を整える
    • 回答としてマーク 三輪の牛 2012年11月29日 3:42
    2012年11月24日 6:25