回答:
ILSpyでコンパイルされたコードを見ると、実際には2つの参照に違いがあります。このような単純なプログラムの場合:
namespace ScratchLambda
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
internal class Program
{
private static void Main(string[] args)
{
var list = Enumerable.Range(1, 10).ToList();
ExplicitLambda(list);
ImplicitLambda(list);
}
private static void ImplicitLambda(List<int> list)
{
list.ForEach(Console.WriteLine);
}
private static void ExplicitLambda(List<int> list)
{
list.ForEach(s => Console.WriteLine(s));
}
}
}
ILSpyは次のように逆コンパイルします。
using System;
using System.Collections.Generic;
using System.Linq;
namespace ScratchLambda
{
internal class Program
{
private static void Main(string[] args)
{
List<int> list = Enumerable.Range(1, 10).ToList<int>();
Program.ExplicitLambda(list);
Program.ImplicitLambda(list);
}
private static void ImplicitLambda(List<int> list)
{
list.ForEach(new Action<int>(Console.WriteLine));
}
private static void ExplicitLambda(List<int> list)
{
list.ForEach(delegate(int s)
{
Console.WriteLine(s);
}
);
}
}
}
両方のIL呼び出しスタックを見ると、Explicit実装にはさらに多くの呼び出しがあります(生成されたメソッドを作成します)。
.method private hidebysig static
void ExplicitLambda (
class [mscorlib]System.Collections.Generic.List`1<int32> list
) cil managed
{
// Method begins at RVA 0x2093
// Code size 36 (0x24)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0006: brtrue.s IL_0019
IL_0008: ldnull
IL_0009: ldftn void ScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
IL_000f: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
IL_0014: stsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_0019: ldsfld class [mscorlib]System.Action`1<int32> ScratchLambda.Program::'CS$<>9__CachedAnonymousMethodDelegate1'
IL_001e: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
IL_0023: ret
} // end of method Program::ExplicitLambda
.method private hidebysig static
void '<ExplicitLambda>b__0' (
int32 s
) cil managed
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
01 00 00 00
)
// Method begins at RVA 0x208b
// Code size 7 (0x7)
.maxstack 8
IL_0000: ldarg.0
IL_0001: call void [mscorlib]System.Console::WriteLine(int32)
IL_0006: ret
} // end of method Program::'<ExplicitLambda>b__0'
暗黙の実装はより簡潔です:
.method private hidebysig static
void ImplicitLambda (
class [mscorlib]System.Collections.Generic.List`1<int32> list
) cil managed
{
// Method begins at RVA 0x2077
// Code size 19 (0x13)
.maxstack 8
IL_0000: ldarg.0
IL_0001: ldnull
IL_0002: ldftn void [mscorlib]System.Console::WriteLine(int32)
IL_0008: newobj instance void class [mscorlib]System.Action`1<int32>::.ctor(object, native int)
IL_000d: callvirt instance void class [mscorlib]System.Collections.Generic.List`1<int32>::ForEach(class [mscorlib]System.Action`1<!0>)
IL_0012: ret
} // end of method Program::ImplicitLambda
私は一般的にラムダ構文を好むでしょう。それを見ると、タイプが何であるかがわかります。が表示さConsole.WriteLine
れたら、IDEにそのタイプを尋ねる必要があります。もちろん、この些細な例では明らかですが、一般的なケースではそれほど多くはないかもしれません。
あなたが与えた2つの例では、あなたが言うときにそれらは異なります
List.ForEach(Console.WriteLine)
実際にForEachループにWriteLineメソッドを使用するよう指示しています
List.ForEach(s => Console.WriteLine(s));
foreachが呼び出すメソッドを実際に定義し、そこで何を処理するかを指定しています。
シンプルなライナーの場合、呼び出すメソッドが既に呼び出されているメソッドと同じシグネチャを持っている場合、ラムダを定義しない方がいいでしょう、もう少し読みやすいと思います。
互換性のないラムダを持つメソッドの場合、それらが過度に複雑にならないことを前提として、間違いなく良い方法です。
最初の行を好む理由は非常に強力です。
すべてのデリゲートにはTarget
プロパティがあり、インスタンスがスコープ外になった後でも、デリゲートはインスタンスメソッドを参照できます。
public class A {
public int Data;
public void WriteData() {
Console.WriteLine(this.Data);
}
}
var a1 = new A() {Data=4};
Action action = a1.WriteData;
a1 = null;
nullであるa1.WriteData();
ため、呼び出すことa1
ができません。しかし、我々は呼び出すことができますaction
問題なくデリゲートを、そしてそれが印刷されます4
ので、action
メソッドが呼び出されるべきでインスタンスへの参照を保持しています。
匿名メソッドがインスタンスコンテキストでデリゲートとして渡される場合、デリゲートはそれが含まれているクラスへの参照を保持します。
public class Container {
private List<int> data = new List<int>() {1,2,3,4,5};
public void PrintItems() {
//There is an implicit reference to an instance of Container here
data.ForEach(s => Console.WriteLine(s));
}
}
この特定のケースでは.ForEach
、デリゲートが内部に格納されていないと想定するのが合理的です。つまり、インスタンスContainer
とそのすべてのデータがまだ保持されていることを意味します。しかし、それを保証するものはありません。デリゲートを受け取るメソッドは、デリゲートとインスタンスを無期限に保持する場合があります。
一方、静的メソッドには、参照するインスタンスはありません。以下は、のインスタンスへの暗黙的な参照を持ちませんContainer
。
public class Container {
private List<int> data = new List<int>() {1,2,3,4,5};
public void PrintItems() {
//Since Console.WriteLine is a static method, there is no implicit reference
data.ForEach(Console.WriteLine);
}
}