Blog Archives
Lambda expressions (advanced)
Well, I have used lambda expressions in previous posts already. I was expecting that you know them already.
A Lambda function does not have a name, therefore it is also refered to as anonymous function.
Let’s have a closer look now. In my last post I was introducing delegates and we started with the following example:
// methods private double Multiply(double a, double b) { return a * b; } private double Add(double a, double b) { return a + b; } // a delegate for any of the above methods private delegate double dCalc(double a, double b); void Delegates1() { dCalc calc = Add; // creates a new delegate Console.WriteLine("Sum " + calc(6.0, 3.0)); calc = Multiply; // creates a new delegate Console.WriteLine("Product " + calc(6.0, 3.0)); Console.ReadLine(); } //
Above code can be shortened by using lambda expressions. It also becomes more legible then:
private delegate double dCalc(double a, double b); void Lambda1() { dCalc calc = (x, y) => x + y; Console.WriteLine("Sum " + calc(6.0, 3.0)); calc = (x, y) => x * y; Console.WriteLine("Product " + calc(6.0, 3.0)); Console.ReadLine(); } //
(x, y) => x + y; is a lambda expression with x and y defined as doubles. The types were defined in the delegate dCalc, which has two doubles as input parameters and one double as return value. The “@” sign is pronounced “at”, likewise the “=>” sign is pronounced “go to” or “goes to”.
As usual you can use curly braces to group multiple statements:
(x, y) => { Console.WriteLine(“hello”); return x + y;}
You don’t have to use parentheses if you only have one parameter.
(x) => x + x;
equals
x => x + x;
And you don’t need the “return” when there is only one statement.
x => x + x;
equals
x => { return x + x; }
C# offers built-in types. They are Func and Action. You can use them to define delegates. Both can take between 0 and 16 parameters. Action has no return value, Func must define a return type in the last parameter.
void Lambda2() { // syntax Func<first parameter type, second parameter type, return value type> Func<double, double, int> func = (x, y) => (int)(x + y); // syntax Action<first parameter type, second parameter type> Action<double, double> action = (x, y) => Console.WriteLine(x + y); } //
Btw. you need empty parentheses whenever you don’t use any parameters.
Func<int> f = () => 2;
You can be more formal and declare types explicitly.
void Lambda3() { Func<double, double, int> func = (double x, double y) => (int)(x + y); Action<double, double> action = (double x, double y) => Console.WriteLine(x + y); } //
If a delegate refers to a local variable it can happen that the local variable does not exist anymore at the time of the lambda expression/(code) execution. It is important to understand that C# is taking care of this problem. The lifetimes of local variables are extended to at least the lifetime of the longest-living delegate. This is called closure.
void Lambda4() { string s = "hello world"; Action action = () => { Thread.Sleep(2000); Console.WriteLine(s); }; Task.Factory.StartNew(action); // Exit the method before the task has completed. // This does not invalidate the string s !!! } //
Delegates (basics)
A delegate is like an enhanced classical pointer. It is type-safe and needs a specific method signature. Delegates can invoke methods, thus they can be used for events.
To declare a delegate simply add the word “delegate” in front of a method definition.
// methods private double Multiply(double a, double b) { return a * b; } private double Add(double a, double b) { return a + b; } // a delegate for any of the above methods private delegate double dCalc(double a, double b);
Assign a method to a delegate and then use the delegate to call the method.
void Delegates1() { dCalc calc = Add; // creates a new delegate Console.WriteLine("Sum " + calc(6.0, 3.0)); calc = Multiply; // creates a new delegate Console.WriteLine("Product " + calc(6.0, 3.0)); Console.ReadLine(); } //
example output:
Sum 9
Product 18
You can use operators to add or remove delegates. The next example shows that a delegate can point to multiple methods. This can be done, because delegates inherit from the System.MulticastDelegate class, which in turn inherits from System.Delegate.
private delegate void dPrint(); private void PrintA() { Console.WriteLine("Print A"); } private void PrintB() { Console.WriteLine("Print B"); } void Delegates2() { Console.WriteLine("--------------------------------"); dPrint print = PrintA; print += PrintB; Console.WriteLine("added A and B"); print(); Console.WriteLine("--------------------------------"); print -= PrintA; Console.WriteLine("removed A"); print(); Console.ReadLine(); } //
example output:
——————————–
added A and B
Print A
Print B
——————————–
removed A
Print B
Delegates do have iterators. You can loop through and/or count them. By now you should easily understand that delegates have nothing to do with pointers (as eg. used in C++). They do not just point somewhere. A delegate is a complex class. And we are dealing with multicast delegates on top of that.
private delegate void dPrint(); private void PrintA() { Console.WriteLine("Print A"); } private void PrintB() { Console.WriteLine("Print B"); } void Delegates3() { dPrint print = PrintA; print += PrintB; print += PrintB; print(); Console.WriteLine("number of invokes: " + print.GetInvocationList().GetLength(0)); foreach (Delegate d in print.GetInvocationList()) { Console.WriteLine(d.Method.Name + "()"); } Console.ReadLine(); } //
example output:
Print A
Print B
Print B
number of invokes: 3
PrintA()
PrintB()
PrintB()
There are some words to learn here:
Covariance describes a return value that is more derived than defined in the delegate.
Contravariance describes a method with parameters that are less derived than those defined in the delegate.
Invariant describes a generic type parameter that is neither marked covariant nor contravariant.
Variance Covariance and Contravariance are collectively called variance.
Covariance looks like standard polymorphism, whereas Contravariance seems counterintuitive. What is important to remember is that a covariant type parameter can be used as the return type of a delegate, and contravariant type parameters can be used as parameter types.
Contravariance was introduced in C# 4. Source code that did not compile in C# 3.5 is now compiling and running very well under C# 4. Contravariance is meaningful for write-only methods. So the input does not produce an output of the same hierarchy.
Covariance example:
class A { public int dummyA = 0; } class B : A { public double dummyB = 0.0; } delegate A dCovariance(); B Test() { return new B(); } void Delegates4() { dCovariance covariance = Test; // test returns class B, not class A as defined in the delegate foreach (Delegate d in covariance.GetInvocationList()) { Console.WriteLine(d.Method.Name + "()"); } Console.ReadLine(); } //
example output:
Test()
Contravariance example:
class A { public int dummyA = 0; } class B : A { public double dummyB = 0.0; } void Test2(A xParameter) { return; } delegate void dContravariance(B xClassB); void Delegates5() { // The parameter in Test2() is of type class "A", but the // delegate was defined with a class "B" parameter. dContravariance contravariance = Test2; foreach (Delegate d in contravariance.GetInvocationList()) { Console.WriteLine(d.Method.Name + "()"); } contravariance(new B()); // <= important !!!! Console.ReadLine(); } //
example output:
Test2()
When calling contravariance(…) we have to pass any class B instance as parameter. And the method Test2() has obviously no problems with this. Class B is more derived. It does make sense now that Test2() can be less derived, doesn’t it?
Contravariance will be discussed in more detail when looking at generic interfaces with the “in T” annotation. One commonly used contravariant interface is IComparer. Lean back for the moment and just have a quick look at a custom interface using the “in T” annotation. That should be enough for today.
interface IPerson<in T> { string Name { get; set; } int Age { get; set; } }