In the following example,squareis assigned the lambda expressionx=>x*x:
delegate int Transformer (int i);
class Test { static void Main() { Transformer square = x => x * x; Console.WriteLine (square(3)); // 9 } }
We could rewrite the example by converting the lambda expression into a method, and then call the method through the delegate. In fact, the compiler internally performs that translation for you when you assign a delegate a lambda expression:
delegate int Transformer (int i);
class Test { static void Main() { Transformer square = Square; Console.WriteLine (square(3)); // 9 } static int Square (int x) {return x * x;} }
A lambda expression has the following BNF form:
(parameters) => expression-or-statement-block
For convenience, you can omit the parentheses if and only if there is exactly one parameter of an inferable type.
In our example, there is a single parameter,x, and the expression isx*x:
x => x * x;
Each parameter of the lambda expression corresponds to a delegate parameter, and the type of the expression (which may bevoid) corresponds to the return type of the delegate.
In our example,xcorresponds to parameteri, and the expressionx * xcorresponds to the return typeint, therefore being compatible with theTransformerdelegate:
delegate int Transformer (int i);
A lambda expression’s code can be a statement block instead of an expression. We can rewrite our example as follows:
x => {return x * x;};
Explicitly Specifying Lambda Parameter Types
The compiler can usually infer the type of lambda parameters contextually. When this is not the case, you must specify the type of each parameter explicitly. Consider the following delegate type:
delegate int Transformer (int i);
The compiler uses type inference to infer thatxis anint, by examiningTransfomer’s parameter type:
Transformer d = x => x * x;
We could explicitly specifyx’s type as follows:
Transformer d = (int x) => x * x;
Generic Lambda Expressions and the Func Delegates
With generic delegates, it becomes possible to write a small set of delegate types that are so general they can work for methods of any return type and any (reasonable) number of arguments. These delegates are the Func and Action delegates, defined in the System namespace:
These delegates are extremely general. TheTransformerdelegate in our previous example can be replaced with aFuncdelegate that takes a singleintargument and returns anintvalue:
These delegates are extremely general. The delegate in our previous example can be replaced with a delegate that takes a single argument and returns an value:
class Test { static void Main() { Func<int,int>square = x => x * x; Console.WriteLine (square(3)); // 9 } }
A lambda expression can reference the local variables and parameters of the method in which it’s defined. For example:
delegate int NumericSequence ();
class Test { static void Main() { int seed = 0; NumericSequence natural = () => seed++; Console.WriteLine (natural()); // 0 Console.WriteLine (natural()); // 1 } }
Local variables and parameters referenced by a lambda expression are called outer variables. In our example,seedis an outer variable referenced by the lambda expression() => seed++. Outer variables are captured, meaning their lifetime is extended to that of the lambda expression. Let’s refactor the example to make the effect of capturing more striking:
delegate int NumericSequence ();
class Test { static NumericSequence Natural () { int seed = 0; // executes once (per call to Natural()) return () => seed++; // executes twice (per call to delegate instance // returned by Natural()) }
The local variableseedwould ordinarily just pop off the stack when theNaturalmethod exits. However,seedis captured by the lambda expression of the delegate instance returned byNatural. This means the lifetime ofseedis extended to the lifetime of that delegate instance. Subsequent invocations of that same delegate instance will reuse the sameseed variable.
Capturing is internally implemented by “lifting” the captured variables into fields of a private class. When the method is called, the class is instantiated and lifetime-bound to the delegate instance.
A local variable instantiated within a lambda expression is unique per invocation of the delegate instance. If we refactor our previous example to instantiateseedwithin the lambda expression, we get a different (in this case, undesirable) result:
delegate int NumericSequence ();
class Test { static NumericSequence Natural () { return () => {int seed = 0; return seed++; }; }
Anonymous methods are a C# 2.0 feature that has been subsumed by C# 3.0 lambda expressions. An anonymous method is like a lambda expression, but it lacks the following features:
Implicitly typed parameters
Expression syntax (an anonymous method must always be a statement block)
The ability to compile to an expression tree, by assigning toExpression<T>
To write an anonymous method, you include thedelegatekeyword followed by a parameter declaration and then a method body. For example:
delegate int Transformer (int i);
class Test { static void Main() { Transformer square = delegate (int x) {return x * x;}; Console.WriteLine (square(3)); // 9 } }
The following line:
Transformer square = delegate (int x) {return x * x;};
is semantically equivalent to the following lambda expression:
Transformer square = (int x) => {return x * x;};
Or simply:
Transformer square = x => x * x;
Anonymous methods capture outer variables in the same way lambda expressions do.
A try statement specifies a code block subject to error-handling or cleanup code. The try block must be followed by a catchblock, afinallyblock, or both. Thecatchblock executes when an error occurs in thetryblock. Thefinallyblock executes after execution leaves thetryblock (or if present, thecatchblock), to perform cleanup code, whether or not an error occurred.
Acatch block has access to anExceptionobject that contains information about the error. You use acatch block to either compensate for the error or rethrow the exception. You rethrow an exception if you merely want to log the problem, or if you want to rethrow a new, higher-level exception type.
Afinallyblock adds determinism to your program, by always executing no matter what. It’s useful for cleanup tasks such as closing network connections.
Atrystatement looks like this:
try { ... // exception may get thrown within execution of this block } catch (ExceptionA ex) { ... // handle exception of type ExceptionA } catch (ExceptionB ex) { ... // handle exception of type ExceptionB } finally { ... // cleanup code }
Consider the following program:
class Test { static int Calc (int x) {return 10 / x;}
static void Main()
{ int y = Calc (0); Console.WriteLine (y); } }
Becausexis zero, the runtime throws aDivideByZeroException, and our program terminates. We can prevent this by catching the exception as follows:
class Test { static int Calc (int x) {return 10 / x;}
static void Main() { try { int y = Calc (0); Console.WriteLine (y); } catch (DivideByZeroException ex) { Console.WriteLine("x cannot be zero"); } Console.WriteLine ("program completed"); } }
OUTPUT: x cannot be zero program completed
When an exception is thrown, the CLR performs a test:
Is execution currently within a trystatement that can catch the exception?
If so, execution is passed to the compatiblecatchblock. If thecatchblock successfully finishes executing, execution moves to the next statement after thetrystatement (if present, executing thefinallyblock first).
If not, execution jumps back to the caller of the function, and the test is repeated (after executing anyfinallyblocks that wrap the statement).
If no function takes responsibility for the exception, an error dialog is displayed to the user, and the program terminates.
Please check back next week for the continuation of this article.