8

Enumerable.Selectオーバーロードされたメソッドへの次の呼び出し:

var itemOnlyOneTuples = "test".Select<char, Tuple<char>>(Tuple.Create);

あいまいなエラーで失敗します (わかりやすくするために名前空間は削除されています)。

The call is ambiguous between the following methods or properties: 
'Enumerable.Select<char,Tuple<char>>
           (IEnumerable<char>,Func<char,Tuple<char>>)'
and 
'Enumerable.Select<char,Tuple<char>>
          (IEnumerable<char>, Func<char,int,Tuple<char>>)'

型引数を明示的に指定しないとあいまいさが生じる理由は確かに理解できますが(両方のオーバーロードが適用されます)、その後はわかりません。

method-group 引数がTuple.Create<char>(char). 期待される型Tuple.Createに変換できるオーバーロードがないため、2 番目のオーバーロードは適用しないでください 。コンパイラが によって混乱してFunc<char,int,Tuple<char>>いると推測Tuple.Create<char, int>(char, int)していますが、その戻り値の型が間違っています。2 つのタプルを返すため、関連するFunc型に変換できません。

ところで、次のいずれかがコンパイラを満足させます。

  1. method-group 引数の type-argument の指定: Tuple.Create<char>(おそらく、これは実際には型推論の問題でしょうか?)。
  2. 引数をメソッド グループではなくラムダ式にする: x => Tuple.Create(x). (呼び出しの型推論でうまく機能しSelectます)。

当然のことながら、この方法で の他のオーバーロードを呼び出そうとしてSelectも失敗します。

var itemIndexTwoTuples = "test".Select<char, Tuple<char, int>>(Tuple.Create);

ここでの正確な問題は何ですか?

4

2 に答える 2

20

まず、これは次の複製であることに注意してください。

Func<T> が Func<IEnumerable<T>> とあいまいなのはなぜですか?

ここでの正確な問題は何ですか?

トーマスの推測は基本的に正しい。正確な詳細は次のとおりです。

一度に 1 ステップずつ見ていきましょう。呼び出しがあります:

"test".Select<char, Tuple<char>>(Tuple.Create); 

オーバーロードの解決によって、Select の呼び出しの意味を決定する必要があります。文字列または文字列の基本クラスにはメソッド「Select」がないため、これは拡張メソッドである必要があります。

IEnumerable<char>文字列は変換可能であり、おそらくusing System.Linq;どこかに があるため、候補セットにはいくつかの可能な拡張メソッドがあります。IEnumerable<char>パターン「Select、ジェネリック アリティ 2、指定されたメソッド タイプ引数で構築された場合、最初の引数としてを受け取る」に一致する多くの拡張メソッドがあります。

具体的には、次の 2 つの候補があります。

Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,Tuple<char>>)
Enumerable.Select<char,Tuple<char>>(IEnumerable<char>,Func<char,int,Tuple<char>>) 

さて、私たちが直面する最初の質問は、候補者が該当するかどうかです。つまり、指定された各引数から対応する仮パラメーターの型への暗黙的な変換はありますか?

素晴らしい質問です。明らかに、最初の引数は文字列である「レシーバー」であり、暗黙的に に変換可能IEnumerable<char>です。ここでの問題は、2 番目の引数であるメソッド グループ "Tuple.Create" が暗黙的に仮パラメーターの型Func<char,Tuple<char>>に変換できるかどうかFunc<char,int, Tuple<char>>です。

メソッド グループが特定のデリゲート型に変換できるのはいつですか? メソッド グループは、デリゲートの仮パラメーターの型と同じ型の引数を指定してオーバーロードの解決が成功した場合に、デリゲート型に変換できます

つまり、M は、型 'A' の式 'someA' が与えられた場合Func<A, R>に、フォームの呼び出しでオーバーロードの解決が成功した場合に変換可能です。M(someA)

への呼び出しでオーバーロードの解決は成功したTuple.Create(someChar)でしょうか? はい; オーバーロードの解決では、 が選択されTuple.Create<char>(char)ます。

への呼び出しでオーバーロードの解決は成功したTuple.Create(someChar, someInt)でしょうか? はい、オーバーロード解決は を選択したでしょうTuple.Create<char,int>(char, int)

どちらの場合もオーバーロードの解決は成功するため、メソッド グループは両方のデリゲート型に変換できます。いずれかのメソッドの戻り値の型がデリゲートの戻り値の型と一致しないという事実は関係ありません。オーバーロードの解決は、戻り値の型分析に基づいて成功または失敗しません

メソッド グループからデリゲート型への変換可能性は、戻り値の型分析に基づいて成功または失敗するはずだと合理的に言うかもしれませんが、それは言語の指定方法ではありません。この言語は、メソッド グループ変換のテストとしてオーバーロードの解決を使用するように指定されています。これは妥当な選択だと思います。

したがって、該当する候補が 2 つあります。どちらが優れているかを判断する方法はありますか? 仕様には、より具体的な型への変換の方が優れていると記載されています。あなたが持っている場合

void M(string s) {}
void M(object o) {}
...
M(null);

文字列はオブジェクトよりも具体的であるため、オーバーロード解決は文字列バージョンを選択します。これらのデリゲート タイプの 1 つは、他のタイプよりも具体的ですか? いいえ、どちらもより具体的ではありません。(これは、より良い変換ルールを簡略化したものです。実際には多くのタイブレーカーがありますが、ここでは適用されません。)

したがって、どちらか一方を優先する根拠はありません。

繰り返しますが、確かに根拠があると合理的に言うことができます。つまり、これらの変換の 1 つがデリゲートの戻り値の型の不一致エラーを生成し、そのうちの 1 つが生成しないということです。繰り返しますが、言語は、選択した変換が最終的にエラーになるかどうかではなく、仮パラメーターの型間の関係を考慮して、より良いものを推論するように指定されています。

どちらを優先するかの根拠がないため、これはあいまいなエラーです。

同様のあいまいなエラーを作成するのは簡単です。例えば:

void M(Func<int, int> f){}
void M(Expression<Func<int, int>> ex) {}
...
M(x=>Q(++x));

それはあいまいです。式ツリー内に ++ を含めることは違法ですが、変換可能性ロジックは、ラムダの本体に式ツリー内で違法となる何かが含まれているかどうかを考慮しません。変換ロジックは、型がチェックアウトされることを確認するだけであり、チェックアウトします。それを考えると、M の 1 つを他のものより優先する理由はないので、これはあいまいです。

あなたはそれに注意してください

"test".Select<char, Tuple<char>>(Tuple.Create<char>); 

成功します。その理由がわかりました。過負荷の解決は、次のことを決定する必要があります

Tuple.Create<char>(someChar)

また

Tuple.Create<char>(someChar, someInt)

成功するでしょう。最初の候補はあり、2 番目の候補はそうでないため、2 番目の候補は適用できず、除外されます。したがって、あいまいになることはありません。

また、次のことに注意してください。

"test".Select<char, Tuple<char>>(x=>Tuple.Create(x)); 

明確です。ラムダ変換では、返される式の型とターゲット デリゲートの戻り値の型との互換性考慮されます。メソッド グループとラムダ式が、変換可能性を判断するために 2 つの微妙に異なるアルゴリズムを使用しているのは残念ですが、現在はそれで行き詰まっています。メソッド グループの変換は、ラムダ変換よりもはるかに長く言語に存在していたことを思い出してください。それらが同時に追加されていれば、それらのルールは一貫したものになったと思います。

于 2011-03-09T22:40:05.650 に答える
5

コンパイラが によって混乱していると推測してTuple.Create<char, int>(char, int)いますが、戻り値の型が間違っています: 2 つのタプルを返します。

戻り値の型はメソッド シグネチャの一部ではないため、オーバーロードの解決時には考慮されません。オーバーロードが選択された後にのみ検証されます。したがって、コンパイラが知る限り、Tuple.Create<char, int>(char, int)は有効な候補であり、より良くも悪くもないTuple.Create<char>(char)ため、コンパイラは決定できません。

于 2011-03-05T13:49:56.017 に答える