D'après mon expérience, chaque fois que la deuxième version semblait préférable, c'était généralement en raison d'une mauvaise dénomination de la méthode en question.
Roman Reiner
Réponses:
23
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;internalclassProgram{privatestaticvoidMain(string[] args){varlist=Enumerable.Range(1,10).ToList();ExplicitLambda(list);ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(Console.WriteLine);}privatestaticvoidExplicitLambda(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{internalclassProgram{privatestaticvoidMain(string[] args){List<int>list=Enumerable.Range(1,10).ToList<int>();Program.ExplicitLambda(list);Program.ImplicitLambda(list);}privatestaticvoidImplicitLambda(List<int>list){list.ForEach(newAction<int>(Console.WriteLine));}privatestaticvoidExplicitLambda(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 staticvoidExplicitLambda(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 voidScratchLambda.Program::'<ExplicitLambda>b__0'(int32)
IL_000f: newobj instance voidclass[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 voidclass[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 staticvoid'<ExplicitLambda>b__0'(int32 s
) cil managed
{.custom instance void[mscorlib]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor()=(01000000)// 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:
Notez qu'il s'agit de la version finale du code d'un programme de scratch rapide, donc il peut y avoir de la place pour une optimisation supplémentaire. Mais c'est la sortie par défaut de Visual Studio.
Agent_9191
2
+1 C'est parce que la syntaxe lambda enveloppe en fait l'appel de méthode brute dans une fonction anonyme <i> sans raison </i>. Ceci est complètement inutile, par conséquent, vous devez utiliser le groupe de méthodes brutes comme paramètre Func <> lorsqu'il est disponible.
Ed James
Wow, vous obtenez la coche verte, pour la recherche!
Benjol
2
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.
Je préférerais la syntaxe labmda pour la cohérence avec les cas où elle est requise.
bunglestink
4
Je ne suis pas en C #, mais dans les langages que j'ai utilisés avec les lambdas (JavaScript, Scheme et Haskell), les gens vous donneraient probablement le contraire. Je pense que cela montre à quel point le bon style dépend de la langue.
Tikhon Jelvis
de quelle manière vous indique-t-il le type? vous pouvez certainement être explicite sur le type d'un paramètre lambdas mais son loin d'être courant pour le faire, et ne se fait pas dans cette situation
jk.
1
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.
Nous ne pouvons pas appeler a1.WriteData();car a1est nul. Cependant, nous pouvons appeler le actiondélégué sans problème, et il s'imprimera 4, car actioncontient 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:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//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 .ForEachne 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:
publicclassContainer{privateList<int> data =newList<int>(){1,2,3,4,5};publicvoidPrintItems(){//Since Console.WriteLine is a static method, there is no implicit reference
data.ForEach(Console.WriteLine);}}
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:
ILSpy le décompile comme:
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):
tandis que l'implémentation implicite est plus concise:
la source
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.la source
avec les deux exemples que vous avez donnés, ils diffèrent en ce que lorsque vous dites
vous dites en fait à la boucle ForEach d'utiliser la méthode WriteLine
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.
la source
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.Nous ne pouvons pas appeler
a1.WriteData();
cara1
est nul. Cependant, nous pouvons appeler leaction
délégué sans problème, et il s'imprimera4
, caraction
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:
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
:la source