C#5のasync/awaitの落とし穴のいくつかに関する this ブログ投稿を読んでください。 Gotcha#4には、非常に奥深いもので、以前は考えもしなかったことが記載されています。
簡単に言えば、Action
を受け取るオーバーロードと_Func<Task>
_を受け取るオーバーロード(たとえば_Task.Run
_)の2つのオーバーロードを持つメソッドがあるシナリオについて説明します。この問題は、_async void
_メソッドはイベントハンドラーにのみ使用されるべきであるという議論に根ざしており、投稿は次のシナリオを描写します-次のようなラムダ関数をコンパイルできる場合、コンパイラは何を推測しますか_Func<Task>
_とAction
の両方:
_Task.Run(async () => {
await Task.Delay(1000);
});
_
_Task.Run
_にはTask.Run(Func<Task>)
とTask.Run(Action)
の両方のシグネチャがあるため、非同期匿名関数はどの型にコンパイルされますか? _async void
_または_Func<Task>
_?私の直感では、純粋に_async void
_にコンパイルされると言っていますが、これはその非ジェネリック型ですが、C#コンパイラは賢く、_Func<Task>
_型を優先させる可能性があるためです。
また、使用するオーバーロードを明示的に宣言する方法はありますか? _Func<Task>
_の新しいインスタンスを作成し、そこに非同期ラムダ関数を渡すことができることを知っていますが、それでも_async void
_にコンパイルし、それを_Func<Task>
_のコンストラクターに渡します。 。 _Func<Task>
_としてコンパイルされていることを確認する理想的な方法は何ですか?
デフォルトでTask.Run(Func<Task>)
にコンパイルされることを確認しましたが、これについては十分な説明がありません。
ここにILの関連部分があります
IL_0001: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0006: brtrue.s IL_001B
IL_0008: ldnull
IL_0009: ldftn UserQuery.<Main>b__0
IL_000F: newobj System.Func<System.Threading.Tasks.Task>..ctor//<--Note here
IL_0014: stsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0019: br.s IL_001B
IL_001B: ldsfld UserQuery.CS$<>9__CachedAnonymousMethodDelegate1
IL_0020: call System.Threading.Tasks.Task.Run
visual Studioの型推論を使用してこれを簡単に確認できます。メソッドの上にマウスを置くか、メソッドを押すだけでコンパイルされるメソッドが表示されます F12 コンパイラによって推測されたタイプが何であるかを示すメタデータを見ることができます。
また、使用するオーバーロードを明示的に宣言する方法はありますか?はい、デリゲートを明示的に指定します。
Task.Run(new Action(async () =>
{
await Task.Delay(1000);
}));
Task.Run(new Func<Task>(async () =>
{
await Task.Delay(1000);
}));
_
Task.Run
_にはTask.Run(Func<Task>)
とTask.Run(Action)
の両方のシグネチャがあるため、非同期匿名関数はどの型にコンパイルされますか? _async void
_または_Func<Task>
_?私の直感では、純粋に_async void
_にコンパイルされますが、これは非ジェネリック型ですが、C#コンパイラは賢く、_Func<Task>
_型を優先する可能性があるためです。
一般的なルールは、async
がなくても、戻り値型のデリゲートは戻り値型のないデリゲートよりも優れた一致であるということです。これの別の例は次のとおりです:
_static void Foo(Action a) { }
static void Foo(Func<int> f) { }
static void Bar()
{
Foo(() => { throw new Exception(); });
}
_
これは明確であり、Foo
の2番目のオーバーロードを呼び出します。
また、使用するオーバーロードを明示的に宣言する方法はありますか?
これを明確にする良い方法は、パラメーター名を指定することです。 Action
および_Func<Task>
_オーバーロードのパラメーター名は異なります。
_Task.Run(action: async () => {
await Task.Delay(1000);
});
Task.Run(function: async () => {
await Task.Delay(1000);
});
_