HomeSearch

C# Lambda Expressions

Use the lambda expression syntax to create function objects. Lambdas have arguments and return values.
Lambda. From a branch a leaf grows. From a trunk a branch grows. A single function could make each leaf, each branch—what makes one makes many. An earthworm crawls among the tree's roots.
A lambda expression describes a pattern. From mathematics, the lambda calculus describes the world in patterns. In C# a lambda is a function that uses clear and short syntax.
An example. A common place to use lambdas is with List. Here we use FindIndex, which receives a Predicate method. We specify this as a lambda expression.List

Left, right: To the left, we have arguments. The "x" is just a name—we can use any valid name. The result is on the right.

Often: We pass lambda expressions as arguments, for sorting or for searching. We use them in queries.

C# program that uses lambda, List using System; using System.Collections.Generic; class Program { static void Main() { List<int> elements = new List<int>() { 10, 20, 31, 40 }; // ... Find index of first odd element. int oddIndex = elements.FindIndex(x => x % 2 != 0); Console.WriteLine(oddIndex); } } Output 2 Lambda details: x The argument name. => Separates argument list from result expression. x % 2 !=0 Returns true if x is not even.
Func. The key part of Func is that it returns a value. It can have zero, or many, arguments. But its invariant is a return value, indicated by the TResult parametric type.Func

Func1: We assign a lambda to func1. This lambda receives an int, and returns 1 plus that int value.

Func2: The lambda here receives an int, and returns 1 plus that int also (just like func1).

Func3: This lambda is another syntax for the same lambda—it has the same result as the previous 2 Func objects.

Func4: We sea more verbose syntax here—we specify the return keyword, and the argument type of the lambda.

Return
C# program that uses lambda, Func using System; class Program { static void Main() { // Use implicitly-typed lambda expression. // ... Assign it to a Func instance. Func<int, int> func1 = x => x + 1; Console.WriteLine("FUNC1: {0}", func1.Invoke(200)); // Use lambda expression with statement body. Func<int, int> func2 = x => { return x + 1; }; Console.WriteLine("FUNC2: {0}", func2.Invoke(200)); // Use formal parameters with expression body. Func<int, int> func3 = (int x) => x + 1; Console.WriteLine("FUNC3: {0}", func3.Invoke(200)); // Use parameters with a statement body. Func<int, int> func4 = (int x) => { return x + 1; }; Console.WriteLine("FUNC4: {0}", func4.Invoke(200)); } } Output FUNC1: 201 FUNC2: 201 FUNC3: 201 FUNC4: 201 Func signatures: Func<TResult> Has one result value, no parameter. Func<T, TResult> Has one result value, one parameter. Func<T1, T2, TResult> Has one result value, two parameters. Func<T1, T2, T3, TResult> ....
Func, 2 arguments. We can use a lambda expression that has 2 arguments. We assign the lambda here to a Func that receives 2 ints, and returns an int value as well.
C# program that uses lambda with 2 arguments, Func using System; class Program { static void Main() { // Use multiple parameters. Func<int, int, int> func = (x, y) => x * y; // ... No need to call Invoke(), just call lambda directly. Console.WriteLine("RESULT: {0}", func(20, 2)); } } Output RESULT: 40
Action. The Action encapsulates a function that receives any number of parameters, but returns no value. It matches a void method. This guy is a solitary character.Action
C# program that uses Action lambda using System; class Program { static void Main() { // Use no parameters in a lambda expression. // ... This is an Action instance. Action value = () => Console.WriteLine("Hi, friend"); value.Invoke(); } } Output Hi, friend
Delegate. The delegate keyword denotes an anonymous function. After this keyword, we use a formal parameter list. We can omit the list if there are no parameters.Delegates

Test1: This delegate (an anonymous function) has 1 argument of type int. It returns an int as well.

Test2: The delegate here has no arguments. It just returns an int. It is assigned to a Func object.

C# program that uses delegate keyword, Func using System; class Program { static void Main() { // Use delegate method expression. Func<int, int> test1 = delegate (int x) { return x + 1; }; // Use delegate expression with no parameter list. Func<int> test2 = delegate { return 1 + 1; }; Console.WriteLine(test1.Invoke(10)); Console.WriteLine(test2.Invoke()); } } Output 11 2
Predicate. Here we use this type with an int parameter. With a lambda expression, we specify that the function returns true if the argument is equal to 5.Predicate

Invoke: In this program, the Invoke method is used to show that the Predicate works as expected.

C# program that uses Predicate using System; class Program { static void Main() { Predicate<int> predicate = value => value == 5; Console.WriteLine(predicate.Invoke(4)); Console.WriteLine(predicate.Invoke(5)); } } Output False True
Anonymous functions. This term includes both delegates and lambda syntaxes. An anonymous function has no name. Perhaps it is running from the law.Anonymous Functions

Overloading: Because it has no name, method overloading is not possible for anonymous functions.

Overload

Note: Many developers regard lambda expressions as a complete improvement over (and replacement for) the delegate syntax.

Lambda array. Suppose we want to call a function in an array based on an index. We can create an array of Funcs and then create each element with a lambda expression.

Here: We create a lookup table of 3 functions, and call them based on an index.

Results: The output shows that the offsets 100,200 and 300 are added in an alternating way to the for-iteration value.

C# program that shows array of lambda Funcs using System; class Program { static void Main() { // Create an array of lambdas. var lookup = new Func<int, int>[] { a => a + 100, a => a + 200, a => a + 300 }; // Call the lambdas in the lookup table and print the results. for (int i = 0; i < 10; i++) { int result = lookup[i % 3](i); Console.WriteLine(result); } } } Output 100 201 302 103 204 305 106 207 308 109
Expression-bodied methods. A method can be specified with lambda expression syntax. We provide a method name, and the method is compiled like a lambda. A "return" statement is implicit.
C# program that uses lambda syntax, method class Program { static int TreeBranches(int branches, int trunks) => (branches * trunks); static void Main() { // Call the expression-bodied method. System.Console.WriteLine(TreeBranches(10, 2)); } } Output 20
Cannot convert lambda error. The right-hand part of a lambda expression must have a conversion to the required result type. Here, Array.Find requires a bool, but we just have an int.

Important: Look at the two parts of the lambda separated by the => syntax. The variable name should be present on both sides.

C# program that shows possible lambda error using System; class Program { static void Main() { var array = new int[] { 10, 20, 30 }; var result = Array.Find(array, element => 20); } } Output Error CS1662 Cannot convert lambda expression to intended delegate type because some of the return types in the block are not implicitly convertible to the delegate return type Error CS0029 Cannot implicitly convert type 'int' to 'bool'
Error, continued. To fix the lambda expression, we must make sure the right-hand part of the lambda is correct. Here is the fix and a possible output of the program.

Tip: We changed the right-hand side of the lambda to have an equality expression, so it now evaluates to a bool correctly.

C# program that shows correct lambda expression using System; class Program { static void Main() { var array = new int[] { 10, 20, 30 }; var result = Array.Find(array, element => element == 20); Console.WriteLine(result); } } Output 20
Performance, delegates. We benchmarked a lambda against an anonymous method, one using the delegate keyword. We used the functions as arguments to the Count() extension.

Result: We found no differences. The lambda expression performed the same as the explicit Func instance.

Thus: Lambda expressions cause no excess performance hit beyond other delegate syntaxes.

C# program that tests delegate, lambda performance using System; using System.Diagnostics; using System.Linq; class Program { const int _max = 10000000; static void Main() { int[] array = { 1 }; Func<int, bool> delegateVersion = delegate (int argument) { return argument == 1; }; // Version 1: use lambda expression for Count. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { if (array.Count(element => element == 1) == 0) { return; } } s1.Stop(); // Version 2: use delegate for Count. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { if (array.Count(delegateVersion) == 0) { return; } } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } } Output 25.85 ns lambda expression 25.72 ns delegate
Lambda optimization, cache. Avoiding repeat work is a key to program optimization. Suppose we want to call a method like Array.Sort with a lambda argument.

And: We may need to reuse the lambda many times. We can store the lambda in a variable like a Comparison, and reuse it.

Here: We call Array.Sort 3 times in each version, but cache it in the second version, so only 1 lambda is used there.

Result: Using a cached lambda in repeat invocations is faster. Consider storing lambdas as static fields or local variables.

C# program that uses lambda cache optimization using System; using System.Diagnostics; class Program { const int _max = 1000000; static void Main() { int[] values = { 0, 10 }; // Version 1: use lambda directly in each Array.Sort call. var s1 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { Array.Sort(values, (a, b) => (b.CompareTo(a))); Array.Sort(values, (a, b) => (b.CompareTo(a))); Array.Sort(values, (a, b) => (b.CompareTo(a))); } s1.Stop(); // Version 2: store lambda as local, then reuse it for each Array.Sort call. var s2 = Stopwatch.StartNew(); for (int i = 0; i < _max; i++) { Comparison<int> temp = (a, b) => (b.CompareTo(a)); Array.Sort(values, temp); Array.Sort(values, temp); Array.Sort(values, temp); } s2.Stop(); Console.WriteLine(((double)(s1.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); Console.WriteLine(((double)(s2.Elapsed.TotalMilliseconds * 1000000) / _max).ToString("0.00 ns")); } } Output 141.58 ns Array.Sort, 3 separate lambdas 111.62 ns Array.Sort, 1 cached lambda
Comparison. This type is specifically used to compare objects. It is useful when calling the List.Sort or Array.Sort methods. It can be used with any object type.Comparison

Performance: Using methods such as List.Sort or Array.Sort (with a Comparison) is often faster than using LINQ to sort on a property.

Events. Like any other method, events can be specified as lambda expressions. With events, many event handlers are called when a certain thing happens. This can simplify some programs.Event
Expressive power. Lambdas advance a language. We can achieve the same thing with regular, non-lambda methods. But they make a language easier to use, more "expressive."

Quote: Higher-order procedures can serve as powerful abstraction mechanisms, vastly increasing the expressive power of our language (Structure and Interpretation of Computer Programs).

Specification. The C# language specification describes anonymous function types. The annotated edition of The C# Programming Language (3rd Edition) covers all syntaxes available.

Tip: We can find more detail on this topic using the precise technical terminology on page 314 of this book.

Boring: This is pretty boring. Proceed at your own risk. Unless you are thinking about making a C# website, it may not be worth the effort.

A summary. Lambdas have unique syntactic rules. We had some help from the C# specification itself. We used lambdas with zero, one or many arguments, and with a return value.
A final note. Anonymous functions have no names, but we learned lots of their details. With the delegate keyword, we also specify method objects.
© 2007-2019 Sam Allen. Every person is special and unique. Send bug reports to info@dotnetperls.com.
Home
Dot Net Perls