Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

19 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 17 de febrero de 2026
Una lambda petrificada

No es algo que se utilice habitualmente, y a veces incluso es un detalle desconocido por los desarrolladores, pero las funciones lambda de C# pueden ser estáticas. Y no es porque sea algo nuevo, pues esta característica se introdujo en la versión 9.0 del lenguaje en 2020... simplemente son esos pequeños detalles que se van añadiendo al lenguaje sin hacer mucho ruido, y que a veces pasan desapercibidos.

De hecho, el uso de lambdas estáticas puede ayudarnos a escribir un código más seguro y predecible, además de tener cierto impacto en el rendimiento de nuestras aplicaciones, por lo que merece la pena conocerlas y saber cuándo y cómo usarlas.

En este post vamos a ver qué son las lambdas estáticas, cómo se implementan y cuándo y por qué deberíamos usarlas.

Lambdas estáticas

Al igual que ocurre con los métodos o funciones en C#, las funciones lambda pueden ser estáticas, es decir, pueden vivir fuera del contexto de una instancia de clase, y se implementan simplemente añadiendo la palabra clave static delante de la definición de la lambda:

var suma = static (x, y) => x + y;

Esto mismo podemos verlo en escenarios más complejos, como en consultas LINQ sobre colecciones u orígenes de datos, ya sea usándolas como delegados o bien como árboles de expresión:

int[] nums = [1, 2, 3, 4, 5];
var sum = nums.Where(static x => x % 2 == 0).Sum(); // Suma los pares
Console.WriteLine(sum); // Imprime 6

En cualquier caso, la declaración de una lambda como estática hace que aparezcan ciertas restricciones en su implementación. En primer lugar, las lambdas estáticas no pueden capturar variables de instancia, pues no tienen acceso a this ni a las variables o métodos de instancia de la clase en la que se definen.

Tampoco pueden capturar variables locales ni parámetros del método que las contiene, a menos que se pasen explícitamente como argumentos.

En otras palabras, deben ser funciones puras, que dependen únicamente de sus parámetros de entrada.

Esto, que a priori puede parecer una limitación, en realidad es una de las principales ventajas de las lambdas estáticas, ya que, al no depender de ningún factor externo, su comportamiento será más predecible y fácil de implementar y depurar.

Observa por ejemplo el siguiente caso, en el que la lambda duplicate captura la variable factor del contexto que la contiene, y hace que su valor dependa de ella:

int factor = 2;
var duplicate = (int x) => x * factor; // No captura el valor de 'factor', 
                                       // sino la referencia a la variable

Console.WriteLine(duplicate(2)); // Imprime 4
factor = 3;
Console.WriteLine(duplicate(2)); // Imprime 6

Fíjate que aunque llamamos a la lambda duplicate con el mismo parámetro, el resultado es diferente porque la variable factor ha cambiado su valor en el segundo caso.

Esto puede provocar errores difíciles de detectar y depurar. De hecho, el entorno de desarrollo (IDE) nos avisa de que la lambda está capturando una variable del contexto cuyo valor es modificado, por lo que puede provocar efectos inesperados:

Captura de variable en lambda

Al convertir la lambda en estática, el compilador nos avisará de que no puede capturar variables del contexto, por lo que nos veremos obligados a usar constantes o bien a pasar los valores como parámetros; en cualquiera de los dos casos, la lambda será siempre una función pura y su comportamiento será totalmente predecible:

int factor = 2;
var duplicate = static (int x) => x * factor; // Error CS8820: A static anonymous function
                                              // cannot contain a reference to 'factor'.

var duplicate = static (int x) => x * 2; // Correcto, factor es un valor constante
var multiply = static (int x, int factor) => x * factor; // Correcto, factor se pasa como parámetro

Pero además, es importante saber que, cuando se invoca una lambda que captura alguna referencia externa, el compilador genera un objeto de cierre (o closure) para almacenar esas referencias, lo que implica una sobrecarga adicional en tiempo de ejecución y un mayor consumo de memoria.

Por tanto, cuando usemos lambdas estáticas, además de tener un código más seguro y determinista, podemos estar seguros de que no se generará ningún objeto de cierre, por lo que el rendimiento y consumo de memoria serán óptimos.

Benchmarks

El impacto del uso de objetos de cierre en el rendimiento y consumo de memoria puede ser significativo en escenarios donde se usan muchas lambdas que capturan referencias, como en bucles o en operaciones con colecciones. Esto puede verse en el siguiente resultado de un benchmark, donde comparamos cuatro escenarios diferentes:

  • una lambda normal sin capturas,
  • otra lambda normal que captura una propiedad de instancia,
  • una tercera que captura una variable local,
  • y una lambda estática sin capturas.
| Method                       | Mean      | Error     | StdDev    | Median    | Gen0   | Allocated |
|----------------------------- |----------:|----------:|----------:|----------:|-------:|----------:|
| NormalLambdaWithNoCapture    | 0.0031 ns | 0.0033 ns | 0.0027 ns | 0.0023 ns |      - |         - |
| NormalLambdaWithCapture      | 4.8063 ns | 0.0654 ns | 0.0612 ns | 4.8035 ns | 0.0077 |      64 B |
| NormalLambdaWithLocalCapture | 7.0980 ns | 0.1036 ns | 0.0969 ns | 7.0805 ns | 0.0105 |      88 B |
| StaticLambda                 | 0.0039 ns | 0.0035 ns | 0.0033 ns | 0.0046 ns |      - |         - |

Lo primero que llama la atención es que las lambdas normales que capturan referencias (ya sean de instancia o locales) son muchísimo más lentas que las que no capturan nada, y además generan consumo de memoria (reservan memoria en el heap y provocan recolecciones de basura).

Las lambdas que no capturan referencias prácticamente consiguen el mismo rendimiento, con una diferencia inapreciable. Y, eso sí, ninguna de las dos genera sobrecarga ni consumo de memoria.

Entonces, ¿cuándo deberíamos usar lambdas estáticas?

En general, salvo que necesitemos capturar referencias del contexto, será conveniente utilizar lambdas estáticas siempre, ya que nos aseguramos de que nuestro código es más seguro y predecible, sin perder en eficiencia o consumo de memoria.

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!