Lambda expressions
Sometimes the whole signature of a method can be more code than the body of a method. There are also situations in which you need to create an entire method only to use it in a delegate.
For these cases, Microsoft added some new features to C#, 2.0 anonymous methods were added. In C# 3.0, things became even better when lambda expressions were added. Lambda expression is the preferred way to go when writing new code.
Below is an example of newer lambda syntax.
1internal class Program
2{
3 public delegate double MathDelegate(double value1, double value2);
4 static void Main(string[] args)
5 {
6 MathDelegate mathDelegate = (x, y) => x + y;
7 var result = mathDelegate(5, 2);
8 Console.WriteLine(result);
9 // output: 7
10 mathDelegate = (x, y) => x - y;
11 result = mathDelegate(5, 2);
12 Console.WriteLine(result);
13 // output: 3
14 Console.ReadLine();
15 }
16}
When reading this code, you can say “go or goes” to for the special lambda syntax. For example, the first lambda expression in the above example is read as “x and y goes to adding x and y”.
The lambda function has no specific name as the methods. Because of this, lambda functions are called anonymous functions. You also don’t have to specify a return type explicitly. The compiler infers this automatically from your lambda. And in the case of the above example, the types of parameters x and y are also not specified explicitly.
You can create lambdas that span multiple statements. You can do this by adding curly braces around the statements that form the lambda as below example shows.
1MathDelegate mathDelegate = (x, y) =>
2{
3 Console.WriteLine("Add");
4 return x + y;
5};
You can learn more about .NET built-in delegates here.
Si riportano di seguito alcuni esempi che spiegano il passaggio da funzione anonima a lambda expression
Si consideri il seguente esempio di delegato con funzione anonima:
1public class Student
2{
3 public int Id { get; set; }
4 public string? Name { get; set; }
5 public int Age { get; set; }
6}
7internal class Program
8{
9 delegate bool IsTeenAger(Student stud);
10 static void Main(string[] args)
11 {
12 IsTeenAger isTeenAger = delegate (Student s)
13 {
14 return s.Age > 12 &&
15 s.Age < 20;
16 };
17 Student stud = new Student() { Age = 25 };
18 Console.WriteLine(isTeenAger(stud));
19 Console.ReadLine();
20 }
21}
Lo stesso risultato si può ottenere con il seguente costrutto lambda
1public class Student
2{
3 public int Id { get; set; }
4 public string? Name { get; set; }
5 public int Age { get; set; }
6}
7internal class Program
8{
9 delegate bool IsTeenAger(Student stud);
10 static void Main(string[] args)
11 {
12 IsTeenAger isTeenAger = s => s.Age > 12 && s.Age<20;
13 Student stud = new Student() { Age = 25 };
14 Console.WriteLine(isTeenAger(stud));
15 Console.ReadLine();
16 }
17}
Spiegazione:
The Lambda expression evolves from anonymous method by first removing the delegate keyword and parameter type and adding a lambda operator =>.
Lambda Expression from Anonymous Method
The above lambda expression is absolutely valid, but we don't need the curly braces, return and semicolon if we have only one statement that returns a value. So we can eliminate it.
Also, we can remove parenthesis (), if we have only one parameter.
Lambda Expression from Anonymous Method
Thus, we got the lambda expression: s => s.Age > 12 && s.Age < 20 where s is a parameter, => is the lambda operator and s.Age > 12 && s.Age < 20 is the body expression:
Lambda Expression Structure in C#
You can wrap the parameters in parenthesis if you need to pass more than one parameter, as below:
1public class Student
2{
3 public int Id { get; set; }
4 public string? Name { get; set; }
5 public int Age { get; set; }
6}
7internal class Program
8{
9 delegate bool IsTeenAger(Student stud);
10 static void Main(string[] args)
11 {
12 IsTeenAger isTeenAger = delegate (Student s)
13 {
14 return s.Age > 12 && s.Age < 20;
15 };
16 Student stud = new Student() { Age = 25 };
17 Console.WriteLine(isTeenAger(stud));
18 Console.ReadLine();
19 }
20}
You can also give type of each parameters if parameters are confusing:
1(Student s,int youngAge) => s.Age >= youngAge;
It is not necessary to have at least one parameter in a lambda expression. The lambda expression can be specify without any parameter also.
1() => Console.WriteLine("Parameter less lambda expression")
Ad esempio:
1internal class Program
2{
3 delegate void Print();
4 static void Main(string[] args)
5 {
6 Print print = () => Console.WriteLine("This is parameter less lambda expression");
7 print();
8 Console.ReadLine();
9 }
10}
You can wrap expressions in curly braces if you want to have more than one statement in the body:
1(s, youngAge) =>
2{
3 Console.WriteLine("Lambda expression with multiple statements in the body");
4 return s.Age >= youngAge;
5}
Altro esempio: lambda expression con due parametri
1public class Student
2{
3 public int Id { get; set; }
4 public string? Name { get; set; }
5 public int Age { get; set; }
6}
7internal class Program
8{
9 delegate bool IsYoungerThan(Student stud, int youngAge);
10 static void Main(string[] args)
11 {
12 IsYoungerThan isYoungerThan = (s, i) => s.Age < i;
13 Student stud = new Student() { Age = 25 };
14 Console.WriteLine(isYoungerThan(stud, 30));
15 }
16}
You can declare a variable in the expression body to use it anywhere in the expression body, as below:
1internal class Program
2{
3 delegate bool IsAdult(Student stud);
4 static void Main(string[] args)
5 {
6 IsAdult isAdult = (s) =>
7 {
8 int adultAge = 18;
9 Console.WriteLine("Lambda expression with multiple statements in the body");
10 return s.Age >= adultAge;
11 };
12 Student stud = new Student() { Age = 25 };
13 Console.WriteLine(isAdult(stud));
14 Console.ReadLine();
15 }
16}
17
18public class Student
19{
20 public int Id { get; set; }
21 public string? Name { get; set; }
22 public int Age { get; set; }
23}
.NET has a couple of built-in delegates types that you can use when declaring delegates.For the MathDelegate
examples, you have used the following delegate: public delegate double MathDelegate(double value1, double value2);
You can replace this delegate with one of the built-in types namely:
1Func<int, int, int>
like this,
1
2internal class Program
3{
4 static void Main(string[] args)
5 {
6 //modalità alternativa per definire un delegato
7 Func<int, int, int> mathDelegate = (x, y) =>
8 {
9 Console.WriteLine("Add");
10 return x + y;
11 };
12 var result = mathDelegate(5, 2);
13 Console.WriteLine(result);
14 // output: 7
15 mathDelegate = (x, y) => x - y; ;
16 result = mathDelegate(5, 2);
17 Console.WriteLine(result);
18 // output: 3
19 Console.ReadLine();
20 }
21}
The Func<...>
types can be found in the System namespace and they
represent delegates that return a type and take 0 to 16 parameters. All
those types inherit from MulticastDelegate so you can add
multiple methods to the invocation list.
The lambda expression can be assigned to Func<in T, out TResult>
type
delegate. The last parameter type in a Func delegate is the return type
and rest are input parameters. Visit Func delegate
section of C# tutorials to know more about it.
Consider the following lambda expression to find out whether a student is a teenager or not.
1Func<Student, bool> isStudentTeenAger = s => s.Age > 12 && s.Age <20;
2Student std = new Student() { age = 21 };
3bool isTeen = isStudentTeenAger(std);// returns false
In the above example, the Func delegate expects the first input parameter to be of Student type and the return type to be boolean. The lambda expression s => s.age > 12 && s.age < 20 satisfies the Func<Student, bool> delegate requirement, as shown below:
Func delegate with Lambda Expression
The Func<>
delegate shown above, would turn out to be a function as
shown below.
1bool isStudentTeenAger(Student s)
2{
3 return s.Age > 12 && s.Age < 20;
4}
Unlike the Func delegate, an Action delegate can only have input parameters. Use the Action delegate type when you don’t need to return any value from lambda expression.
If you want a delegate type that doesn’t return a value, you can use the System.Action types. They can also take 0 to 16 parameters, but they don’t return a value.
Here is an example of using the Action type
1internal class Program
2{
3 static void Main(string[] args)
4 {
5 Action<int, int> mathDelegate = (x, y) =>
6 {
7 Console.WriteLine(x + y);
8 };
9 mathDelegate(5, 2);
10 // output: 7
11 mathDelegate = (x, y) => Console.WriteLine(x - y);
12 mathDelegate(5, 2);
13 // output: 3
14 Console.ReadLine();
15 }
16}
Esempio con le lambda expressions:
1internal class Program
2{
3 static void Main(string[] args)
4 {
5 Action<Student> PrintStudentDetail = s => Console.WriteLine("Name:{0}, Age: {1}", s.Name, s.Age");
6 Student std = new Student() { Name = "Bill", Age = 21 };
7 PrintStudentDetail(std);
8 Console.ReadLine();
9 }
10}
11
12public class Student
13{
14 public int Id { get; set; }
15 public string? Name { get; set; }
16 public int Age { get; set; }
17}
Things start to become more complex when your lambda function starts referring to variables declared outside of the lambda expression or to this reference. Normally when control leaves the scope of the variable, the variable is no longer valid. But what if a delegate refers to a local variable. To fix this, the compiler generates code that makes the life of the captured variable at least as long as the longest-living delegate. This is called a closure.
A closure is a function that is bound to the environment in which it is declared. Thus, the function can reference elements from the environment within it’s body. In the case of a C# 2.0 anonymous method, the environment to which it is bound is its parenting method body. This means that local variables from the parenting method body can be referenced within the anonymous method's body. So, this code prints 0 to the console as expected:
1internal class Program
2{
3 delegate void MyAction();
4 static void Main(string[] args)
5 {
6 int x = 0;
7 MyAction a = delegate { Console.WriteLine(x); };
8 a();
9 Console.ReadLine();
10 }
11}
Most developers don't have any problem with the code above. A local variable "x" is declared and initialized to 0. Then, a new delegate "a" of type Action is declared and assigned to an anonymous method that writes "x" to the console. Finally, "a" is called and the value of "x" (0) is printed to the console. The rub occurs when the code is changed like this:
1internal class Program
2{
3 delegate void MyAction();
4 static void Main(string[] args)
5 {
6 int x = 0;
7 MyAction a = delegate { Console.WriteLine(x); };
8 x = 1;
9 a();
10 Console.ReadLine();
11 }
12}
Now, x
is reassigned to a value of 1 before a
is called. What
will be output to the console?
It turns out that the answer is 1, not 0. The reason for this is that
the anonymous method is a closure and is bound to its parenting method
body and the local variables in it. The important distinction is that it
is bound to variables, not to values. In other words, the value of x
is not copied in when a
is declared. Instead, a reference to x
is used so that a
will always use the most recent value ofx
. In
fact, this reference to x
will be persisted even ifx
goes out
of scope. Consider this code:
1internal class Program
2{
3 delegate void MyAction();
4 static MyAction GetAction()
5 {
6 int x = 0;
7 MyAction a = delegate { Console.WriteLine(x); };
8 x = 1;
9 return a;
10 }
11
12 static void Main(string[] args)
13 {
14 MyAction a = GetAction();
15 a();
16 Console.ReadLine();
17 }
18}
That will still print 1 to the console even though x
is out of scope
by the time that a
is called. So, how is this achieved? Well, the
good news is that this is handled through compiler magic. There isn’t
any runtime support for closures. That means that you could use the same
techniques to create a closure without using an anonymous method.
Lambda Expression is a shorter way of representing anonymous method.
Lambda Expression syntax: parameters => body expression
Lambda Expression can have zero parameter.
Lambda Expression can have multiple parameters in parenthesis ().
Lambda Expression can have multiple statements in body expression in curly brackets {}.
Lambda Expression can be assigned to Func, Action or Predicate delegate.
Lambda Expression can be invoked in a similar way to delegate.