En mi experiencia, cuando la segunda versión parecía preferible, generalmente se debía a una mala denominación del método en cuestión.
Roman Reiner el
Respuestas:
23
Mirando el código compilado a través de ILSpy, en realidad hay una diferencia en las dos referencias. Para un programa simplista como este:
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 lo descompila como:
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 observa la pila de llamadas de IL para ambos, la implementación explícita tiene muchas más llamadas (y crea un método generado):
.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'
mientras que la implementación implícita es más concisa:
Tenga en cuenta que esta es la versión de lanzamiento del código de un programa de scratch rápido, por lo que puede haber espacio para una mayor optimización. Pero esta es la salida predeterminada de Visual Studio.
Agent_9191
2
+1 Esto se debe a que la sintaxis lambda en realidad está envolviendo la llamada al método sin procesar en una función anónima <i> sin motivo </i>. Esto es completamente inútil, por lo tanto, debe usar el grupo de métodos sin procesar como parámetro Func <> cuando esté disponible.
Ed James
¡Guau, obtienes la marca verde, para la investigación!
Benjol
2
Prefiero la sintaxis lambda en general . Cuando veas eso, te dirá cuál es el tipo. Cuando vea Console.WriteLine, tendría que preguntarle al IDE de qué tipo es. Por supuesto, en este ejemplo trivial, es obvio, pero en el caso general, podría no ser tanto.
Prefiero la sintaxis labmda para mantener la coherencia con los casos en los que se requiere.
bunglestink
44
No estoy en una persona de C #, pero en los idiomas que he usado con lambdas (JavaScript, Scheme y Haskell) la gente probablemente te daría el consejo opuesto. Creo que eso muestra cuán buen estilo depende del idioma.
Tikhon Jelvis
de qué manera te dice el tipo? ciertamente puede ser explícito sobre el tipo de parámetro lambdas, pero está lejos de ser común hacerlo, y no se hace en esta situación
jk.
1
con los dos ejemplos que diste, difieren en eso cuando dices
List.ForEach(Console.WriteLine)
en realidad le estás diciendo al ForEach Loop que use el método WriteLine
List.ForEach(s =>Console.WriteLine(s));
en realidad está definiendo un método que llamará el foreach y luego le está diciendo qué manejar allí.
por lo tanto, para líneas simples, si su método al que va a llamar lleva la misma firma que el método que ya se llamó, preferiría no definir la lambda, creo que es un poco más legible.
Los métodos con lambdas incompatibles son definitivamente una buena opción, suponiendo que no sean demasiado complicados.
Hay una razón muy fuerte para preferir la primera línea.
Cada delegado tiene una Targetpropiedad, que permite a los delegados referirse a métodos de instancia, incluso después de que la instancia se haya salido del alcance.
No podemos llamar a1.WriteData();porque a1es nulo. Sin embargo, podemos invocar al actiondelegado sin problemas, y se imprimirá 4, porque actioncontiene una referencia a la instancia con la que se debe llamar al método.
Cuando los métodos anónimos se pasan como delegados en un contexto de instancia, el delegado aún tendrá una referencia a la clase que lo contiene, aunque no sea obvio:
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));}}
En este caso específico, es razonable suponer que .ForEachno está almacenando el delegado internamente, lo que significaría que la instancia deContainer y todos sus datos aún se conservan. Pero no hay garantía de eso; el método que recibe al delegado puede retener al delegado y la instancia indefinidamente.
Los métodos estáticos, por otro lado, no tienen una instancia de referencia. Lo siguiente no tendrá una referencia implícita a la instancia 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);}}
Respuestas:
Mirando el código compilado a través de ILSpy, en realidad hay una diferencia en las dos referencias. Para un programa simplista como este:
ILSpy lo descompila como:
Si observa la pila de llamadas de IL para ambos, la implementación explícita tiene muchas más llamadas (y crea un método generado):
mientras que la implementación implícita es más concisa:
fuente
Prefiero la sintaxis lambda en general . Cuando veas eso, te dirá cuál es el tipo. Cuando vea
Console.WriteLine
, tendría que preguntarle al IDE de qué tipo es. Por supuesto, en este ejemplo trivial, es obvio, pero en el caso general, podría no ser tanto.fuente
con los dos ejemplos que diste, difieren en eso cuando dices
en realidad le estás diciendo al ForEach Loop que use el método WriteLine
en realidad está definiendo un método que llamará el foreach y luego le está diciendo qué manejar allí.
por lo tanto, para líneas simples, si su método al que va a llamar lleva la misma firma que el método que ya se llamó, preferiría no definir la lambda, creo que es un poco más legible.
Los métodos con lambdas incompatibles son definitivamente una buena opción, suponiendo que no sean demasiado complicados.
fuente
Hay una razón muy fuerte para preferir la primera línea.
Cada delegado tiene una
Target
propiedad, que permite a los delegados referirse a métodos de instancia, incluso después de que la instancia se haya salido del alcance.No podemos llamar
a1.WriteData();
porquea1
es nulo. Sin embargo, podemos invocar alaction
delegado sin problemas, y se imprimirá4
, porqueaction
contiene una referencia a la instancia con la que se debe llamar al método.Cuando los métodos anónimos se pasan como delegados en un contexto de instancia, el delegado aún tendrá una referencia a la clase que lo contiene, aunque no sea obvio:
En este caso específico, es razonable suponer que
.ForEach
no está almacenando el delegado internamente, lo que significaría que la instancia deContainer
y todos sus datos aún se conservan. Pero no hay garantía de eso; el método que recibe al delegado puede retener al delegado y la instancia indefinidamente.Los métodos estáticos, por otro lado, no tienen una instancia de referencia. Lo siguiente no tendrá una referencia implícita a la instancia de
Container
:fuente