Lambda Expression
Lambda expressions are anonymous functions.
The lambda operator =>
(“returns”) separates a lambda’s parameter list from its body.
(x) => x * x; // A lambda (anonymous method) with a parameter named x that returns x * x.
Lambdas in Methods
int MyFunc(int x)
{
return x;
}
…is equivalent to…
int MyFunc(int x) => x;
Lambdas in Anonymous Methods
Func<int, int> = delegate (int x) { return x; };
…is equivalent to…
Func<int, int> = x => x;
And…
someVar.Find(delegate(int in)
{
if (n < 10)
return n;
});
…is equivalent to…
someVar.Find(n => n < 10);
Lambda Definition vs. Normal Method Definition
Lambda:
public override string ToString() => $"{firstName} {lastName}".Trim
Normal:
public override string ToString()
{
return $"{firstName} {lastName}".Trim();
}
2 Forms of Lambda Expressions
Expression Lambdas take this form:
(input-parameters) => expression with return-value
Statement Lambdas take this form:
(input-parameters) => { sequence-of-statements }
The body of a statement lambda can have any number of statements (though usually limited to 2 or 3):
Action<string> greet = name =>
{
string greet = $"Hello {name}!");
Console.WriteLine(greeting);
};
greet("World"); // Output: "Hello World!"
Lambdas as Delegates
Any lambda expression can be converted to a delegate type. If the lambda doesn’t return a value, it can convert to one of the Action delegate types.
- A lambda with 2 parameters, no return value, can convert to
Action<T1, T2>
. Otherwise, it can be converted to one of the Func delegate types. - A lambda with 1 parameter and a return value can convert to a
Func<T, TResult>
:
Func<int, int> square = (x) => x * x; // Since this only has one input parameter, () is optional.
Lambdas can be used when code requires instances of delegate types like in Task.Run(Action)
.
Input Parameters of Lambda Expressions
Input parameters are enclosed in parentheses:
Action line = () => Console.WriteLine();
Since this only has one input parameter, ()
is optional:
Func<double, double> cube = (x) => x * x * x;
Two or more input parameters are separated by commas:
Func<int, int, bool> testForEquality = (x, y) => x == y;
If the compiler cannot infer types of input parameters, specify them explicitly:
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
Discards can also be used if the input parameters are not used in the expression:
Func<int, int, int> constant = (_, _) => 42;
ℹ️ Important
Availability: C# 12
Parameters can have default values. The syntax and restrictions are the same as for methods and local functions:
var IncrementBy = (int source, int increment = 1) => source + increment;
Console.WriteLine(IncrementBy(5)); // 6
Console.WriteLine(IncrementBy(5, 2)); // 7
Tuples
Lambdas can contain tuples as a comma-delimited list of components in parentheses:
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); // doubledNumbers is (4, 6, 8)
Lambdas with System.Linq
The standard query operators can target a Named Method:
static bool NamesLongerThanFour(string n) { return n > 4; }
var query1 = names.Where(
new Func<string, bool>(NamesLongerThanFour) // For each string element in names, pass it to this function.
);
Or a Lambda Expression:
var query2 = names.Where(name => name.Length > 4);
With Func
delegates, user-defined expressions can be applied to each element in a collection.
With 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($"{oddNumbers} odd numbers."); // 6.
With the TakeWhile
standard query operator:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine($"{firstNumbersLessThanSix}"); // 5 4 1 3
If querying IEnumberable<Customer>
, the input variable is inferred to be of type Customer which gives you access to Customer’s methods and properties:
customer.Where(c => c.City == "London");
Natural (Inferred) Type of a Lambda Expression
ℹ️ Important
Availability: C# 10
The compiler can sometimes infer the type of a lambda expression.
Here, the type is inferred to be Func<string, int>
:
var parse = (string s) => int.Parse(s);
If there’s not enough information, the compiler cannot infer the parameter type:
var parse = s => int.parse(s); // ERROR
So you must declare the type:
Func<string, int> parse = s => int.Parse(s); // OK
Return Type of a Lambda Expression
ℹ️ Important
Availability: C# 10
Usually, the compiler can infer the return type of a lambda expression. In some cases, it cannot:
var choose = (bool b) => b ? 1 : "two"; // ERROR: Cannot infer return type
In these cases, the return type can be explicitly specified:
var choose = object (bool b) => b ? 1 : "two"; // Func<bool, object>
Note that when explicitly specifying the return type, the input parameters must be parenthesized.
Attributes
ℹ️ Important
Availability: C# 10
Attributes (like ProvidesNullCheck
) can be added to a lambda expression:
Func<string?, int?> parse = [ProvidesNullCheck] (s) => (s is not null) ? int.Parse(s) : null;
They can also be added to a lambda expression’s parameters, like DisallowNull
here:
var concat = ([DisallowNull] string a, [DisallowNull] string b) => a + b;
Finally, they can be added to the lambda expression’s return value, like NotNullIfNotNull
here:
var inc = [return: NotNullIfNotNull(nameof(s))] (int? s) => s.HasValue ? s++ : null;