web-dev-qa-db-ja.com

メソッドを追加すると、あいまいさに関係しないのに、なぜあいまいな呼び出しが追加されるのでしょうか。

私はこのクラスを持っています

_public class Overloaded
{
    public void ComplexOverloadResolution(params string[] something)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }
}
_

私がそれをこのように呼ぶならば:

_        var blah = new Overloaded();
        blah.ComplexOverloadResolution("Which wins?");
_

_Normal Winner_をコンソールに書き込みます。

しかし、別のメソッドを追加すると、次のようになります。

_    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
_

次のエラーが発生します。

次のメソッドまたはプロパティ間で呼び出しがあいまいです:> 'Overloaded.ComplexOverloadResolution(params string[])'と 'Overloaded.ComplexOverloadResolution<string>(string)'

メソッドを追加すると呼び出しのあいまいさが生じる可能性があることは理解できますが、それは_(params string[])_と<string>(string)!にすでに存在する2つのメソッド間のあいまいさです。最初のメソッドはparamsであり、2番目のメソッドはジェネリックであるため、あいまいさに関係する2つのメソッドのどちらも新しく追加されたメソッドではないことは明らかです。

これはバグですか?仕様のどの部分がこれが当てはまるはずだと言っていますか?

112
McKay

これはバグですか?

はい。

おめでとうございます。過負荷の解決にバグが見つかりました。バグはC#4および5で再現されます。セマンティックアナライザの「Roslyn」バージョンでは再現されません。私はC#5テストチームに通知しました。最終リリースの前にこれを調査して解決できることを願っています。 (いつものように、約束はありません。)

正しい分析が続きます。候補者は次のとおりです。

0: C(params string[]) in its normal form
1: C(params string[]) in its expanded form
2: C<string>(string) 
3: C(string, object) 

stringstring[]に変換できないため、候補ゼロは明らかに適用できません。それは3つを残します。

3つのうち、独自の最良の方法を決定する必要があります。これを行うには、残りの3つの候補をペアごとに比較します。そのようなペアは3つあります。省略されたオプションのパラメータを取り除くと、それらすべてに同一パラメータリストがあります。つまり、仕様のセクション7.5.3.2で説明されている高度なタイブレイクラウンドに進む必要があります。

1と2のどちらが良いですか?関連するタイブレーカーは、ジェネリックメソッドは非ジェネリックメソッドよりも常に悪いということです。 2は1よりも悪いので、2が勝者になることはできません。

1と3のどちらが良いですか?関連するタイブレーカーは次のとおりです。拡張形式でのみ適用可能な方法は、通常の形式で適用可能な方法よりも常に劣ります。したがって、1は3よりも悪いです。したがって、1が勝者になることはできません。

2と3のどちらが良いですか?関連するタイブレーカーは、ジェネリックメソッドは非ジェネリックメソッドよりも常に悪いということです。 2は3よりも悪いので、2が勝者になることはできません。

複数の該当する候補者のセットから選択されるには、候補者は(1)無敗、(2)少なくとも1つの他の候補者を打ち負かし、(3)最初の2つのプロパティを持つ一意の候補者である必要があります。候補者3は他の候補者に殴打されておらず、少なくとも1人の他の候補者に殴打されています。このプロパティを持つ唯一の候補です。したがって、候補3は一意の最良の候補です。勝つはずです。

奇妙なエラーメッセージを報告していることに正しく注意しているので、C#4コンパイラが間違っているだけではありません。コンパイラが過負荷解決分析を間違って取得していることは、少し驚くべきことです。エラーメッセージが間違っていることはまったく驚くべきことではありません。 「あいまいな方法」エラーヒューリスティックは、基本的に、最適な方法を決定できない場合、候補セットから任意の2つの方法を選択します。 「本当の」曖昧さを見つけるのは、実際にあるとしても、あまり得意ではありません。

それがなぜであるかを合理的に尋ねる人もいるかもしれません。 「betterness」関係は自動詞であるため、「明確にあいまい」な2つのメソッドを見つけるのは非常に困難です。候補者1が2よりも優れており、2が3よりも優れており、3が1よりも優れている状況を考え出すことができます。そのような状況では、2つを「あいまいなもの」として選択するよりも良い方法はありません。

Roslynのこのヒューリスティックを改善したいのですが、優先度は低くなっています。

(読者への演習:「より良い関係が自動詞であるn個の要素のセットの一意の最良のメンバーを識別するための線形時間アルゴリズムを考案する」は、このチームのインタビューの日に尋ねられた質問の1つでした。非常に難しいアルゴリズムです。試してみてください。)

オプションの引数をC#に長い間追加することを後回しにした理由のひとつは、過負荷解決アルゴリズムに導入される複雑で曖昧な状況の数でした。どうやら私たちはそれを正しく理解していませんでした。

追跡するためにConnectの問題を入力したい場合は、お気軽に。それを私たちの注意を引くだけにしたい場合は、それが完了したと考えてください。来年はテストをフォローアップします。

これを私の注意を引いてくれてありがとう。エラーをお詫びします。

107
Eric Lippert

仕様のどの部分がこれが当てはまるはずだと言っていますか?

セクション7.5.3(過負荷解決)、セクション7.4(メンバールックアップ)および7.5.2(型推論)。

特にセクション7.5.3.2(より良い関数メンバー)に注意してください。これには、「対応する引数のないオプションのパラメーターがパラメーターリストから削除されます」および「If M(p)が非-一般的な方法AMD M(q)は一般的な方法であり、M(p)はM(q)よりも優れています。」

ただし、仕様のこれらの部分を完全に理解していないため、仕様のどの部分がこの動作を制御しているかを知ることができず、準拠しているかどうかを判断することもできません。

5
phoog

一部のメソッドで最初のパラメーターの名前を変更し、割り当てるパラメーターを指定することで、このあいまいさを回避できます。

このような :

public class Overloaded
{
    public void ComplexOverloadResolution(params string[] somethings)
    {
        Console.WriteLine("Normal Winner");
    }

    public void ComplexOverloadResolution<M>(M something)
    {
        Console.WriteLine("Confused");
    }

    public void ComplexOverloadResolution(string something, object somethingElse = null)
    {
        Console.WriteLine("Added Later");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Overloaded a = new Overloaded();
        a.ComplexOverloadResolution(something:"asd");
    }
}
3
MhdSyrwan

最初のメソッドからparamsを削除した場合、これは発生しません。最初と3番目のメソッドには両方とも有効な呼び出しComplexOverloadResolution(string)がありますが、最初のメソッドがpublic void ComplexOverloadResolution(string[] something)の場合、あいまいさはありません。

パラメータに値を指定するobject somethingElse = nullはそれをオプションのパラメーターにするので、そのオーバーロードを呼び出すときに指定する必要はありません。

編集:コンパイラはここでいくつかのクレイジーなことをしています。コード内の3番目のメソッドを最初のメソッドの後に移動すると、正しくレポートされます。したがって、正しいものをチェックせずに、最初の2つのオーバーロードを取得して報告しているようです。

'ConsoleApplication1.Program.ComplexOverloadResolution(params string [])'および 'ConsoleApplication1.Program.ComplexOverloadResolution(string、object)'

Edit2:新しい発見。上記の3つからメソッドを削除しても、2つの間にあいまいさは生じません。したがって、順序に関係なく、3つのメソッドが存在する場合にのみ競合が発生するようです。

1
  1. あなたが書くなら

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");
    

    または単に書く

    var blah = new Overloaded();
    blah.ComplexOverloadResolution();
    

    最終的には同じメソッドに、メソッドで

    public void ComplexOverloadResolution(params string[] something
    

    これが原因paramsキーワードであり、パラメータが指定されていないの場合にも最適です。

  2. このような新しいメソッドを追加しようとすると

    public void ComplexOverloadResolution(string something)
    {
        Console.WriteLine("Added Later");
    }
    

    stringパラメータを使用した呼び出しでは完全一致であるため、このメソッドは完全にコンパイルされて呼び出されます。 params string[] somethingよりもはるかに強力です。

  3. あなたがしたようにあなたは2番目のメソッドを宣言します

    public void ComplexOverloadResolution(string something, object something=null);
    

    コンパイラーは、最初のメソッドとこれの間で完全に混乱し、1つ追加しました。彼があなたの電話で今どの機能をすべきかわからないからです

    var blah = new Overloaded();
    blah.ComplexOverloadResolution("Which wins?");
    

    実際、次のコードのように、呼び出しから文字列パラメータを削除すると、すべてが正しくコンパイルされ、以前と同じように機能します

    var blah = new Overloaded();
    blah.ComplexOverloadResolution(); // will be ComplexOverloadResolution(params string[] something) function called here, like a best match.
    
1
Tigran