web-dev-qa-db-ja.com

C#型の引数は、複数の戻り値があるSelectでの使用法から推測することはできません

難解なことは何もしていないと思いますが、これについて他に質問はありません。

次のコード(私はそれを本質に減らしました)はC#4でコンパイラエラーを生成します。ただし、type引数が何であるかは明らかです-最大公約数(「クラスA」)も明示的に定義されていますメソッド「Frob」の戻り値の型。コンパイラは、ラムダ式のすべての戻り値の型のリストを作成し、共通の祖先を見つけるために祖先ツリーを作成してから、それを含むメソッドの期待される戻り値の型と調整する必要がありますか?

メソッド 'System.Linq.Enumerable.Select(System.Collections.Generic.IEnumerable、System.Func)'の型引数は、使用法から推測できません。型引数を明示的に指定してみてください。

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Sample
{
    public abstract class A
    {
        private A(int index) { /* ... */ }

        public sealed class A1 : A
        {
            public A1(string text, int index)
                : base(index)
            { /* ... */ }
        }

        public sealed class A2 : A
        {
            public A2(int index)
                : base(index)
            { /* ... */ }
        }

        private static Regex _regex = new Regex(@"(to be)|(not to be)");
        public static IEnumerable<A> Frob(string frobbable)
        {
            return _regex.Matches(frobbable)
                .Cast<Match>()
                .Select((match, i) =>
                {
                    if (match.Groups[1].Success)
                    {
                        return new A1(match.Groups[1].Value, i);
                    }
                    else
                    {
                        return new A2(i);
                    }
                });
        }
    }
}
12
Lars Kemmann

これは、C#4仕様のセクション7.5.2.12です。

匿名関数Fの推定された戻り値の型は、型の推論と過負荷の解決中に使用されます。推定される戻り値の型は、すべてのパラメーター型が既知である匿名関数に対してのみ決定できます。これは、それらが明示的に指定されているか、無名関数変換によって提供されるか、または囲んでいる汎用メソッド呼び出しで型推論中に推定されるためです。推定される戻り値の型は次のように決定されます。

  • Fの本体が式の場合、Fの推定戻り値の型はその式の型です。
  • Fの本体がブロックであり、ブロックのreturnステートメントの式のセットが最も一般的な型T(§7.5.2.14)である場合、Fの推定される戻り値の型はTです。
  • そうしないと、Eの戻り値の型を推測できません。

セクション7.5.2.14はこれです:

場合によっては、一連の式に対して共通の型を推測する必要があります。特に、暗黙的に型指定された配列の要素型とブロック本体を持つ無名関数の戻り値の型は、この方法で検出されます。

直感的には、一連の式E1…Emが与えられると、この推論はメソッドの呼び出しと同等になるはずです。

Tr M<X>(X x1 … X xm)

eiを引数として。

より正確には、推論は固定されていない型変数Xから始まります。次に、出力型推論が各EiからXに対して行われます。最後に、Xが固定され、成功した場合、結果の型Sが式の結果として得られる最も一般的な型になります。そのようなSが存在しない場合、式には最適な共通型がありません。

したがって、次のようになります。

void M<X>(X x1, X x2) {}

A1 a1 = new A1();
A2 a2 = new A2();
M(a1, a2);

... Xの型引数を決定できないため、戻り値の推論も同じように失敗します。

戻り値のどちらかAにキャストすると、機能すると思います。

19
Jon Skeet

これを指示する特定のC#仕様句がどこかにあると思います。 (編集:ジョンスキートはそれを見つけて彼の答えに投稿しました)

通常、このようなラムダ(または三項演算など)は、あいまいさを避けるために、各段階で同じ正確な戻り値の型を持つ必要があります。たとえば、あなたの場合、タイプAまたはObjectを返したいですか?インターフェイスまたは複数レベルの継承をミックスに投入すると、さらに楽しくなります。

この場合の最善の策は、各returnステートメントをタイプAにキャストするか、一時変数に格納することです。

if (match.Groups[1].Success)
    return (A)(new A1(match.Groups[1].Value, i));
else
    return (A)(new A2(i));

または

A returnValue;

if (match.Groups[1].Success)
    returnValue = new A1(match.Groups[1].Value, i);
else
    returnValue = new A2(i);

return returnValue;

編集:推測されたタイプがなくても問題がない場合は、次のコマンドを使用してSelectクエリを明示的に呼び出すことができます。

.Cast<Match>()
.Select<Match, A>((match, i) =>
{
    if (match.Groups[1].Success)
        return new A1(match.Groups[1].Value, i);
    else
        return new A2(i);
});

次に、コンパイラは、戻り値の型がA(それらは)と暗黙的に互換性があることを確認します。

7
Chris Sinclair