Expresiones lambda (Guía de programación de C#)Lambda expressions (C# Programming Guide)

Una expresión lambda es una expresión que tiene cualquiera de estas dos formas:A lambda expression is an expression of any of the following two forms:

Use el operador de declaración lambda => para separar la lista de parámetros de la lamba de su cuerpo.Use the lambda declaration operator => to separate the lambda's parameter list from its body. Para crear una expresión lambda, especifique los parámetros de entrada (si existen) a la izquierda del operador lambda y una expresión o bloque de instrucciones en el otro lado.To create a lambda expression, you specify input parameters (if any) on the left side of the lambda operator and an expression or a statement block on the other side.

Toda expresión lambda se puede convertir en un tipo delegado.Any lambda expression can be converted to a delegate type. El tipo delegado al que se puede convertir una expresión lambda se define según los tipos de sus parámetros y el valor devuelto.The delegate type to which a lambda expression can be converted is defined by the types of its parameters and return value. Si una expresión lambda no devuelve un valor, se puede convertir en uno de los tipos delegados Action; de lo contrario, se puede convertir en uno de los tipos delegados Func.If a lambda expression doesn't return a value, it can be converted to one of the Action delegate types; otherwise, it can be converted to one of the Func delegate types. Por ejemplo, una expresión lambda que tiene dos parámetros y no devuelve ningún valor corresponde a un delegado Action<T1,T2>.For example, a lambda expression that has two parameters and returns no value can be converted to an Action<T1,T2> delegate. Una expresión lambda que tiene un parámetro y devuelve un valor se puede convertir en un delegado Func<T,TResult>.A lambda expression that has one parameter and returns a value can be converted to a Func<T,TResult> delegate. En el ejemplo siguiente, la expresión lambda x => x * x, que especifica un parámetro denominado x y devuelve el valor de x al cuadrado, se asigna a una variable de un tipo delegado:In the following example, the lambda expression x => x * x, which specifies a parameter that’s named x and returns the value of x squared, is assigned to a variable of a delegate type:

Func<int, int> square = x => x * x;
Console.WriteLine(square(5));
// Output:
// 25

Las expresiones lambda también se pueden convertir en los tipos de árbol de expresión, como se muestra en los ejemplos siguientes:Expression lambdas also can be converted to the expression tree types, as the following example shows:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine(e);
// Output:
// x => (x * x)

Puede usar expresiones lambda en cualquier código que requiera instancias de tipos delegados o de árboles de expresión, por ejemplo, como un argumento del método Task.Run(Action) para pasar el código que se debe ejecutar en segundo plano.You can use lambda expressions in any code that requires instances of delegate types or expression trees, for example as an argument to the Task.Run(Action) method to pass the code that should be executed in the background. También puede usar expresiones lambda al escribir LINQ en C#, como se muestra en el ejemplo siguiente:You also can use lambda expressions when you write LINQ in C#, as the following example shows:

int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25

Cuando se usa la sintaxis de método para llamar al método Enumerable.Select en la clase System.Linq.Enumerable, por ejemplo en LINQ to Objects y en LINQ to XML, el parámetro es un tipo delegado System.Func<T,TResult>.When you use method-based syntax to call the Enumerable.Select method in the System.Linq.Enumerable class, for example in LINQ to Objects and LINQ to XML, the parameter is a delegate type System.Func<T,TResult>. Cuando se llama al método Queryable.Select en la clase System.Linq.Queryable, por ejemplo en LINQ to SQL, el tipo de parámetro es un tipo de árbol de expresión Expression<Func<TSource,TResult>>.When you call the Queryable.Select method in the System.Linq.Queryable class, for example in LINQ to SQL, the parameter type is an expression tree type Expression<Func<TSource,TResult>>. En ambos casos, se puede usar la misma expresión lambda para especificar el valor del parámetro.In both cases you can use the same lambda expression to specify the parameter value. Esto hace que las dos llamadas Select tengan un aspecto similar aunque, de hecho, el tipo de objetos creados a partir las lambdas es distinto.That makes the two Select calls to look similar although in fact the type of objects created from the lambdas is different.

Lambdas de expresiónExpression lambdas

Una expresión lambda con una expresión en el lado derecho del operador => se denomina lambda de expresión.A lambda expression with an expression on the right side of the => operator is called an expression lambda. Las lambdas de expresión se usan ampliamente en la construcción de árboles de expresión.Expression lambdas are used extensively in the construction of expression trees. Una expresión lambda devuelve el resultado de evaluar la condición y tiene la siguiente forma:An expression lambda returns the result of the expression and takes the following basic form:

(input-parameters) => expression

Los paréntesis solo son opcionales si la lambda tiene un parámetro de entrada; de lo contrario, son obligatorios.The parentheses are optional only if the lambda has one input parameter; otherwise they are required.

Para especificar cero parámetros de entrada, utilice paréntesis vacíos:Specify zero input parameters with empty parentheses:

Action line = () => Console.WriteLine();

Dos o más parámetros de entrada se separan por comas y se encierran entre paréntesis:Two or more input parameters are separated by commas enclosed in parentheses:

Func<int, int, bool> testForEquality = (x, y) => x == y;

A veces resulta imposible que el compilador deduzca los tipos de entrada.Sometimes it's impossible for the compiler to infer the input types. Puede especificar los tipos de manera explícita, tal como se muestra en el ejemplo siguiente:You can specify the types explicitly as shown in the following example:

Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;

Los tipos de parámetro de entrada deben ser todos explícitos o todos implícitos; de lo contrario, se produce un error del compilador CS0748.Input parameter types must be all explicit or all implicit; otherwise, a CS0748 compiler error occurs.

El cuerpo de una expresión lambda puede constar de una llamada al método.The body of an expression lambda can consist of a method call. Sin embargo, si crea árboles de expresión que se evalúan fuera del contexto de Common Language Runtime de .NET, como en SQL Server, no debe utilizar llamadas a métodos en expresiones lambda.However, if you are creating expression trees that are evaluated outside the context of the .NET common language runtime, such as in SQL Server, you should not use method calls in lambda expressions. Los métodos no tendrán ningún significado fuera del contexto de .NET Common Language Runtime.The methods will have no meaning outside the context of the .NET common language runtime.

Lambdas de instrucciónStatement lambdas

Una lambda de instrucción es similar a un lambda de expresión, salvo que las instrucciones se encierran entre llaves:A statement lambda resembles an expression lambda except that the statement(s) is enclosed in braces:

(input-parameters) => { <sequence-of-statements> }

El cuerpo de una lambda de instrucción puede estar compuesto de cualquier número de instrucciones; sin embargo, en la práctica, generalmente no hay más de dos o tres.The body of a statement lambda can consist of any number of statements; however, in practice there are typically no more than two or three.

Action<string> greet = name => 
{ 
    string greeting = $"Hello {name}!";
    Console.WriteLine(greeting);
};
greet("World");
// Output:
// Hello World!

Las expresiones lambdas no se pueden usar para crear árboles de expresión.Statement lambdas cannot be used to create expression trees.

Lambdas asincrónicasAsync lambdas

Puede crear fácilmente expresiones e instrucciones lambda que incorporen el procesamiento asincrónico mediante las palabras clave async y await .You can easily create lambda expressions and statements that incorporate asynchronous processing by using the async and await keywords. Por ejemplo, en el siguiente ejemplo de formularios Windows Forms se incluye un controlador de eventos que llama y espera un método asincrónico, ExampleMethodAsync.For example, the following Windows Forms example contains an event handler that calls and awaits an async method, ExampleMethodAsync.

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += button1_Click;
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\n";
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Puede agregar el mismo controlador de eventos utilizando una lambda asincrónica.You can add the same event handler by using an async lambda. Para agregar este controlador, agregue un modificador async antes de la lista de parámetros lambda, como se muestra en el ejemplo siguiente:To add this handler, add an async modifier before the lambda parameter list, as the following example shows:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\n";
        };
    }

    private async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

Para obtener más información sobre cómo crear y usar métodos asincrónicos, vea Programación asincrónica con async y await.For more information about how to create and use async methods, see Asynchronous Programming with async and await.

Expresiones lambda y tuplasLambda expressions and tuples

A partir de C# 7.0, el lenguaje C# proporciona compatibilidad integrada para las tuplas.Starting with C# 7.0, the C# language provides built-in support for tuples. Puede proporcionar una tupla como argumento a una expresión lambda, mientras que la expresión lambda también puede devolver una tupla.You can provide a tuple as an argument to a lambda expression, and your lambda expression can also return a tuple. En algunos casos, el compilador de C# usa la inferencia de tipos para determinar los tipos de componentes de la tupla.In some cases, the C# compiler uses type inference to determine the types of tuple components.

Para definir una tupla, incluya entre paréntesis una lista delimitada por comas de los componentes.You define a tuple by enclosing a comma-delimited list of its components in parentheses. En el ejemplo siguiente se usa la tupla con tres componentes para pasar una secuencia de números a una expresión lambda, que duplica cada valor y devuelve una tupla con tres componentes que contiene el resultado de las multiplicaciones.The following example uses tuple with three components to pass a sequence of numbers to a lambda expression, which doubles each value and returns a tuple with three components that contains the result of the multiplications.

Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)

Normalmente, los campos de una tupla se denominan Item1, Item2, etc., aunque puede definir una tupla con componentes con nombre, como en el ejemplo siguiente.Ordinarily, the fields of a tuple are named Item1, Item2, etc. You can, however, define a tuple with named components, as the following example does.

Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");

Para más información sobre las tuplas de C#, consulte el artículo sobre los tipos de tuplas de C#.For more information about C# tuples, see C# tuple types.

Lambdas con los operadores de consulta estándarLambdas with the standard query operators

LINQ to Objects, entre otras implementaciones, tiene un parámetro de entrada cuyo tipo es uno de la familia Func<TResult> de delegados genéricos.LINQ to Objects, among other implementations, have an input parameter whose type is one of the Func<TResult> family of generic delegates. Estos delegados usan parámetros de tipo para definir el número y el tipo de los parámetros de entrada, así como el tipo de valor devuelto del delegado.These delegates use type parameters to define the number and type of input parameters, and the return type of the delegate. Los delegadosFunc son muy útiles para encapsular expresiones definidas por el usuario que se aplican a cada elemento en un conjunto de datos de origen.Func delegates are very useful for encapsulating user-defined expressions that are applied to each element in a set of source data. Por ejemplo, considere el tipo delegado Func<T,TResult>:For example, consider the Func<T,TResult> delegate type:

public delegate TResult Func<in T, out TResult>(T arg)

Se pueden crear instancias del delegado como una instancia Func<int, bool>, donde int es un parámetro de entrada y bool es el valor devuelto.The delegate can be instantiated as a Func<int, bool> instance where int is an input parameter and bool is the return value. El valor devuelto siempre se especifica en el último parámetro de tipo.The return value is always specified in the last type parameter. Por ejemplo, Func<int, string, bool> define un delegado con dos parámetros de entrada, int y string, y un tipo de valor devuelto de bool.For example, Func<int, string, bool> defines a delegate with two input parameters, int and string, and a return type of bool. El delegado Func siguiente, cuando se invoca, devuelve un valor booleano que indica si el parámetro de entrada es igual a cinco:The following Func delegate, when it's invoked, returns Boolean value that indicates whether the input parameter is equal to five:

Func<int, bool> equalsFive = x => x == 5;
bool result = equalsFive(4);
Console.WriteLine(result);   // False

También puede proporcionar una expresión lambda cuando el tipo de argumento es Expression<TDelegate>, (por ejemplo, en los operadores de consulta estándar que se definen en el tipo Queryable).You can also supply a lambda expression when the argument type is an Expression<TDelegate>, for example in the standard query operators that are defined in the Queryable type. Al especificar un argumento Expression<TDelegate>, la lambda se compila en un árbol de expresión.When you specify an Expression<TDelegate> argument, the lambda is compiled to an expression tree.

En el ejemplo siguiente se usa el operador de consulta estándar Count:The following example uses the Count standard query operator:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");

El compilador puede deducir el tipo del parámetro de entrada o también se puede especificar explícitamente.The compiler can infer the type of the input parameter, or you can also specify it explicitly. Esta expresión lambda particular cuenta aquellos enteros (n) que divididos por dos dan como resto 1.This particular lambda expression counts those integers (n) which when divided by two have a remainder of 1.

El siguiente ejemplo genera una secuencia que contiene todos los elementos de la matriz numbers que preceden al 9, ya que ese es el primer número de la secuencia que no cumple la condición:The following example produces a sequence that contains all elements in the numbers array that precede the 9, because that's the first number in the sequence that doesn't meet the condition:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3

En el siguiente ejemplo se especifican varios parámetros de entrada encerrándolos entre paréntesis.The following example specifies multiple input parameters by enclosing them in parentheses. El método devuelve todos los elementos de la matriz numbers hasta que encuentra un número cuyo valor es menor que la posición ordinal en la matriz:The method returns all the elements in the numbers array until it encounters a number whose value is less than its ordinal position in the array:

int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4

Inferencia de tipos en expresiones lambdaType inference in lambda expressions

Al escribir lambdas, no tiene por qué especificar un tipo para los parámetros de entrada, ya que el compilador puede deducir el tipo según el cuerpo de lambda, los tipos de parámetros y otros factores, tal como se describe en la especificación del lenguaje C#.When writing lambdas, you often don't have to specify a type for the input parameters because the compiler can infer the type based on the lambda body, the parameter types, and other factors as described in the C# language specification. Para la mayoría de los operadores de consulta estándar, la primera entrada es el tipo de los elementos en la secuencia de origen.For most of the standard query operators, the first input is the type of the elements in the source sequence. Si está realizando una consulta sobre IEnumerable<Customer>, se deducirá que la variable de entrada será un objeto Customer, lo cual significa que se podrá tener acceso a sus métodos y propiedades:If you are querying an IEnumerable<Customer>, then the input variable is inferred to be a Customer object, which means you have access to its methods and properties:

customers.Where(c => c.City == "London");

Las reglas generales para la inferencia de tipos de las lambdas son las siguientes:The general rules for type inference for lambdas are as follows:

  • La lambda debe contener el mismo número de parámetros que el tipo delegado.The lambda must contain the same number of parameters as the delegate type.

  • Cada parámetro de entrada de la lambda debe poder convertirse implícitamente a su parámetro de delegado correspondiente.Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.

  • El valor devuelto de la lambda (si existe) debe poder convertirse implícitamente al tipo de valor devuelto del delegado.The return value of the lambda (if any) must be implicitly convertible to the delegate's return type.

Observe que las expresiones lambda, en sí mismas, no tienen tipo, ya que el sistema de tipos comunes no tiene ningún concepto intrínseco de "expresión lambda".Note that lambda expressions in themselves don't have a type because the common type system has no intrinsic concept of "lambda expression." Sin embargo, a veces resulta práctico hablar coloquialmente del "tipo" de una expresión lambda.However, it's sometimes convenient to speak informally of the "type" of a lambda expression. En estos casos, el tipo hace referencia al tipo del delegado o el tipo de Expression en el que se convierte la expresión lambda.In these cases the type refers to the delegate type or Expression type to which the lambda expression is converted.

Captura de variables externas y el ámbito de las variables en las expresiones lambdaCapture of outer variables and variable scope in lambda expressions

Las operaciones lambda pueden hacer referencia a variables externas.Lambdas can refer to outer variables. Estas son las variables que están en el ámbito del método que define la expresión lambda o en el ámbito del tipo que contiene la expresión lambda.These are the variables that are in scope in the method that defines the lambda expression, or in scope in the type that contains the lambda expression. Las variables que se capturan de esta manera se almacenan para su uso en la expresión lambda, cuando de otro modo quedarían fuera de ámbito y serían eliminadas por la recolección de elementos no utilizados.Variables that are captured in this manner are stored for use in the lambda expression even if the variables would otherwise go out of scope and be garbage collected. Para poder utilizar una variable externa en una expresión lambda, debe estar previamente asignada.An outer variable must be definitely assigned before it can be consumed in a lambda expression. En el ejemplo siguiente se muestran estas reglas:The following example demonstrates these rules:

public static class VariableScopeWithLambdas
{
    public class VariableCaptureGame
    {
        internal Action<int> updateCapturedLocalVariable;
        internal Func<int, bool> isEqualToCapturedLocalVariable;

        public void Run(int input)
        {
            int j = 0;

            updateCapturedLocalVariable = x =>
            {
                j = x;
                bool result = j > input;
                Console.WriteLine($"{j} is greater than {input}: {result}");
            };

            isEqualToCapturedLocalVariable = x => x == j;

            Console.WriteLine($"Local variable before lambda invocation: {j}");
            updateCapturedLocalVariable(10);
            Console.WriteLine($"Local variable after lambda invocation: {j}");
        }
    }

    public static void Main()
    {  
        var game = new VariableCaptureGame();
        
        int gameInput = 5;
        game.Run(gameInput);

        int jTry = 10;
        bool result = game.isEqualToCapturedLocalVariable(jTry);
        Console.WriteLine($"Captured local variable is equal to {jTry}: {result}");

        int anotherJ = 3;
        game.updateCapturedLocalVariable(anotherJ);

        bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ);
        Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}");
    }
    // Output:
    // Local variable before lambda invocation: 0
    // 10 is greater than 5: True
    // Local variable after lambda invocation: 10
    // Captured local variable is equal to 10: True
    // 3 is greater than 5: False
    // Another lambda observes a new value of captured variable: True
}

Las reglas siguientes se aplican al ámbito de las variables en las expresiones lambda:The following rules apply to variable scope in lambda expressions:

  • Una variable capturada no se recolectará como elemento no utilizado hasta que el delegado que hace referencia a ella sea elegible para la recolección de elementos no utilizados.A variable that is captured will not be garbage-collected until the delegate that references it becomes eligible for garbage collection.

  • Las variables introducidas en una expresión lambda no son visibles en el método envolvente.Variables introduced within a lambda expression are not visible in the enclosing method.

  • Una expresión lambda no puede capturar directamente un parámetro in, ref ni out desde el método envolvente.A lambda expression cannot directly capture an in, ref, or out parameter from the enclosing method.

  • Una instrucción return en una expresión lambda no hace que el método envolvente devuelva un valor.A return statement in a lambda expression doesn't cause the enclosing method to return.

  • Una expresión lambda no puede contener una instrucción goto, break ni continue si el destino de esa instrucción de salto está fuera del bloque de la expresión lambda.A lambda expression cannot contain a goto, break, or continue statement if the target of that jump statement is outside the lambda expression block. También es un error utilizar una instrucción de salto fuera del bloque de la expresión lambda si el destino está dentro del bloque.It's also an error to have a jump statement outside the lambda expression block if the target is inside the block.

Especificación del lenguaje C#C# language specification

Para obtener más información, vea la sección Expresiones de función anónima de la Especificación del lenguaje C#.For more information, see the Anonymous function expressions section of the C# language specification.

Delegates, Events, and Lambda Expressions (Delegados, eventos y expresiones lambda) en C# 3.0 Cookbook, Tercera edición: More than 250 solutions for C# 3.0 programmers (Más de 250 soluciones para programadores de C# 3.0)Delegates, Events, and Lambda Expressions in C# 3.0 Cookbook, Third Edition: More than 250 solutions for C# 3.0 programmers

Vea tambiénSee also