List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Pour moi, la différence est purement cosmétique, mais y a-t-il des raisons subtiles pour lesquelles l'une pourrait être préférée à l'autre?
List.ForEach(Console.WriteLine);
List.ForEach(s => Console.WriteLine(s));
Pour moi, la différence est purement cosmétique, mais y a-t-il des raisons subtiles pour lesquelles l'une pourrait être préférée à l'autre?
Réponses:
En regardant le code compilé via ILSpy, il y a en fait une différence entre les deux références. Pour un programme simpliste comme celui-ci:
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 le décompile comme:
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);
}
);
}
}
}
Si vous regardez la pile d'appels IL pour les deux, l'implémentation explicite a beaucoup plus d'appels (et crée une méthode générée):
.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'
tandis que l'implémentation implicite est plus concise:
.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
Je préfère la syntaxe lambda en général . Lorsque vous voyez cela, il vous indique quel est le type. Lorsque vous voyez Console.WriteLine
, vous devez demander à l'IDE de quel type il s'agit. Bien sûr, dans cet exemple trivial, c'est évident, mais dans le cas général, ce n'est peut-être pas tant.
avec les deux exemples que vous avez donnés, ils diffèrent en ce que lorsque vous dites
List.ForEach(Console.WriteLine)
vous dites en fait à la boucle ForEach d'utiliser la méthode WriteLine
List.ForEach(s => Console.WriteLine(s));
définit en fait une méthode que foreach appellera, puis vous lui direz quoi y gérer.
donc pour un simple liners si votre méthode que vous allez appeler porte la même signature que la méthode qui est déjà appelée, je préférerais ne pas définir le lambda, je pense que c'est un peu plus lisible.
pour les méthodes avec des lambdas incompatibles sont certainement une bonne façon de procéder, en supposant qu'elles ne sont pas trop compliquées.
Il y a une raison très forte de préférer la première ligne.
Chaque délégué a une Target
propriété, qui permet aux délégués de se référer aux méthodes d'instance, même après que l'instance est hors de portée.
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;
Nous ne pouvons pas appeler a1.WriteData();
car a1
est nul. Cependant, nous pouvons appeler le action
délégué sans problème, et il s'imprimera 4
, car action
contient une référence à l'instance avec laquelle la méthode doit être appelée.
Lorsque des méthodes anonymes sont passées en tant que délégué dans un contexte d'instance, le délégué contiendra toujours une référence à la classe contenante, même si ce n'est pas évident:
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));
}
}
Dans ce cas spécifique, il est raisonnable de supposer que .ForEach
ne stocke pas le délégué en interne, ce qui signifierait que l'instance deContainer
et toutes ses données sont toujours conservées. Mais il n'y a aucune garantie de cela; la méthode de réception du délégué peut s'accrocher indéfiniment au délégué et à l'instance.
Les méthodes statiques, en revanche, n'ont aucune instance à référencer. Les éléments suivants n'auront pas de référence implicite à l'instance de 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);
}
}