Función local vs Lambda C # 7.0

178

Estoy mirando las nuevas implementaciones en C # 7.0 y me parece interesante que hayan implementado funciones locales, pero no puedo imaginar un escenario en el que se preferiría una función local sobre una expresión lambda, y cuál es la diferencia entre las dos.

Entiendo que las lambdas son anonymousfunciones, mientras que las funciones locales no lo son, pero no puedo entender un escenario del mundo real, donde la función local tiene ventajas sobre las expresiones lambda

Cualquier ejemplo sería muy apreciado. Gracias.

Sid
fuente
9
Genéricos, parámetros de salida, funciones recursivas sin tener que inicializar el lambda a nulo, etc.
Kirk Woll
55
@KirkWoll: debe publicar esto como respuesta.
Enigmatividad

Respuestas:

276

Esto fue explicado por Mads Torgersen en C # Design Meeting Notes donde se discutieron por primera vez las funciones locales :

Quieres una función auxiliar. Solo lo está utilizando desde una única función, y probablemente utiliza variables y parámetros de tipo que están dentro del alcance de esa función que lo contiene. Por otro lado, a diferencia de un lambda, no lo necesita como un objeto de primera clase, por lo que no le importa darle un tipo de delegado y asignar un objeto de delegado real. También es posible que desee que sea recursivo o genérico, o que lo implemente como un iterador.

Para ampliarlo un poco más, las ventajas son:

  1. Actuación.

    Al crear una lambda, se debe crear un delegado, que es una asignación innecesaria en este caso. Las funciones locales son realmente solo funciones, no se necesitan delegados.

    Además, las funciones locales son más eficientes con la captura de variables locales: las lambdas generalmente capturan variables en una clase, mientras que las funciones locales pueden usar una estructura (aprobada usando ref), que nuevamente evita una asignación.

    Esto también significa que llamar a las funciones locales es más barato y pueden integrarse, posiblemente aumentando aún más el rendimiento.

  2. Las funciones locales pueden ser recursivas.

    Las lambdas también pueden ser recursivas, pero requieren un código incómodo, donde primero se asigna nulla una variable delegada y luego a la lambda. Las funciones locales pueden ser naturalmente recursivas (incluidas las recursivas recíprocas).

  3. Las funciones locales pueden ser genéricas.

    Las lambdas no pueden ser genéricas, ya que deben asignarse a una variable con un tipo concreto (ese tipo puede usar variables genéricas del ámbito externo, pero eso no es lo mismo).

  4. Las funciones locales se pueden implementar como un iterador.

    Lambdas no puede usar la palabra clave yield return(y yield break) para implementar la IEnumerable<T>función de retorno. Las funciones locales pueden.

  5. Las funciones locales se ven mejor.

    Esto no se menciona en la cita anterior y podría ser solo mi sesgo personal, pero creo que la sintaxis de la función normal se ve mejor que asignar una lambda a una variable delegada. Las funciones locales también son más sucintas.

    Comparar:

    int add(int x, int y) => x + y;
    Func<int, int, int> add = (x, y) => x + y;
svick
fuente
22
Me gustaría agregar que las funciones locales tienen nombres de parámetros en el lado de la persona que llama. Las lambdas no lo hacen.
Lensflare
3
@Lensflare Es cierto que los nombres de parámetros de lambdas no se conservan, pero eso se debe a que deben convertirse en delegados, que tienen sus propios nombres. Por ejemplo: Func<int, int, int> f = (x, y) => x + y; f(arg1:1, arg2:1);.
svick
1
Gran lista! Sin embargo, puedo imaginar cómo el compilador IL / JIT podría realizar todas las optimizaciones mencionadas en 1. también para los delegados si su uso se adhiere a ciertas reglas.
Marcin Kaczmarek
1
@Casebash Porque las lambdas siempre usan un delegado y ese delegado mantiene el cierre como un object. Entonces, lambdas podría usar una estructura, pero tendría que estar encuadrada, por lo que aún tendría esa asignación adicional.
svick
1
@happybits Principalmente cuando no necesita darle un nombre, como cuando lo pasa al método.
svick
83

Además de la excelente respuesta de svick, hay una ventaja más para las funciones locales:
se pueden definir en cualquier lugar de la función, incluso después de la returndeclaración.

public double DoMath(double a, double b)
{
    var resultA = f(a);
    var resultB = f(b);
    return resultA + resultB;

    double f(double x) => 5 * x + 3;
}
Tim Pohlmann
fuente
55
Esto es realmente útil, ya que puedo acostumbrarme a poner todas las funciones auxiliares en una #region Helpersen la parte inferior de la función, para evitar el desorden dentro de esa función y, especialmente, evitar el desorden en la clase principal.
AustinWBryan
También aprecio esto. Hace que la función principal que está viendo sea más fácil de leer, ya que no necesita mirar a su alrededor para encontrar dónde comienza. Si desea ver los detalles de implementación, siga mirando más allá del final.
Remi Despres-Smyth
3
Si sus funciones son tan grandes que necesitan regiones en ellas, son demasiado grandes.
ssmith
9

Si también se pregunta cómo probar la función local, debe verificar JustMock ya que tiene la funcionalidad para hacerlo. Aquí hay un ejemplo de clase simple que se probará:

public class Foo // the class under test
{ 
    public int GetResult() 
    { 
        return 100 + GetLocal(); 
        int GetLocal () 
        { 
            return 42; 
        } 
    } 
}

Y así es como se ve la prueba:

[TestClass] 
public class MockLocalFunctions 
{ 
    [TestMethod] 
    public void BasicUsage() 
    { 
        //Arrange 
        var foo = Mock.Create<Foo>(Behavior.CallOriginal); 
        Mock.Local.Function.Arrange<int>(foo, "GetResult", "GetLocal").DoNothing(); 

        //Act 
        var result = foo. GetResult(); 

        //Assert 
        Assert.AreEqual(100, result); 
    } 
} 

Aquí hay un enlace a la documentación de JustMock .

Descargo de responsabilidad. Soy uno de los desarrolladores responsables de JustMock .

Mihail Vladov
fuente
Es genial ver a desarrolladores tan apasionados abogando por que las personas usen su herramienta. ¿Cómo se interesó en escribir herramientas de desarrollador como un trabajo a tiempo completo? Como estadounidense, mi impresión es que puede ser difícil encontrar tales carreras a menos que tenga una maestría o un doctorado. en comp sci.
John Zabroski
Hola John y gracias por las amables palabras. Como desarrollador de software, no veo nada mejor que ser apreciado por mis clientes por el valor que les proporciono. Combine eso con el deseo de un trabajo desafiante y competitivo y recibirá una lista bastante limitada de cosas que me apasionarían. Escribir herramientas para desarrolladores de productividad está en esa lista. Al menos en mi mente :) Con respecto a la carrera, creo que las empresas que proporcionan herramientas para desarrolladores son un porcentaje bastante pequeño de todas las empresas de software y es por eso que es más difícil encontrar esa oportunidad.
Mihail Vladov
Una pregunta aparte. ¿Por qué no llamas a VerifyAll aquí? ¿Hay alguna manera de decirle a JustMock que verifique que también se llamó a la función local?
John Zabroski
2
Hola @JohnZabroski, el escenario probado no requirió la afirmación de ocurrencias. Por supuesto, puede verificar que se realizó una llamada. Primero, debe especificar cuántas veces espera que se llame al método. De esta manera: .DoNothing().OccursOnce();y luego afirme que la llamada se realizó llamando al Mock.Assert(foo);método. Si está interesado en cómo se admiten otros escenarios, puede leer nuestro artículo de ayuda Afirmación de sucesos .
Mihail Vladov
0

Utilizo funciones en línea para evitar la presión de recolección de basura, especialmente cuando se trata de métodos de ejecución más largos. Digamos que a uno le gustaría obtener 2 años o datos de mercado para un símbolo de teletipo dado. Además, uno puede empacar mucha funcionalidad y lógica de negocios si es necesario.

lo que uno hace es abrir una conexión de socket al servidor y recorrer los datos que vinculan un evento a un evento. Uno puede pensarlo de la misma manera que se diseña una clase, solo uno no está escribiendo métodos auxiliares en todo el lugar que realmente solo funcionan para un nivel de funcionalidad. a continuación se muestra una muestra de cómo se vería esto, tenga en cuenta que estoy usando variables y los métodos "auxiliares" están debajo de finalmente. En Finalmente, elimino muy bien los controladores de eventos, si mi clase de Exchange fuera externa / inyectada, no tendría ningún controlador de eventos pendiente registrado

void List<HistoricalData> RequestData(Ticker ticker, TimeSpan timeout)
{
    var socket= new Exchange(ticker);
    bool done=false;
    socket.OnData += _onData;
    socket.OnDone += _onDone;
    var request= NextRequestNr();
    var result = new List<HistoricalData>();
    var start= DateTime.Now;
    socket.RequestHistoricalData(requestId:request:days:1);
    try
    {
      while(!done)
      {   //stop when take to long….
        if((DateTime.Now-start)>timeout)
           break;
      }
      return result;

    }finally
    {
        socket.OnData-=_onData;
        socket.OnDone-= _onDone;
    }


   void _OnData(object sender, HistoricalData data)
   {
       _result.Add(data);
   }
   void _onDone(object sender, EndEventArgs args)
   {
      if(args.ReqId==request )
         done=true;
   } 
}

Puede ver las ventajas que se mencionan a continuación, aquí puede ver una implementación de muestra. Espero que ayude a explicar los beneficios.

Walter Vehoeven
fuente
2
1. Ese es un ejemplo y explicación realmente complejo solo para demostrar funciones locales. 2. Las funciones locales no evitan ninguna asignación cuando se comparan con lambdas en este ejemplo, porque todavía tienen que convertirse en delegados. Así que no veo cómo evitarían GC.
svick
1
sin pasar / copiar variables, la respuesta de svick cubre el resto realmente bien. No es necesario duplicar su respuesta
Walter Vehoeven