のようなものを扱うときList<string>
は、次のように書くことができます。
list.ForEach(x => Console.WriteLine(x));
または、メソッドグループを使用して同じ操作を実行できます。
list.ForEach(Console.WriteLine);
コードの2行目は見た目がすっきりしているので好きですが、これには何か利点がありますか?
のようなものを扱うときList<string>
は、次のように書くことができます。
list.ForEach(x => Console.WriteLine(x));
または、メソッドグループを使用して同じ操作を実行できます。
list.ForEach(Console.WriteLine);
コードの2行目は見た目がすっきりしているので好きですが、これには何か利点がありますか?
さて、様子を見て、何が起こるか見てみましょう。
static void MethodGroup()
{
new List<string>().ForEach(Console.WriteLine);
}
static void LambdaExpression()
{
new List<string>().ForEach(x => Console.WriteLine(x));
}
これは、次の IL にコンパイルされます。
.method private hidebysig static void MethodGroup() cil managed
{
.maxstack 8
L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
L_0005: ldnull
L_0006: ldftn void [mscorlib]System.Console::WriteLine(string)
L_000c: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
L_0011: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>)
L_0016: ret
}
.method private hidebysig static void LambdaExpression() cil managed
{
.maxstack 8
L_0000: newobj instance void [mscorlib]System.Collections.Generic.List`1<string>::.ctor()
L_0005: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_000a: brtrue.s L_001d
L_000c: ldnull
L_000d: ldftn void Sandbox.Program::<LambdaExpression>b__0(string)
L_0013: newobj instance void [mscorlib]System.Action`1<string>::.ctor(object, native int)
L_0018: stsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_001d: ldsfld class [mscorlib]System.Action`1<string> Sandbox.Program::CS$<>9__CachedAnonymousMethodDelegate1
L_0022: call instance void [mscorlib]System.Collections.Generic.List`1<string>::ForEach(class [mscorlib]System.Action`1<!0>)
L_0027: ret
}
メソッド グループ アプローチではAction<T>
1 回限りのデリゲートが作成され、ラムダ式アプローチでは非表示の匿名デリゲート フィールドが作成され、必要に応じてそのインライン初期化が行われることに注目してください。brtrue
の指示に注意してくださいIL_000a
。
ラムダ式を使用する場合、余分なレベルの間接参照があります。このような非クロージャ式を使用すると、他の人が言及しているように、間に追加のメソッド呼び出しを行うだけです。
ただし、いくつかの興味深い違いがあります。2番目のケースでは、呼び出しごとに新しいデリゲートインスタンスが作成されます。前者の場合、デリゲートは一度作成され、非表示フィールドとしてキャッシュされるため、頻繁に呼び出す場合は、割り当てを節約できます。
さらに、ラムダ式にローカル変数を導入すると、それはクロージャになり、ローカルメソッドが生成されるだけでなく、この情報を保持する新しいクラスが作成されます。つまり、そこに追加の割り当てが行われます。
他の人が指摘しているように、ラムダによって引き起こされる間接の余分な不要な層があります。ただし、言語にも微妙な違いがあります。たとえば、C#3では、ジェネリック型推論は、戻り型推論を実行しようとしたときとM(F)
は異なる動作をします。M(x=>F(x))
詳細については、以下を参照してください。
およびフォローアップ:
https://docs.microsoft.com/en-us/archive/blogs/ericlippert/method-type-inference-changes-part-zero
メリットがあると思います。最初のケースでは、関数を呼び出す匿名メソッドを作成していConsole.Writeline(string)
ますが、他のケースでは、既存の関数への参照を渡すだけです。
はい; 1つ目は、実際には不要な追加の暫定呼び出しが発生する可能性があります。x
Console.WriteLineはすでにForEachが探している署名と一致するメソッドであるため、単純に呼び出すメソッドに渡すConsole.WriteLine(x);
必要はありません。最初のメソッドを実行する必要はありません。
個人的には、デバッグの混乱が少ないので2番目の方が好きですが、この場合は、どちらも同じことを行うので、スタイルの問題だと思います。
メソッドグループが好きな人や嫌いな人を困らせる以外に、具体的なメリットはありません[喜ばれるはずです]。また、コードが以前のコンパイラと互換性がなくなります。
-オシーン