C# 3.0 ジェネリック型推論 - デリゲートを関数パラメーターとして渡す
-
03-07-2019 - |
質問
C# 3.0 コンパイラは、同じメソッドのデリゲートを暗黙的に作成できるにもかかわらず、ジェネリック関数にパラメータとして渡されるときにメソッドの型を推測できないのはなぜなのか疑問に思っています。
以下に例を示します。
class Test
{
static void foo(int x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
Action<int> f = foo; // I can do this
bar(f); // and then do this
bar(foo); // but this does not work
}
}
合格できると思っていただろう foo
に bar
コンパイラに型を推測させます Action<T>
渡される関数の署名からの情報ですが、これは機能しません。ただし、作成できます Action<int>
から foo
キャストなしでは、コンパイラーが型推論を通じて同じことを実行できない正当な理由はありますか?
解決
これにより、より明確になります:
public class SomeClass
{
static void foo(int x) { }
static void foo(string s) { }
static void bar<T>(Action<T> f){}
static void barz(Action<int> f) { }
static void test()
{
Action<int> f = foo;
bar(f);
barz(foo);
bar(foo);
//these help the compiler to know which types to use
bar<int>(foo);
bar( (int i) => foo(i));
}
}
fooはアクションではありません-fooはメソッドグループです。
- 代入文では、コンパイラはint型が指定されているため、あなたが話しているfooを明確に伝えることができます。
- barz(foo)ステートメントでは、int型が指定されているため、コンパイラはどのfooについて話しているかを知ることができます。
- bar(foo)ステートメントでは、単一のパラメーターを持つ任意のfooになる可能性があるため、コンパイラーはあきらめます。
編集:コンパイラーが型を判別するのに役立つ2つの(さらに)方法を追加しました(つまり、推論ステップをスキップする方法)。
JSkeetの答えの記事を読んだことから、タイプを推測しないという決定は、相互推測シナリオに基づいているようです
static void foo<T>(T x) { }
static void bar<T>(Action<T> f) { }
static void test()
{
bar(foo); //wut's T?
}
一般的な問題は解決できないため、解決策が未解決として存在する特定の問題を残すことを選択します。
この決定の結果として、メソッドにオーバーロードを追加したり、単一のメンバーメソッドグループに使用されるすべての呼び出し元から多くのタイプの混乱を得たりすることはありません。それは良いことだと思います。
他のヒント
推論は、型がこれまでに拡張された場合、失敗する可能性はないはずだということです。つまり、メソッドfoo(string)が型に追加された場合、既存のメソッドの内容が変わらない限り、既存のコードには関係ありません。
そのため、メソッドfooが1つしかない場合でも、fooへの参照(メソッドグループと呼ばれます)をAction<T>
などの型固有ではないデリゲートにキャストすることはできませんが、 Action<int>
などの特定のデリゲート。
それは少し奇妙です、はい。型推論に関する C# 3.0 仕様は読みにくく、間違いもありますが、 見た目 うまくいくはずです。最初のフェーズ (セクション 7.4.2.1) には間違いがあると思います。(明示的なパラメーター型推論 (7.4.2.7) でカバーされていないため、最初の箇条書きでメソッド グループについて言及すべきではありません。つまり、メソッド グループを使用する必要があります)出力タイプの推論 (7.4.2.6)。それ 見た目 うまくいくはずですが、明らかにうまくいきません:(
MS が型推論の仕様を改善しようとしているのは知っているので、もう少し明確になるかもしれません。また、読むのが難しいかどうかに関係なく、メソッド グループと型推論には制限があることも知っています。これは、メソッド グループが実際には 1 つのメソッドのみである場合に特殊な制限になる可能性があります。
Eric Lippert のブログエントリが次のとおりです 戻り値の型推論がメソッド グループで機能しない それは 似ている この場合も同様ですが、ここでは戻り値の型には関心がなく、パラメータの型のみに関心があります。その可能性があります 彼の型推論シリーズの他の投稿 でも役立つかもしれない。
課題に留意してください
Action<int> f = foo;
すでに多くの構文糖があります。コンパイラは実際にこのステートメントのコードを生成します:
Action<int> f = new Action<int>(foo);
対応するメソッド呼び出しは問題なくコンパイルされます:
bar(new Action<int>(foo));
Fwiwは、コンパイラーがtype引数を推測するのを支援します:
bar<int>(foo);
では、問題に要約します。なぜ、メソッドの呼び出しではなく、割り当てステートメントに砂糖が含まれているのでしょうか?私はそれが割り当ての砂糖が明確であることからだと推測する必要があります、可能な置換は1つだけです。しかし、メソッド呼び出しの場合、コンパイラの作成者はすでにオーバーロード解決の問題に対処する必要がありました。そのルールは非常に複雑です。彼らはたぶんそれに近づきませんでした。
完全を期すために、これはC#に固有のものではありません:同じVB.NETコードは同様に失敗します:
Imports System
Module Test
Sub foo(ByVal x As integer)
End Sub
Sub bar(Of T)(ByVal f As Action(Of T))
End Sub
Sub Main()
Dim f As Action(Of integer) = AddressOf foo ' I can do this
bar(f) ' and then do this
bar(AddressOf foo) ' but this does not work
End Sub
End Module
エラーBC32050: 'Public Sub bar(Of T)(f As System.Action(Of T))'の型パラメーター 'T'は推測できません。