tipos metodos funciones expresiones español ejemplos delegados anonimos anonimas c# lambda delegates anonymous-methods

funciones - metodos anonimos c#



Métodos anónimos contra la expresión lambda (3)

¿Alguien puede proporcionar una distinción concisa entre el método anónimo y las expresiones lambda?

Uso del método anónimo:

private void DoSomeWork() { if (textBox1.InvokeRequired) { //textBox1.Invoke((Action)(() => textBox1.Text = "test")); textBox1.Invoke((Action)delegate { textBox1.Text = "test"; }); } }

¿Es solo una expresión lambda normal lanzada a un delegado fuertemente tipado o hay más de encubierto?

Estoy muy consciente de que un delegado fuertemente tipado como seguir

UpdateTextDelegate mydelegate = new UpdateTextDelegate(MethodName)

es suficiente como un parámetro del tipo System.Delegate , pero la idea del método anónimo es bastante nueva para mí.


¿Qué es un método anónimo? ¿Es realmente anónimo? Eso tiene un nombre? Todas las buenas preguntas, así que comencemos con ellas y avancemos hasta expresiones lambda a medida que avanzamos.

Cuando haces esto:

public void TestSomething() { Test(delegate { Debug.WriteLine("Test"); }); }

¿Qué sucede realmente?

El compilador primero decide tomar el "cuerpo" del método, que es el siguiente:

Debug.WriteLine("Test");

y separa eso en un método.

Dos preguntas que el compilador ahora tiene que responder:

  1. ¿Dónde debería poner el método?
  2. ¿Cómo debería ser la firma del método?

La segunda pregunta es fácil de responder. El delegate { parte contesta eso. El método no toma parámetros (nada entre el delegate y { ), y como no nos importa su nombre (de ahí la parte "anónima"), podemos declarar el método como tal:

public void SomeOddMethod() { Debug.WriteLine("Test"); }

Pero, ¿por qué hizo todo esto?

Veamos qué es un delegado, como realmente es Action .

Un delegado es, si por un momento hacemos caso omiso del hecho de que los delegados en .NET son en realidad una lista vinculada de múltiples "delegados" individuales, una referencia (puntero) a dos cosas:

  1. Una instancia de objeto
  2. Un método en esa instancia de objeto

Entonces, con ese conocimiento, la primera parte del código podría reescribirse así:

public void TestSomething() { Test(new Action(this.SomeOddMethod)); } private void SomeOddMethod() { Debug.WriteLine("Test"); }

Ahora, el problema con esto es que el compilador no tiene forma de saber qué es lo que Test realmente hace con el delegado que se le da, y dado que la mitad del delegado es una referencia a la instancia en la que se llamará el método, this en el ejemplo anterior, no sabemos a cuántos datos se hará referencia.

Por ejemplo, considere si el código anterior era parte de un objeto realmente grande, pero un objeto que solo vive temporalmente. También tenga en cuenta que Test almacenaría ese delegado en algún lugar donde viviría durante mucho tiempo. Ese "largo tiempo" se vincularía a la vida de ese objeto enorme también, manteniendo una referencia a eso durante mucho tiempo también, probablemente no sea bueno.

Entonces el compilador hace más que solo crear un método, también crea una clase para mantenerlo. Esto responde a la primera pregunta, ¿ dónde debería ponerlo? .

El código anterior se puede reescribir de la siguiente manera:

public void TestSomething() { var temp = new SomeClass; Test(new Action(temp.SomeOddMethod)); } private class SomeClass { private void SomeOddMethod() { Debug.WriteLine("Test"); } }

Es decir, para este ejemplo, de qué se trata realmente un método anónimo.

Las cosas se ponen un poco más peludas si comienzas a usar variables locales, considera este ejemplo:

public void Test() { int x = 10; Test(delegate { Debug.WriteLine("x=" + x); }); }

Esto es lo que ocurre debajo del capó, o al menos algo muy cercano:

public void TestSomething() { var temp = new SomeClass; temp.x = 10; Test(new Action(temp.SomeOddMethod)); } private class SomeClass { public int x; private void SomeOddMethod() { Debug.WriteLine("x=" + x); } }

El compilador crea una clase, levanta todas las variables que el método requiere en esa clase, y reescribe todo el acceso a las variables locales para acceder a los campos en el tipo anónimo.

El nombre de la clase y el método son un poco extraños, preguntemos a LINQPad cuál sería:

void Main() { int x = 10; Test(delegate { Debug.WriteLine("x=" + x); }); } public void Test(Action action) { action(); }

Si le pido a LINQPad que muestre el IL (Idioma Intermedio) de este programa, obtengo esto:

// var temp = new UserQuery+<>c__DisplayClass1(); IL_0000: newobj UserQuery+<>c__DisplayClass1..ctor IL_0005: stloc.0 // CS$<>8__locals2 IL_0006: ldloc.0 // CS$<>8__locals2 // temp.x = 10; IL_0007: ldc.i4.s 0A IL_0009: stfld UserQuery+<>c__DisplayClass1.x // var action = new Action(temp.<Main>b__0); IL_000E: ldarg.0 IL_000F: ldloc.0 // CS$<>8__locals2 IL_0010: ldftn UserQuery+<>c__DisplayClass1.<Main>b__0 IL_0016: newobj System.Action..ctor // Test(action); IL_001B: call UserQuery.Test Test: IL_0000: ldarg.1 IL_0001: callvirt System.Action.Invoke IL_0006: ret <>c__DisplayClass1.<Main>b__0: IL_0000: ldstr "x=" IL_0005: ldarg.0 IL_0006: ldfld UserQuery+<>c__DisplayClass1.x IL_000B: box System.Int32 IL_0010: call System.String.Concat IL_0015: call System.Diagnostics.Debug.WriteLine IL_001A: ret <>c__DisplayClass1..ctor: IL_0000: ldarg.0 IL_0001: call System.Object..ctor IL_0006: ret

Aquí puede ver que el nombre de la clase es UserQuery+<>c__DisplayClass1 , y el nombre del método es <Main>b__0 . Edité en el código C # que produjo este código, LINQPad no produce nada más que el IL en el ejemplo anterior.

Los signos de menor que y mayor que están allí para garantizar que no se pueda crear accidentalmente un tipo y / o método que coincida con lo que el compilador produjo para usted.

Entonces eso es básicamente lo que es un método anónimo.

¿Entonces qué es esto?

Test(() => Debug.WriteLine("Test"));

Bueno, en este caso es lo mismo, es un atajo para producir un método anónimo.

Puedes escribir esto de dos maneras:

() => { ... code here ... } () => ... single expression here ...

En su primera forma, puede escribir todo el código que haría en un cuerpo de método normal. En su segunda forma, puedes escribir una expresión o declaración.

Sin embargo , en este caso el compilador tratará esto:

() => ...

de la misma manera que esto:

delegate { ... }

Siguen siendo métodos anónimos, es solo que la sintaxis () => es un atajo para llegar a ella.

Entonces, si es un atajo para llegar a él, ¿por qué lo tenemos?

Bueno, hace la vida un poco más fácil para el propósito del cual se agregó, que es LINQ.

Considere esta declaración LINQ:

var customers = from customer in db.Customers where customer.Name == "ACME" select customer.Address;

Este código se reescribe de la siguiente manera:

var customers = db.Customers .Where(customer => customer.Name == "ACME") .Select(customer => customer.Address");

Si delegate { ... } sintaxis delegate { ... } , tendrías que volver a escribir las expresiones con return ... y así sucesivamente, y se verían más funky. La sintaxis lambda se agregó para facilitar la vida de los programadores de nosotros al escribir código como el anterior.

Entonces, ¿qué son expresiones?

Hasta ahora no he mostrado cómo se ha definido Test , pero definamos Test para el código anterior:

public void Test(Action action)

Esto debería ser suficiente. Dice que "Necesito un delegado, es de tipo Acción (sin tomar parámetros, sin devolver ningún valor)".

Sin embargo, Microsoft también agregó una forma diferente de definir este método:

public void Test(Expression<Func<....>> expr)

Tenga en cuenta que dejé una parte allí, la .... parte, volvamos a eso 1 .

Este código, junto con esta llamada:

Test(() => x + 10);

en realidad no pasará en un delegado, ni nada que pueda llamarse (inmediatamente). En cambio, el compilador reescribirá este código en un código similar (pero no del todo similar):

var operand1 = new VariableReferenceOperand("x"); var operand2 = new ConstantOperand(10); var expression = new AdditionOperator(operand1, operand2); Test(expression);

Básicamente, el compilador construirá un objeto Expression<Func<...>> , que contiene referencias a las variables, los valores literales, los operadores utilizados, etc. y pasa ese árbol de objetos al método.

¿Por qué?

Bueno, considere la parte db.Customers.Where(...) arriba.

¿No sería agradable si, en lugar de descargar todos los clientes (y todos sus datos) de la base de datos al cliente, revisándolos a todos, descubriendo qué cliente tiene el nombre correcto, etc. el código realmente le pidiera a la base de datos que encontrar ese cliente único, correcto, a la vez?

Ese es el propósito de la expresión. El Entity Framework, Linq2SQL o cualquier otra capa de base de datos que soporte LINQ tomará esa expresión, la analizará, la separará y escribirá un SQL con el formato correcto para ejecutarse en la base de datos.

Esto nunca podría suceder si aún le diéramos delegados a los métodos que contienen IL. Solo puede hacer esto debido a un par de cosas:

  1. La sintaxis permitida en una expresión lambda adecuada para una Expression<Func<...>> es limitada (sin enunciados, etc.)
  2. La sintaxis lambda sin los corchetes, que le dice al compilador que esta es una forma más simple de código

Entonces, resumiremos:

  1. Los métodos anónimos en realidad no son tan anónimos, terminan como un tipo con nombre, con un método nombrado, solo que no tienes que nombrar esas cosas tú mismo
  2. Es mucha magia de compilación debajo del capó que mueve las cosas para que no tengas que
  3. Las expresiones y los delegados son dos formas de ver algunas de las mismas cosas
  4. Las expresiones son para marcos que quieren saber qué hace y cómo funciona el código , de modo que puedan usar ese conocimiento para optimizar el proceso (como escribir una declaración SQL)
  5. Los delegados están destinados a marcos que solo están preocupados por poder llamar al método

Notas al pie:

  1. La .... parte de una expresión tan simple se refiere al tipo de valor de retorno que obtienes de la expresión. La () => ... simple expression ... solo permite expresiones , es decir, algo que devuelve un valor, y no puede ser enunciados múltiples. Como tal, un tipo de expresión válido es este: Expression<Func<int>> , básicamente, la expresión es una función (método) que devuelve un valor entero.

    Tenga en cuenta que la "expresión que devuelve un valor" es un límite para los parámetros o tipos de Expression<...> , pero no de delegados. Este es un código completamente legal si el tipo de parámetro de Test es una Action :

    Test(() => Debug.WriteLine("Test"));

    Obviamente, Debug.WriteLine("Test") no devuelve nada, pero esto es legal. Sin embargo, si el método Test requiere una expresión , no lo sería, ya que una expresión debe devolver un valor.


Hay una diferencia sutil que debes tener en cuenta. Considere las siguientes consultas (usando el proverbial NorthWind).

Customers.Where(delegate(Customers c) { return c.City == "London";}); Customers.Where(c => c.City == "London");

El primero usa un delegado anónimo y el segundo usa una expresión lambda. Si evalúa los resultados de ambos, verá lo mismo. Sin embargo, mirando el SQL generado, veremos una historia bastante diferente. El primero genera

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [Customers] AS [t0]

Mientras que el segundo genera

SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [Customers] AS [t0] WHERE [t0].[City] = @p0

Aviso en el primer caso, la cláusula where no se pasa a la base de datos. ¿Por qué es esto? El compilador puede determinar que la expresión lambda es una expresión sencilla de línea única que puede conservarse como un árbol de expresiones, mientras que el delegado anónimo no es una expresión lambda y, por lo tanto, no se puede envolver como una Expression<Func<T>> . Como resultado, en el primer caso, la mejor coincidencia para el método de extensión Where es la que extiende IEnumerable en lugar de la versión IQueryable que requiere una Expression<Func<T, bool>> .

En este punto, hay poco uso para el delegado anónimo. Es más detallado y menos flexible. En general, recomendaría siempre usar la sintaxis lambda sobre la sintaxis delegada anónima y también recuperar la capacidad de análisis y la sintaxis.


Para ser precisos, lo que usted llama ''delegado anónimo'' es en realidad un método anónimo.

Bueno, tanto lambdas como métodos anónimos son solo azúcar sintáctico. El compilador generará al menos un método "normal" para usted, aunque a veces (en el caso de un cierre) generará una clase anidada con el método que ya no es anónimo.