0x55——C#中的Lambda Expression
本文主要参考MSDN上的Lambda Expression(C# Programming Guide),博文Lambda Expression和code project上的博文Exploring Lambda Expression in C#。
Introduction
Lambda表达式是一个匿名函数,含有表达式(Expression)以及语句(statements),可以用于构造delegates和expression tree。
所有的Lambda Expression都有一个Lambda Expression符号=>,念作“goes to”,符号的左侧是函数参数,右侧则是表达式和语句,“x => x * x”就念作“x goes to x times x”,并且可以赋值给一个delegates。就如同如下:
del MyDelegate = x => x * x; Console.WriteLine("MyDelegate(5) = {0}", MyDelegate(5));
或者构建一个Expression Tree Type:
Expression<del> myET = x => x * x; Console.WriteLine("MyET(5) = {0}", myET.Compile()(5));
=>的运算优先级和赋值运算符(=)相同,且是右连接的。
Parameter
一个Lambda表达式具有如下格式:
(input paramter) => expression
如果只有一个参数,可以像Introduction中那样传参。如果有两个或以上,就需要加上括号:
(x, y) => x == y
有时候如果还需要指明类型,可以如下:
(int x, string y) => s.Length == x;
如果不传参数,如下:
() => SomeMethod()
Statement Lambdas
Lambdas可以用花括号括起来写很多语句,尽管管理上一般就只写两到三句(更长的就用delegate一类的吧)
delegate void TestDelegate(string s); ... // the body of a statement lambda can consist of any number of statements; // however, in practice there are typically no more than two or three. TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); }; myDel("Hello");
Lambdas With the Standard Query Operators
很多Standard Query Operators都有个输入参数是Func<T1, TResult>,它的定义中,尖括号内,前面的几个都是它的参数arg0, arg1,……,最后一个TResult是它的返回值。譬如:
Func<int, string, bool> myFunc = (x, s) => x == s.Length; bool result = myFunc(5, "Cuero"); Console.WriteLine("{0}, {1}", result.GetType(), result);
于是下面就是若干个在Standard Query Operators里使用Lambdas的例子:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int[] numbers2 = { 5,4,9,3}; int odds = numbers.Count(n => n % 2 == 1); var firstNumbersLessThan6 = numbers.TakeWhile(n => n < 6); foreach (int i in firstNumbersLessThan6.ToList()) Console.Write("{0} ", i); Console.WriteLine(); firstNumbersLessThan6 = numbers2.TakeWhile(n => n < 6); foreach (int i in firstNumbersLessThan6.ToList()) Console.Write("{0} ", i); Console.WriteLine();
上面这个例子中,第一次循环到9就停下了,因为此时n == 9, n >= 6了,所以循环停止。
如下是另一个例子:
Func<int, bool> where = n => n < 9; Func<int, int> select = n => n; Func<int, string> orderby = n => n % 2 == 0 ? "even" : "odd"; var nums = numbers.Where(where).OrderBy(orderby).Select(select); foreach (int i in nums.ToList()) Console.Write("{0} ", i); Console.WriteLine();
OrderBy提供了一个分类的依据,此处应该是按照字母序,如果都是even,可能那就按传入的先后顺序。
Variable Scope in Lambda Expressions
Lambdas可以获得其外部的变量。如下是MSDN上的代码:
class Test { D del; D2 del2; public void TestMethod(int input) { int j = 0; // Initialize the delegates with lambda expressions. // Note access to 2 outer variables. // del will be invoked within this method. del = () => { j = 10; return j > input; }; // del2 will be invoked after TestMethod goes out of scope. del2 = (x) => {return x == j; }; // Demonstrate value of j: // Output: j = 0 // The delegate has not been invoked yet. Console.WriteLine("j = {0}", j); // Invoke the delegate. bool boolResult = del(); // Output: j = 10 b = True Console.WriteLine("j = {0}. b = {1}", j, boolResult); } static void Main() { Test test = new Test(); test.TestMethod(5); // Prove that del2 still has a copy of // local variable j from TestMethod. bool result = test.del2(10); // Output: True Console.WriteLine(result); Console.ReadKey(); } }
不过此时应该注意如下几点:
- 使用的变量不会被垃圾收集器收走,直到匿名函数作用结束。
- Lambdas内部的变量在外部是不可见的。
- Lambdas不能直接获得ref和out参数(必须传进去)。
- Lambdas里面的return语句不会使整个大函数return。
- 不能用goto、break和continue语句跳到Lambdas之外的标签。
关于其上的第三点,在这里给一个例子:
delegate void OutParameter(out int i); delegate void RefParameter(ref int i); public static void GotchasWithLambdas() { // must explicitly specify the parameter type // example with out parameter int i; int i; OutParameter something = (out int x) => x = 5; something(out i); Console.WriteLine("out i = {0}", i); // example with ref parameter int i; int a = 2; RefParameter test = (ref int x) => x++; test(ref a); Console.WriteLine("ref a = {0}", a); }
The Action Delegates Type
另外,Lambdas还能和Action配合使用,简要代码如下:
class Program { public static void SomeAsynchronousMethod(Action<string> complete, string s) { Thread.Sleep(1000); complete(s); } static void Main(string[] args) { SomeAsynchronousMethod(((string s) => { Console.WriteLine(s + " done"); }), "Cuero"); } }
关于这部分内容,以及Expression Tree,在本文都不作太多的介绍,只是贴一些简单的例子而已。