メソッドを追加することで曖昧な呼び出しに関与しない場合に、あいまいな呼び出しを追加する理由


112

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

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)'

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

これはバグですか?仕様のどの部分がこれが当てはまると述べていますか?


2
私はメソッドについて'Overloaded.ComplexOverloadResolution(string)'言及しているとは思いません<string>(string)(string, object)オブジェクトが提供されていないメソッドを指していると思います。
phoog 2011

1
@phoogああ、そのデータはタグであるためStackOverflowによってカットされましたが、エラーメッセージにはテンプレート指定子が含まれています。私はそれをバック追加している。
マッケイ

あなたは私を捕まえた!回答の中で仕様の関連するセクションを引用しましたが、最後の30分はそれらを読んで理解していません!
phoog 2011

@phoogは、仕様のこれらの部分を調べて、他の2つのメソッドではなく、それ自体と別のメソッド以外のメソッドに曖昧さを導入することについては何もわかりません。
マッケイ

これは単なるじゃんけんだと思いました。2つの異なる値のセットには勝者がいますが、3つの値の完全なセットにはありません。
phoog 2012

回答:


107

これはバグですか?

はい。

おめでとうございます。オーバーロードの解決でバグが見つかりました。このバグは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) 

string変換できないため、候補ゼロは明らかに適用できませんstring[]。それは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つのメソッドを選択します。「本当の」あいまいさを見つけるのは、実際にあるとしても、あまり得意ではありません。

それがなぜなのかを合理的に尋ねるかもしれません。「より良い」関係は自動詞であるため、「あいまいさがないあいまい」な2つの方法を見つけるのは非常に難しいです。候補1が2より優れている、2が3より優れている、3が1より優れているという状況が考えられます。

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

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

C#へのオプションの引数の追加をあまりにも長く延期した理由の1つは、オーバーロード解決アルゴリズムに導入された複雑なあいまいな状況の数でした。どうやら、私たちはそれを正しく理解していませんでした。

Connectの問題を入力して追跡したい場合は、お気軽にご連絡ください。あなたがそれを私たちの注意を引くようにしたいだけなら、それは終わったと考えてください。私は来年のテストでフォローアップします。

これを私の注意を喚起してくれてありがとう。エラーの謝罪。


1
ご返信ありがとうございます。「1は2よりも悪い」とおっしゃいましたが、方法1と2しかない場合は方法1を選択しますか?
マッケイ、2011

@McKay:おっと、あなたは正しいです、私は逆に述べました。テキストを修正します。
Eric Lippert、2011

1
「残りの1年」というフレーズが半週も残っていないことを考えると、それを読むのは不便に感じられます:)
BoltClock

2
@BoltClockは確かに、「残りの年に向けて出発する」という記述は、1日の休暇を意味します。
phoog 2011

1
私はそう思う。「3)最初の2つの特性を持つ唯一の候補である」「(無敗で、少なくとも1つの他の候補に勝る)唯一の候補であると読みました。しかし、あなたの最新のコメントは私に「(無敗の唯一の候補者であり)そして少なくとも他の1人の候補者を破った」と思わせます。英語では、実際にグループ化記号を使用できます。後者が真実なら、私はそれを再び得る。
default.kramer 2012年

5

仕様のどの部分がこれが当てはまると述べていますか?

セクション7.5.3(オーバーロードの解決)、およびセクション7.4(メンバーの検索)および7.5.2(型の推論)。

特に「対応する引数のないオプションのパラメーターはパラメーターリストから削除されます」、および「M(p)が非ジェネリックメソッドである場合amd M(q)が一部含まれるセクション7.5.3.2(より良い関数メンバー)に注意してください。汎用的な方法では、M(p)がM(q)よりも優れています。」

ただし、仕様のどの部分がこの動作を制御するかを知るには、仕様のこれらの部分を十分に理解していません。それが準拠しているかどうかを判断することはもちろんです。


しかし、これは、メンバーを追加すると、既存の2つのメソッドのあいまいさが生じる理由を説明していません。
マッケイ

@McKayは十分に公正です(編集を参照)。Eric Lippertがこれが正しい動作であるかどうかを知らせるのを待つ必要があります:->
phoog

1
これらは仕様の正しい部分です。問題は、これは当てはまらないはずだと彼らが言うことです!
Eric Lippert、2011

3

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

このような :

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");
    }
}

ああ、私はこのコードが悪いことを知っています、そしてそれを回避するいくつかの方法があります、問題は「なぜコンパイラがこのように振る舞うのですか?」
マッケイ

1

params最初のメソッドからを削除した場合、これは起こりません。1番目と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つの方法が存在する場合にのみ発生するようです。


しかし、これは、メンバーを追加すると、既存の2つのメソッドのあいまいさが生じる理由を説明していません。
マッケイ

最初の方法と3番目の方法の間であいまいさが発生しますが、コンパイラが他の2つを報告する理由は私を超えています。
Tomislav Markovski、

しかし、2番目のメソッドを削除しても、あいまいさがなく、3番目のメソッドが正常に呼び出されます。したがって、コンパイラーが最初の方法と3番目の方法のあいまいさを持っているようには見えません。
マッケイ

私の編集を参照してください。クレイジーコンパイラ。
トミスラフマルコフスキー2011

実際には、2つの方法だけを組み合わせても、あいまいさは生じません。これは非常に奇妙です。編集2。
Tomislav Markovski、

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);

    コンパイラーは、最初の方法とこれを完全に混同し、追加したばかりです。それは彼があなたの呼び出しで今すべての機能をどのようにすべきかわからないからです

    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.

まず、同じ2つの呼び出しケースを記述します。もちろん、それは同じ方法になりますか、それとも何か違うものを書くつもりですか?
マッケイ

しかし、繰り返しになりますが、残りの回答を正しく理解していれば、コンパイラーが言っていること、つまり最初に追加した3番目のメソッドではなく、最初のメソッドと2番目のメソッドの混乱を読んでいないことになります。
マッケイ

ああ、ありがとう。しかし、それでも私が2番目のコメントで言及する問題はあなたの投稿に残ります。
マッケイ

より明確にするために、あなたは「コンパイラー、最初の方法とこれを追加したばかりの最初の方法との間の完全な混乱に飛び込む」と述べています。しかし、そうではありません。他の2つの方法と混同している。paramsメソッドとジェネリックメソッド。
マッケイ、2011

@McKay:正確には、state1つまたは2つの関数ではなく、3つの関数が与えられていると混乱してしまいます。事実、問題を解決するためには、それらをコメントするだけで十分です。利用可能な関数の中で最もよく一致するのは、が1つありparams、2つ目がgenericsパラメータを持つものです。3つ目を追加すると、1つsetの関数に混乱が生じます。おそらく、コンパイラによって生成された明確なエラーメッセージではないようです。
Tigran、2011
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.