Lambda 表达式(C# 编程指南)
Visual Studio 2010
“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型。
所有 Lambda 表达式都使用 Lambda 运算符 =>,该运算符读为“goes to”。 该 Lambda 运算符的左边是输入参数(如果有),右边包含表达式或语句块。 Lambda 表达式 x => x * x 读作“x goes to x times x”。可以将此表达式分配给委托类型,如下所示:
C#
delegate int del(int i);
static void Main(string[] args)
{
delmyDelegate = x => x * x;
int j = myDelegate(5); //j = 25
}
创建表达式树类型:
C#
usingSystem.Linq.Expressions;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Expression<del> myET = x => x * x;
}
}
}
=> 运算符具有与赋值运算符 (=) 相同的优先级,并且是右结合运算符。
Lambda 在基于方法的 LINQ 查询中用作标准查询运算符方法(如 Where)的参数。
使用基于方法的语法在 Enumerable 类中调用 Where 方法时(像在 LINQ toObjects 和 LINQ to XML 中那样),参数是委托类型 System.Func<T, TResult>。 使用 Lambda 表达式创建委托最为方便。 例如,当您在 System.Linq.Queryable 类中调用相同的方法时(像在 LINQ toSQL 中那样),则参数类型是 System.Linq.Expressions.Expression<Func>,其中 Func 是包含至多十六个输入参数的任何 Func 委托。 同样,Lambda 表达式只是一种用于构造表达式树的非常简练的方式。 尽管事实上通过 Lambda 创建的对象的类型是不同的,但 Lambda 使得 Where 调用看起来类似。
在前面的示例中,请注意委托签名具有一个 int 类型的隐式类型输入参数,并返回 int。 可以将 Lambda 表达式转换为该类型的委托,因为该表达式也具有一个输入参数 (x),以及一个编译器可隐式转换为 int 类型的返回值。 (以下几节中将对类型推理进行详细讨论。)使用输入参数 5 调用委托时,它将返回结果 25。
适用于匿名方法的所有限制也适用于 Lambda 表达式。 有关更多信息,请参见 匿名方法(C# 编程指南)。
Lambda 表达式
表达式在右边的 Lambda 表达式称为“Lambda 表达式”。 Lambda 表达式在构造表达式树(C# 和 Visual Basic)时广泛使用。 Lambda 表达式返回表达式的结果,并采用以下基本形式:
复制
(input parameters) => expression
只有在 Lambda 有一个输入参数时,括号才是可选的;否则括号是必需的。 两个或更多输入参数由括在括号中的逗号分隔:
(x, y) => x == y
有时,编译器难于或无法推断输入类型。 如果出现这种情况,您可以按以下示例中所示方式显式指定类型:
(int x, string s) => s.Length > x
使用空括号指定零个输入参数:
() => SomeMethod()
在上一个示例中,请注意 Lambda 表达式的主体可以包含方法调用。 但是,如果要创建将在另一个域(比如 SQL Server)中使用的表达式树,则不应在 Lambda 表达式中使用方法调用。 方法在 .NET 公共语言运行时上下文的外部将没有意义。
Lambda 语句
Lambda 语句与 Lambda 表达式类似,只是语句括在大括号中:
(input parameters) => {statement;}
Lambda 语句的主体可以包含任意数量的语句;但是,实际上通常不会多于两个或三个语句。
delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n +" " + "World"; Console.WriteLine(s); };
myDel("Hello");
像匿名方法一样,Lambda 语句无法用于创建表达式树。
带有标准查询运算符的 Lambda
许多标准查询运算符都具有输入参数,其类型是泛型委托的 Func<T, TResult> 系列的其中之一。 Func<T,TResult> 委托使用类型参数定义输入参数的数目和类型,以及委托的返回类型。 Func 委托对于封装应用于一组源数据中每个元素的用户定义表达式非常有用。 例如,假设有以下委托类型:
public delegate TResult Func<TArg0,TResult>(TArg0 arg0)
可以将委托实例化为 Func<int,bool>myFunc,其中 int 是输入参数,bool 是返回值。 始终在最后一个类型参数中指定返回值。 Func<int,string, bool> 定义包含两个输入参数(int 和 string)且返回类型为 bool 的委托。 在调用下面的 Func 委托时,该委托将返回 true 或 false 以指示输入参数是否等于 5:
Func<int, bool> myFunc = x => x == 5;
bool result = myFunc(4); // returns false ofcourse
当参数类型为 Expression<Func> 时,您也可以提供 Lambda 表达式,例如在 System.Linq.Queryable 内定义的标准查询运算符中。 如果指定 Expression<Func> 参数,Lambda 将编译为表达式树。
此处显示了一个标准查询运算符,Count 方法:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0};
int oddNumbers = numbers.Count(n => n % 2 ==1);
编译器可以推断输入参数的类型,或者您也可以显式指定该类型。 这个特别的 Lambda 表达式将计算整数 (n) 的数量,这些整数除以 2 时余数为 1。
以下方法将生成一个序列,其中包含 numbers 数组中在 9 左边的所有元素,因为 9 是序列中不满足条件的第一个数字:
var firstNumbersLessThan6 = numbers.TakeWhile(n=> n < 6);
此示例演示如何通过将输入参数括在括号中来指定多个输入参数。 该方法将返回数字数组中的所有元素,直至遇到一个值小于其位置的数字为止。 不要将 Lambda 运算符 (=>) 与大于等于运算符 (>=) 混淆。
var firstSmallNumbers = numbers.TakeWhile((n,index) => n >= index);
Lambda 中的类型推理
在编写 Lambda 时,通常不必为输入参数指定类型,因为编译器可以根据 Lambda 主体、基础委托类型以及 C# 语言规范中描述的其他因素推断类型。 对于大多数标准查询运算符,第一个输入是源序列中的元素的类型。 因此,如果要查询 IEnumerable<Customer>,则输入变量将被推断为 Customer 对象,这意味着您可以访问其方法和属性:
customers.Where(c => c.City =="London");
Lambda 的一般规则如下:
Lambda 包含的参数数量必须与委托类型包含的参数数量相同。
Lambda 中的每个输入参数必须都能够隐式转换为其对应的委托参数。
Lambda 的返回值(如果有)必须能够隐式转换为委托的返回类型。
请注意,Lambda 表达式本身没有类型,因为常规类型系统没有“Lambda 表达式”这一内部概念。但是,有时会不正式地论及 Lambda 表达式的“类型”。 在这些情况下,类型是指委托类型或 Lambda 表达式所转换为的 Expression 类型。
Lambda 表达式中的变量范围
Lambda 可以引用“外部变量”,这些变量位于在其中定义 Lambda 的封闭方法或类型的范围内。 将会存储通过这种方法捕获的变量以供在 Lambda 表达式中使用,即使变量将以其他方式超出范围或被作为垃圾回收。 必须明确地分配外部变量,然后才能在 Lambda 表达式中使用该变量。 下面的示例演示这些规则:
delegate bool D();
delegate bool D2(int i);
class Test
{
D del;
D2del2;
publicvoid TestMethod(int input)
{
intj = 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);
}
staticvoid 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();
}
}
下列规则适用于 Lambda 表达式中的变量范围:
捕获的变量将不会被作为垃圾回收,直至引用变量的委托超出范围为止。
在外部方法中看不到 Lambda 表达式内引入的变量。
Lambda 表达式无法从封闭方法中直接捕获 ref 或 out 参数。
Lambda 表达式中的返回语句不会导致封闭方法返回。
Lambda 表达式不能包含其目标位于所包含匿名函数主体外部或内部的 goto 语句、break 语句或 continue 语句。
总结
C#3.0新技术之Lambda表达式
http://hi.baidu.com/%CC%A4%C0%CB%CB%A7/blog/item/5eaba5238527b4ae4723e853.html
1.关于C# Lambda Expressions:
Lambda表达式是与委托紧密联系的。只要有委托参数类型的地方,就可以使用Lambda表达式。一个Lambda Expression (译为Lambda式) 就是一个包含若干表达式和语句的匿名函数。可以被用作创建委托对象或表达式树类型。所有的Lambda式都使用操作符“=>“,表示“goes to (转变为)”。操作符左边部分是输入参数表,右边部分是表达式或语句块。x => x * x 读成“x转变为x 乘x”。 我想这样理解:Lambda表达式表示是一个函数的另一种写法。运算符=>左边是输入,右边是函数体。函数体需不需要返回值,由接收这个函数定义的lambda表达式的委托决定。
实例1:Lambda式可以被赋值给一个委托类型:
1. delegate int del(int i);
2.
3. del myDelegate = x=> x * x;
4.
5. int j = myDelegate(5); //j = 25
也可以被用于创建一个表达式树类型:
实例2:
1. using System.Linq.Expressions;
2.
3. //…
4.
5. Expression<del>= x => x *x;
操作符“=>”具有和“=”一样的运算优先级,且为右相关(右边先执行)。
在例1中,我们注意到委托的定义中有一个int类型的输入参数以及int类型的返回值。例子中的Lambda式中并没有任何类型的声明。是编译器为我们做了相应的隐式数据类型转换:输入参数类型能够从委托的输入参数类型隐式转换,返回类型能够被隐式转换为委托的返回类型。
Lambda式不允许作为“is”和“as”操作符的左操作数出现。也就是
1. del myDelegate = x=> x * x as string; //error
所有对于匿名方法的约束也同样适用于Lambda式。请参阅Anonymous Methods (C# ProgrammingGuide).
2.从表达式理解C# Lambda Expressions
由一个计算表达式组成的一个Lambda式称之为表达式Lambda。表达式Lambda常被用于构造表达式树。一个表达式Lambda返回计算表达式运算的结果。基本结构如下:
1. (input parameters)=> expression
2.
3. //如果只有一个输入参数时,括号可以省略。
4.
5. //如果具有一个以上的输入参数,必需加上括号。
6.
7. (x) => x * x 等于 x => x * x
8.
9. (x, y) => x ==y
10.
11. //可以显式指定输入参数的类型
12.
13. (int x, strings) => s.Length > x
14.
15. //也可以没有任何输入参数
16.
17. () =>SomeMethod1()
上面这段代码在Lambda式中调用了一个方法。需要注意的是,如果在创建会被其他方使用的表达式树的时候,不宜在Lambda式中执行方法调用。比如:在SQL Server内执行。
一般来说,让一个方法在原先设计的上下文环境以外执行没有意义,也不能真正工作。
3.从语句理解C# Lambda Expressions
语句Lambda和表达式Lambda非常相似,只是语句被包含在大括号内:
1. (input parameters)=> {statement;}
大括号中的语句可以是任意多条,也可以写成多行(定义一个Lambda式也就是在定义一个匿名方法):
1. TestDelegate myDel= n => {
2. strings = n + " " + "World";
3. Console.WriteLine(s);};
当然语句Lambda跟匿名方法一样,无法用于创建表达式树。
4.C# Lambda Expressions之类型猜测
当编写一个Lambda式的时候,我们通常不需要明确指定输入参数的类型。因为编译器会根据Lambda体的实现,以及委托的定义来猜测类型。
举例:如果要从一个List<int>中删除小于100的元素
1. lst.RemoveAll(i=> i < 100); //i会被猜测为int
通常的猜测规则如下:
◆Lambda式必须包含与委托定义中相等数量的输入参数;
◆每个Lambda式的输入参数必须能够隐式转换成委托定义中所要求的输入参数;
◆Lambda式的返回值必须能够隐式转换成委托定义中的返回值。
注意:由于目前在common type system中还没有一个“Lambda式类型”的类型。如果在有些场合提到“Lambda式的类型”,那通常表示委托的定义或者是Expression<>类型。
5.C# Lambda Expressions变量作用域
在Lambda式定义中可以引用外部变量。只要是在定义处能够访问到的变量,都可以在Lambda式中引用。Lambda式的定义仅仅是定义一个匿名方法,最终会生成一个委托对象。外部变量的引用将被“捕获”到委托对象内部,将会伴随委托对象的整个生命周期。在委托对象生命周期结束之前该变量都不会被垃圾回收。就算外部变量已经超过了原来的作用域,也还能继续在Lambda式中使用。所有会被引用的外部变量必须在Lambda式定义之前被显式赋值。见下例
1. delegate bool D();
2. delegate bool D2(int i);
3. class Test
4. {
5. D del;
6. D2 del2;
7. public voidTestMethod(int input)
8. {
9. int j = 0;
10. //Initialize the delegates with lambda expressions.
11. //Note access to 2 outer variables.
12. //del will be invoked within this method.
13. del = () => { j= 10; return j > input;};
14.
15. //del2 will be invoked after TestMethod goes out of scope.
16. del2 = (x) => {return x == j; };
17. //Demonstrate value of j:
18. //Output: j = 0
19. //The delegate has not been invoked yet.
20. Console.WriteLine("j = {0}", j);
21. //Invoke the delegate.
22. bool boolResult = del();
23. //Output: j = 10 b = True //注意j在del的执行过程中被修改
24. Console.WriteLine("j = {0}. b = {1}", j, boolResult);
25. }
26. static voidMain()
27. {
28. Test test = new Test();
29. test.TestMethod(5);
30. //Prove that del2 still has a copy of
31. //local variable j from TestMethod.
32.
33. //j的引用超出了原先定义的作用域
34. bool result = test.del2(10);
35. //Output: True
36. Console.WriteLine(result);
37. Console.ReadKey();
38. }
39. }
40.
下面是关于变量作用域的规则:
◆被“捕获”的变量在委托的生命周期结束前都不会被垃圾回收;
Lambda所使用的变量,在委托的生命周期内不会被垃圾回收. 要让委托承载的方法数超出范围估计很难达到, 这个具体委托能承载多少个方法,我也想知道, 我试了加载100W个方法,也没出现问题, 这个应该和内存有关, 只要你机器有足够的内存.
◆在Lambda式内部定义的变量对外不可见;
delegate bool D();
D del;
for (int a = 0; a < 4; a++)
{
del += () => { int t = 0;return true; };
Console.WriteLine("t={0}", t); //这里无法访问Lambda中定义的变量t
}
◆Lambda式无法直接捕获一个具有ref或out描述的参数变量;
如果委托中有ref或out参数,则Lambda必须有参数类型(即显示类型)
delegate boolD2(int i,ref string str);
D2 del2;
for (int a = 0; a< 4; a++)
{
del2 += (int x, ref string c) =>
{ c = c + "j"; Console.WriteLine(x+ c); return true; };//这里必须显示参数类型 x, c}
strings="sadf";
del2(5,ref s);
Console.WriteLine(s);
◆Lambda式中的return语句不会导致当前所在的方法返回;
Lambda表达式中return 只是跳出Lambda, 然后继续执行方法中Lambda下面的语句, 不会跳出当前所在的方法.
◆Lambda式中不允许包含会导致跳出当前执行范围的goto,break 或 continue语句。
del += () => {int t = 0; break; return true; };
//这里会出现如下语法错误 Control cannot leave the body of ananonymous method or lambda expression
6.C# Lambda Expressions学习的总结
Lambda式可以说就是另外一种形式的匿名方法。用在某些地方,会使代码更加简洁。定义一个Lambda式本质上就是定义一个委托的实现体。
Lambda表达式学习
Lambda表达式可以简化C#编程的某些方面,用法非常灵活。因此也不容易掌握。
1、Lambda表达式是与委托紧密联系的。只要有委托参数类型的地方,就可以使用Lambda表达式。
Lambda表达式的运算符是=>。运算符左边列举出了需要的参数,右边定义了赋予Lambda变量的方法的实现代码。
下面这段代码是一个最简单的使用方法:
[csharp] view plaincopy
1. public class MyLambda
2. {
3. public void disPlay()
4. {
5. string mid = ",middle part,";
6. Func<string, string> lambda = param =>
7. {
8. param += mid;
9. param += "and this was added to the string";
10. return param;
11. };
12. Console.WriteLine(lambda("Start of string"));
13. }
14. }
Func<string,string>是一个委托类型。包含两个参数:一个输入的string和一个输出的string.
param是输入参数,所以他的类型可以认为是string类型的(当然很多地方会出现没有名字的类型的)。
运算符=>右边表示一个方法,这个方法没有名字。这个方法赋予了变量lambda。
在本例中,通过Console.WriteLine(lambda("Startof string"));向lambda方法传递参数“Start ofstring”.经处理后会有这样的输出:
Startof string,middle part,and this was added to the string。
所以我想这样理解:Lambda表达式表示是一个函数的另一种写法。运算符=>左边是输入,右边是函数体。函数体需不需要返回值,由接收这个函数定义的lambda表达式的委托决定。
2、Lambda表达式的参数。
lambda表达式如果只有一个参数,只写出参数名就行了。下边的lambda表达式使用了参数s,因为委托类型定义了一个string类型。所以s的类型就是string。实现函数体的代码调用了String.Format()方法返回一个字符串,在调用委托是就直接把字符串输出了:
[csharp] view plaincopy
1. Func<string, string> oneParam = s =>
2. {
3. return String.Format("Change To UpperCase {0}", s.ToUpper());
4. };
5.
6.
7. Console.WriteLine( oneParam("abc"));
注:Func<string,string>是一个系统定义的带有一个输入和一个输出的委托。
如果委托有多个参数,就要把参数放到括号中,如下:
[csharp] view plaincopy
1. Func<double, double, double> twoParam = (x, y) =>
2. {
3. return x * y;
4. };
3、单行代码和多行代码
如果Lambda表达式只有一条语句,在方法块内就不需要花括号和return语句了,编译器会自动添加一条隐式的return语句。如上边的两个表达式可以分别表示为:
[csharp] view plaincopy
1. Func<string, string> oneParam = s => String.Format("Change To UpperCase {0}", s.ToUpper());
2. Func<double, double, double> twoParam = (x, y) => x * y;
但是如果Lambda表达式实现代码需要多条语句时,就必须添加花括号和return语句了。如第一个例子的代码。
4、Lambda表达式的外部变量
Lambda表达式可以使用外部变量。但使用时应该注意些问题:
先看下边的代码:
[csharp] view plaincopy
1. int someVar = 5;
2. Func<int, int> f = x => x + someVar;
3. someVar = 10;
4. Console.WriteLine(f(5));
输出应该是x+5还是x+10呢?运行下发现输出15(x+10),即修改外部变量someVar后,会使用外部变量的新值。
对于表达式x => x+ someVar; 编译器会创建一个匿名类,他有一个构造函数来传递外部变量。该构造函数取决于从外部传递过来的变量个数。对于上边这个例子,我们可以认为构造函数接受一个int,匿名类包含了一个匿名方法,其实现代码和返回类型有lambda表达式定义:
[csharp] view plaincopy
1. public class AnonymousClass
2. {
3. private int someVar;
4. public AnonymousClass(int someVar)
5. {
6. this.someVar = someVar;
7. }
8. public int AnonymousMethod(int x)
9. {
10. return x + someVar;
11. }
12. }
使用Lambda表达式时,调用该方法(AnonymousMethod(int x)),会创建匿名类的一个实例(相当于newAnonymousClass(somevar)),并传递调用该方法时的变量的值。
5、泛型Action<T>委托和Func<T>委托是系统定义的两个泛型委托。
Action<T>委托表示引用一个返回类型为Void的方法。这个委托存在不同的变体,可以传递最多16个不同的参数类型。同时,没有泛型参数的Action类可以调用没有参数的方法。例如,Action<inT>表示有一个输入参数的方法,Action<inT1,in T2>表示有两个输入参数的方法。
Func<T>可以以类似的方法使用。不过Func<T>允许调用带返回参数的方法。Func<T>也有不同的变体,最多可以传递16个参数和一个返回类型。例如:Func<outTResult>委托类型可以无参的带返回类型的方法,Func<inT1,inT2,out Tresult>表示带两个参数和一个返回类型的方法。
需要记住一个东西,Action<T>中的T可以有多个,但这些T类型都表示不同的输入类型。Func<T>可以表示带输出的方法,T可以有多个,且只有最后一个表示输出即最后一个是返回类型。Func<inT1,inT2,out Tresult>中的字符in、out在实际代码中是不会出现的。在VS中,可以通过IntelliSense查看:
Action<T>的16参数封装:
Func<T>16参加一个返回的封装
下边通过一个简单的代码演示一下这两个泛型委托与一般委托的异同。
第一步:先定义俩函数:
[csharp] view plaincopy
1. public double MultiplyByTwo(double x)
2. {
3. return x * 2;
4. }
5. public double Square(double x)
6. {
7. return x * x;
8. }
这俩函数有共同的特征:输入和返回类型都是double
第二步:定义委托数组,并用这两个方法的方法名初始化数组:
[csharp] view plaincopy
1. delegate double DoubleOp(double x);
2.
3. DoubleOp[] MyDoubleOp =
4. {
5. this.MultiplyByTwo,
6. this.Square
7. };
8.
9.
10. Func<double, double>[] myFunc = //
11. {
12. this.MultiplyByTwo,
13. this.Square
14. };
最后:查看输出
[csharp] view plaincopy
1. for (int i = 0; i < MyDoubleOp.Length; i++)
2. {
3. Console.WriteLine(MyDoubleOp[i](1.414));
4. }
5.
6.
7. for (int i = 0; i < myFunc.Length; i++)
8. {
9. Console.WriteLine(myFunc[i](2.236));
10. }
比较一下,其实泛型委托和自定义的委托在使用上没什么不同。只不过泛型委托Func<T>系统已经为我们定义好了,直接使用就可以了,不需要再进行这样的定义delegate doubleDoubleOp(double x);。另一个泛型委托Action <T>的使用也是一样的,只不过不能有返回类型而已。
贴出完整的代码:一个简单的类MyClass类:
[csharp] view plaincopy
1. public class MyClass
2. {
3. delegate double DoubleOp(double x);
4.
5. public double MultiplyByTwo(double x)
6. {
7. return x * 2;
8. }
9. public double Square(double x)
10. {
11. return x * x;
12. }
13.
14. public void myDelegate()
15. {
16. DoubleOp[] MyDoubleOp =
17. {
18. this.MultiplyByTwo,
19. this.Square
20. };
21.
22. for (int i = 0; i < MyDoubleOp.Length; i++)
23. {
24. Console.WriteLine(MyDoubleOp[i](1.414));
25. }
26.
27. Func<double, double>[] myFunc =
28. {
29. this.MultiplyByTwo,
30. this.Square
31. };
32. for (int i = 0; i < myFunc.Length; i++)
33. {
34. Console.WriteLine(myFunc[i](2.236));
35. }
36. }
37. }
入口函数:
[csharp] view plaincopy
1. class Program
2. {
3. static void Main()
4. {
5. EventsSample.MyClass myClass = new EventsSample.MyClass();
6. myClass.myDelegate();
7. Console.Read();
8.
9. }
10.
11. }
可见,在.net3. 5里面 ,委托的定义和实现被大大的简化了!使用关键字Func或Action就可以定义一个委托 , 使用Lambda表达式就可以实现一个具体的委托。
Func关键字是用来定义一个有返回值的委托 , 它一共有五个重载 , 我们介绍其中的三个
publicdelegate TResult Func<TResult>();
这表示一个没有参数 , 只有一个返回值的委托 , 返回值的类型就是TResult(泛型)
代码
1 public class test
2 {
3 /// <summary>
4 /// 定义一个委托
5 /// </summary>
6 public Func<string> _GetName;
7 /// <summary>
8 /// 一个普通的没有参数 , 有返回值的方法
9 /// </summary>
10 /// <returns></returns>
11 public string GetName()
12 {
13 return "张三";
14 }
15 public void Main()
16 {
17 //3. 5以前的委托的实现 , 直接赋值
18 _GetName = GetName;
19
20 //拉姆达表达式的实现方法
21 _GetName = (
22 () //因为这个委托没参数 , 所以参数列表没有东西
23 = > //拉姆达表达式的符号
24 { //大括号的代码段表示具体的委托的实现
25 return "还是张三";
26 });
27 //拉姆达表达式的简写 , 如果委托的实现代码段中只有一句return 则可以省略代码段最外面的大括号 , 和return关键字
28 _GetName = () = > "总是张三";
29 //调用
30 string MyName = _GetName();
31 }
32 }
public delegate TResult Func<T , TResult>(T arg);
这表示有且仅有一个参数 , 并且有返回值的委托.
代码
1 public class test
2 {
3 /// <summary>
4 /// 定义一个委托 , 有一个参数和一个返回值
5 /// </summary>
6 public Func<string , string> _GetName;
7 /// <summary>
8 /// 有一个参数的方法
9 /// </summary>
10 /// <param name = "strName">这是一个参数!</param>
11 /// <returns>这是一个返回值</returns>
12 public string GetName(string strName)
13 {
14 return strName;
15 }
16 public void Main()
17 {
18 //3. 5以前的委托的实现 , 直接赋值
19 _GetName = GetName;
20
21 //拉姆达表达式的实现方法
22 _GetName = (
23 (S) //有一个参数!所以参数列表里面有一个东西 , 这个东西随大家高兴叫个阿猫阿狗都行!只要符合规范
24 = > //拉姆达表达式的符号
25 { //大括号的代码段表示具体的委托的实现
26 return "还是" + S;
27 });
28 //拉姆达表达式的简写 , 如果委托的实现代码段中只有一句return 则可以省略代码段最外面的大括号 , 和return关键字
29 _GetName = (abc) = > "总是" + abc;
30 //调用
31 string MyName = _GetName("张三");
32 }
33 }
public delegate TResult Func<T1 , T2 , TResult>(T1 arg1 , T2 arg2);
这表示有且仅有两个参数 , 并且有返回值的委托.
代码
1 public class test
2 {
3 /// <summary>
4 /// 定义一个委托 , 有一个参数和一个返回值
5 /// </summary>
6 public Func<string , int , string> _GetName;
7 /// <summary>
8 /// 这是一个有两个参数的方法 , 方法的参数类型的顺序必须和委托的参数类型顺序一致
9 /// </summary>
10 /// <param name = "strName">第一个是字符类型</param>
11 /// <param name = "intAGE">第二个是整形 , 请不要颠倒类型!</param>
12 /// <returns>返回一个字符串 , 对应委托的最后一个参数</returns>
13 public string GetName(string strName , int intAGE)
14 {
15 return string. Format("{0}的年龄是{1}岁" , strName , intAGE);
16 }
17 public void Main()
18 {
19 //3. 5以前的委托的实现 , 直接赋值
20 _GetName = GetName;
21
22 //拉姆达表达式的实现方法
23 _GetName = (
24 (S, W) //有一个参数!所以参数列表里面有一个东西 , 这个东西随大家高兴叫个阿猫阿狗都行!只要符合规范
25 = > //拉姆达表达式的符号
26 { //大括号的代码段表示具体的委托的实现
27 return string. Format("{0}的年龄是{1}岁" , S , W);
28 });
29 //拉姆达表达式的简写 , 如果委托的实现代码段中只有一句return 则可以省略代码段最外面的大括号 , 和return关键字
30 _GetName = (abc , efd) = > string. Format("{0}的年龄是{1}岁" , abc , efd);
31 //调用
32 string MyName = _GetName("张三" , 33);
33 }
34 }
Action关键字用来定义一个没有返回值的方法 , 它有一个非泛型方法 , 和四个泛型方法 , 一共五种. Action和func的区别就在于一个没有返回值 , 一个有返回值!其他的都一样!就好像VB的sub和function一样!
public delegate void Action(); 没有参数也没有返回值
代码
1 public class test
2 {
3 /// <summary>
4 /// 定义一个委托 , 没有返回值也没有参数
5 /// </summary>
6 public Action _GetName;
7
8 public void GetName()
9 {
10 System.Windows. Forms. MessageBox. Show("没有参数也没有返回值 , 我只要自己显示了!");
11 }
12 public void Main()
13 {
14 //3. 5以前的委托的实现 , 直接赋值
15 _GetName = GetName;
16
17 //拉姆达表达式的实现方法
18 _GetName = (
19 ()
20 = > //拉姆达表达式的符号
21 { //大括号的代码段表示具体的委托的实现
22 System.Windows. Forms. MessageBox. Show("没有参数也没有返回值 , 我只要自己显示了!");
23 });
24 //因为action没有返回值 , 所以下面的简写方式是错误的
25 //_GetName = () = > System. Windows. Forms. MessageBox. Show("没有参数也没有返回值 , 我只要自己显示了!");
26 //调用
27 _GetName();
28 }
29 }
public delegatevoid Action<T>(T obj); 有一个参数但没有返回值
代码
1 public class test
2 {
3 /// <summary>
4 /// 定义一个委托 , 没有返回值也没有参数
5 /// </summary>
6 public Action<bool> _GetName;
7
8 public void GetName(bool blnShow)
9 {
10 if (blnShow)
11 {
12 System.Windows. Forms. MessageBox. Show("要我显示就显示 , 多没面子");
13 }
14 else
15 {
16 System.Windows. Forms. MessageBox. Show("不要我显示 , 我偏要显示");
17 }
18 }
19 public void Main()
20 {
21 //3. 5以前的委托的实现 , 直接赋值
22 _GetName = GetName;
23
24 //拉姆达表达式的实现方法
25 _GetName = (
26 (b)
27 = > //拉姆达表达式的符号
28 { //大括号的代码段表示具体的委托的实现
29 if (b)
30 {
31 System.Windows. Forms. MessageBox. Show("要我显示就显示 , 多没面子");
32 }
33 else
34 {
35 System.Windows. Forms. MessageBox. Show("不要我显示 , 我偏要显示");
36 }
37 });
38
39 _GetName(true);
40 }
41 }
6、C# 中动态构建 Lambda Expressions
C# Lambda表达式的概念大家一定已经清楚了,那么如何动态生成C# Lambda表达式呢?具体的操作是什么呢?有什么需要注意的呢?那么本文就向你介绍具体的内容。
对于C# Lambda的理解我们在之前的文章中已经讲述过了,那么作为Delegate的进化使用,为了让代码简洁和优雅的呈现,C# Lambda表达式的使用功不可灭,那么依托外部条件如何动态构建C# Lambda表达式呢。下面让我们来具体的看看实施。
或许你会奇怪这个需求是如何产生的…… 首先,Lambda 在 DLinq 中承担了以往 T-SQL 的部分角色;其次,在数据库设计中,我们往往需要依据外部未知的动态条件组合来查询数据。而问题在于作为一种静态语言,我们显然无法用动态语法或者拼接字符串的方法来创建一个Delegate/Lambda,那么如何达到类似的目的呢?CodeDom?Emit?或许最佳的选择是 System.Linq.Expressions。
1)首先我们了解一个简单C# Lambda表达式的构成。
i => i > 5
在这个表达式中,"i" 被称为 Parameter,"i > 5" 是 Body。我们可以对 Body 进行更进一步的分解,那么 "i > 5" 分别包含参数(i)、操作符(>)以及一个常数(5)。所有这些通过特定顺序的组合,从而构建一个完整的 Lambda 表达式。
2)我们通过一些例子,来学习如何动态构建C# Lambda表达式。
动态构建C# Lambda表达式例子1
varints = new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//var r =ints.Where(i => i > 5); // 要实现的表达式
// 创建参数 i
varparameter = Expression.Parameter(typeof(int), "i");
// 创建常量5
varconstant = Expression.Constant(5);
// 创建比较表达式 i > 5
varbin = Expression.GreaterThan(parameter,constant);
// 获取Lambda表达式
varlambda = Expression.Lambda<Func<int, bool>>(bin, parameter);
// 通过 Compile 方法获取Delegate
var_r = ints.Where(lambda.Compile());
foreach(var n in _r)
{
Console.WriteLine(n.ToString());
}
在代码中设置断点,我们可以看到调试器中显示的表达式信息。
.NET FX 3.5 中为 Lambda 新增了一些委托类型。
(1) 用于处理无返回数据的 Action。
public delegatevoid Action()
public delegatevoid Action<T> (T arg)
public delegatevoid Action<T1, T2> (T1 arg1, T2 arg2)
public delegatevoid Action<T1, T2, T3> (T1 arg1,T2 arg2, T3 arg3)
public delegatevoid Action<T1, T2, T3, T4> (T1arg1, T2 arg2, T3 arg3, T4 arg4)
(2) 用于处理带返回数据的 Func。
public delegateTResult Func<TResult> ()
public delegateTResult Func<T, TResult> (Targ)
public delegateTResult Func<T1, T2, TResult> (T1 arg1, T2 arg2)
public delegateTResult Func<T1, T2, T3,TResult> (T1 arg1, T2 arg2, T3arg3)
public delegateTResult Func<T1, T2, T3, T4,TResult> (T1 arg1, T2 arg2, T3 arg3,T4 arg4)
动态构建C# Lambda表达式例子2
下面我们看一个比较复杂的例子:
varints = new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//var r =ints.Where(i => i > 5 && i <= 7); // 要实现的表达式
// 创建参数 i
varparameter = Expression.Parameter(typeof(int), "i");
// 创建表达式 i > 5
varcon1 = Expression.Constant(5);
varbin1 = Expression.GreaterThan(parameter,con1);
// 创建表达式 i <= 7
varcon2 = Expression.Constant(7);
varbin2 = Expression.LessThanOrEqual(parameter,con2);
// 组合两个表达式
varbody = Expression.And(bin1, bin2);
// 获取 Lambda 表达式
varlambda = Expression.Lambda<Func<int, bool>>(body, parameter);
var_r = ints.Where(lambda.Compile());
在例子2中,我们对复杂的表达式进行了分解,并使用 And 完成多个表达式的组装,由此我们可以创建更加复杂的逻辑组合,比如例子3。
动态构建C# Lambda表达式例子3
// 创建参数 i
varparameter = Expression.Parameter(typeof(int), "i");
// 创建表达式 i > 5
varcon1 = Expression.Constant(5);
varbin1 = Expression.GreaterThan(parameter,con1);
// 创建表达式 i <= 7
varcon2 = Expression.Constant(7);
varbin2 = Expression.LessThanOrEqual(parameter,con2);
// 创建表达式 i == 3
varcon3 = Expression.Constant(3);
varbin3 = Expression.Equal(parameter, con3);
// 组合 i > 5 && i <= 7
varbody = Expression.And(bin1, bin2);
// 组合 ( i > 5 && i <= 7) OR (i ==3)
body = Expression.Or(body,bin3);
varlambda = Expression.Lambda<Func<int, bool>>(body, parameter);
var_r = ints.Where(lambda.Compile());
我们继续看几个常见的例子。
动态构建C# Lambda表达式例子4
varints = new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//var r =ints.Select(i => i % 2 == 0 ? i : 0); // 要实现的表达式
// 创建参数 i
varparameter = Expression.Parameter(typeof(int), "i");
// 创建表达式 i % 2
varcon1 = Expression.Constant(2);
varbin1 = Expression.Modulo(parameter, con1);
// 创建表达式 (i % 2) == 0
varcon2 = Expression.Constant(0);
varbin2 = Expression.Equal(bin1, con2);
// 创建表达式 IIF(((i % 2) = 0), i, 0)
varbin3 = Expression.Condition(bin2, parameter,Expression.Constant(0));
varlambda = Expression.Lambda<Func<int, int>>(bin3, parameter);
var_r = ints.Select(lambda.Compile());
动态构建C# Lambda表达式例子5
varints = new int[]{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// var r= ints.Where(i => (i > 5 && i <= 7) || (i == 3)); // 要实现的表达式
// 创建参数 i
varparameter = Expression.Parameter(typeof(int), "i");
// 创建表达式 i > 5
varcon1 = Expression.Constant(5);
varbin1 = Expression.GreaterThan(parameter,con1);
// 创建表达式 i <= 7
varcon2 = Expression.Constant(7);
varbin2 = Expression.LessThanOrEqual(parameter,con2);
// 创建表达式 i == 3
varcon3 = Expression.Constant(3);
varbin3 = Expression.Equal(parameter, con3);
// 组合 i > 5 && i <= 7
varbody = Expression.And(bin1, bin2);
// 组合 ( i > 5 && i <= 7) OR (i ==3)
body = Expression.Or(body,bin3);
varlambda = Expression.Lambda<Func<int, bool>>(body, parameter);
var_r = ints.Where(lambda.Compile());
是该花点时间去好好研究一下 System.Linq.Expressions Namespace 了……
动态构建C# Lambda表达式的基本内容就向你介绍到这里,希望那个对你了解和掌握使用动态构建C# Lambda表达式有所帮助。
7、集合操作实例
项目里面需要经常对一系列同类型集合进行操作 , 如对集合进行增加元素 , 删除集合的指定索引的元素等等. 我们可以使用ArrayList来进行. 如
1 ArrayList stringArrayList= new ArrayList();
2 stringArrayList. Add("大家好");
3 stringArrayList. Add("你们好");
4 stringArrayList. Add("同志们好");
5 string str1 = (string)stringArrayList[0];//取出一个元素后 , 需要转换一次类型才可以
或者是
1 ArrayList intArrayList = new ArrayList();
2 intArrayList. Add(6);
3 intArrayList. Add(8);
4 intArrayList. Add(66);
5 int int1 = (int)intArrayList[0];//取出一个元素后 , 需要转换一次类型才可以
但是ArrayList中每个元素的类型都是Object(stringArrayList[0]的类型是Object) , 这意味着我们每一次的操作 , 其实都进行了隐式的类型转换 , 加入资料是把普通类型转换成Object类型 , 取出资料是把Object类型转换成普通类型.
于是我在想象 , 如果有一种数组类型 , 在定义的时候 , 可以给出每个元素具体的类型 , 并且在赋值或者取值的时候 , 就是完全按照这个类型进行操作该多好.
在. net2. 0里面 , 我找到了List这个类型. List是一个泛型 , 我们看看List的使用
1 List<string> stringArrayList = new List<string>();
2 stringArrayList. Add("大家好");
3 string str1 = stringArrayList[0];//直接赋值成功!因为取出来的就是string
4 //或者
5 List<int> intArrayList = new List<int>();
6 intArrayList. Add(8);
7 int int1 = intArrayList[0];//直接赋值成功!因为取出来的就是int
大家可以看出 , List在实例化的时候就需要定义一个类型 , 也就是尖括号中间的东西 , 在增加元素 , 或者获取元素的时候 , 操作的都是最开始定义的那种类型. List便是传说中的泛型类型.
泛型可以用在方法上 , 也可以用在类上. 如果看到某个方法或者类后面带有尖括号的 , 那么这个肯定就是泛型了.
现在 , 我找到了能够有效存储我要操作的集合类型 , 那么我们要解决一些操作了.
我需要对集合进行一个连接输出(把所有的元素连接在一起 , 每个元素之间使用<BR>来分割) , 还需要知道所有元素的总长度. 显然 , 光一个List类型是解决不了问题的. 于是我自己定义了一个自己的泛型类型
1 /// <summary>
2 /// 这是一个泛型类 , 类名后面接着一个尖括号里面的那个t , 是我们自己定义的 , 如果你高兴 , 你可以定义w , y , z , WC都没有问题!
3 /// 这个T表示说我们在实例化类的时候 , 需要告诉类 , 我们是用哪一种类型来进行操作.
4 /// </summary>
5 /// <typeparam name ="T"></typeparam>
6 public class MyList<T>
7 {
8 public List<T> _List { get; set; }
9 public MyList()
10 {
11 this. _List = new List<T>();
12 }
13 /// <summary>
14 /// 用来连接所有元素用
15 /// </summary>
16 /// <returns>连接后的字符串</returns>
17 public string JoinOut()
18 {
19 StringBuilder stbTemp = new StringBuilder();
20 foreach (var item in _List)
21 {
22 stbTemp.Append(item);
23 stbTemp.Append("<BR>");
24 }
25 return stbTemp. ToString();
26 }
27 /// <summary>
28 /// 所有元素的长度
29 /// </summary>
30 /// <returns>元素的整体长度</returns>
31 public int AllLen()
32 {
33 StringBuilder stbTemp = new StringBuilder();
34 foreach (var item in _List)
35 {
36 stbTemp.Append(item);
37 }
38 return stbTemp.Length;
39 }
40
41 }
但是如果我在求元素长度的时候 , 要求如果是stirng则返回所有元素的长度 , 而是int的时候 , 则返回所有元素的和. 于是我重写了AllLen方法
1 public int AllLen()
2 {
3 //StringBuilder stbTemp = new StringBuilder();
4 //foreach (var item in _List)
5 //{
6 // stbTemp.Append(item);
7 //}
8 //return stbTemp.Length;
9
10 StringBuilder stbTemp = new StringBuilder();
11 var type = typeof(T);
12 if (type = = typeof(string))
13 {
14 foreach (var item in _List)
15 {
16 stbTemp.Append(item);
17 stbTemp.Append("<BR>");
18 }
19 return stbTemp.Length;
20 }
21 if (type = = typeof(int))
22 {
23 int intSum = 0;
24 foreach (var item in _List)
25 {
26 intSum += int. Parse(item.ToString());
27 }
28 return stbTemp.Length;
29 }
30
31 /* 这里可能还需要根据不同的类型进行不同的处理
32 */
33 return 0;
34 }
我在整个项目中 , 会负责编写公用类库. 我不知道其他前台编码人员需要什么样子的操作. 并且前台编码人员会各处一些稀奇古怪的需求我 , 要我实现 , 如他想接受一系列的bool类型 , 然后判断所有结果为True的数量 , 或者传入一系列的日期 , 判断所有星期一的日期有多少个. . . 等等. 我比较懒 , 并且我非常不愿意去修改我已经写好的类库. 所以我又对Allen进行了一次修改.
1 //写一个委托 , 谁愿意做什么操作就自己写去 , 哥不管了!
2 public delegate int delegateAllLen<T>(List<T> list);
3 //写一个委托 , 谁愿意做什么操作就自己写去 , 哥不管了!
4 public delegateAllLen<T> FuncAllLen { get; set; }
5 public int AllLen()
6 {
7 if (FuncAllLen != null)
8 {
9 return FuncAllLen(_List);
10 }
11 return 0;
12 }
我告诉前台编码人员 , 你们想做什么就先去实现委托FuncAllLen. 然后调用AllLen方法.
1 /// <summary>
2 /// 委托的实现
3 /// </summary>
4 /// <param name = "bln"></param>
5 /// <returns></returns>
6 public int Temp(List<bool> bln)
7 {
8 int i = 0;
9 foreach (var item in bln)
10 {
11 if (item) i++;
12 }
13 return i;
14 }
15
16 public void Main()
17 {
18 var list = new MyList<bool>();
19
20 list._List. Add(true);
21 list._List. Add(false);
22 list._List. Add(true);
23 ///实现委托
24 list.FuncAllLen + = Temp;
25 MessageBox.Show(list. AllLen(). ToString());
26 }
现在我就轻松多了 , 可以去睡大觉了!所有的具体操作 , 前台编码人员自己去实现FuncAllLen 这个委托去!我全部不管了!哈哈哈!
不过这样写可能还是有有点难以理解. 一会定义一个delegate intdelegateAllLen<T>(List<T> list);一会又是delegateAllLen<T> FuncAllLen {get; set; } , 都不知道那个是那个. . . .
于是我采用C#3. 5中委托的写法
1 /*
2 public delegate int delegateAllLen<T>(List<T> list);
3 public delegateAllLen<T> FuncAllLen { get; set; }
4 以上这两句 , 可以简写成下面的一句!
5 */
6 public Func<List<T>, int> FuncAllLen { get; set; }
调用的方法和以前一样 , 可是编码人员告诉我:这样你方便了 , 我们可就麻烦了 , 每次都要记得在使用AllLen方法的时候 , 都要先把委托实现了. 特别是新来的人 , 总是记不住.
正好 , 最近我在学习Linq , c#3. 5中推出了拉姆达表达式 , 可以让委托更简单的实现!于是我最后一次重写AllLen方法
1 //我要使用最先进 , 最流行的拉姆达表达式!所以下面的这行委托我不需要了!哈哈哈哈
2 //public Func<List<T>, int> FuncAllLen { get; set; }
3
4 //其实我把上面的委托定义放到函数里面当参数了. . . .
5 public int AllLen(Func<List<T>, int> FuncAllLen)
6 {
7 if (FuncAllLen != null)
8 {
9 return FuncAllLen(_List);
10 }
11 return 0;
12 }
最后我们看看调用的方法
1 public void Main()
2 {
3 var list = new MyList<bool>();
4 list. _List. Add(true);
5 list. _List. Add(false);
6 list. _List. Add(true);
7
8 //传说中的拉姆达表达式出现了!!!!!!
9 int intRef = list. AllLen(
10 PList = >
11 {
12 int i = 0;
13 foreach (var item in PList)
14 {
15 if (item) i++;
16 }
17 return i;
18 });
19 }
具体我们来看看拉姆达表达式的用法!
拉姆达表达式由三个部分组成 , = >是拉姆达中固定的符号 , 必须出现!
= >左边的表达式是一个参数列表 , 是一组没有类型的字符(字符怎么写随意!只要符合命名规范就好了) , 每个字符表示一个参数 , 每个参数之间使用逗号分割.
例:如果有三个参数 , 则表达式为(A , B , C) , 或者是(P1 , P2 , P3) ,
= >右边的是具体要实现的代码段 , 代码段里面可以使用参数列表中的参数进行各种运算.
例:{return P1+P2+p3;}
合起来就是 (P1 , P2 , P3) =>{return P1+P2+P3;}
如果参数只有一个 , 那么省去小括号:P1 = >{returnP1+10;}
如果具体的实现代码只有一句返回语句 , 则可以简写成 P1 = >P1+10;
一定要注意 , 拉姆达表达式只是一个委托的定义而已 , 当程序运行到拉姆达表达式的时候 , 拉姆达表达式里面的语句是不会被立刻执行的 , 很多人在初学拉姆达或者委托的时候都会犯这种错误. 如:
1 public void Main()
2 {
3 var intSumTemp = Sum((tempInt) = > { return tempInt + 1; });
4 }
5
6 public static int Sum(Func<int , int> func)
7 {
8 var int1 = 5;
9 int1+ = 5;
10 var intTemp = func(int1);
11 return intTemp * intTemp;
12 }
上面的intSumTemp的结果是121.
运行的顺序是:首先调用Sum方法而不会去执行Lambda表达式,然后得到int1 = 10的结果(5+5) ,
接着需要运行func了 , 并且知道func的参数值是int1 , 即10.
那么func是通过拉姆达表达式定义的 , 所以这个时候才会执行Lambda表达式, 我们把10传入Lambda表达式中 , 进行运算得到11(10+1),法最后是一个平方操作. 结果为121(11*11)
知道Lambda的写法 , 和使用的方法 , 那么我们在什么情况下可以使用拉姆达表达式呢?
当我们在使用一个方法 , 方法的参数是Func , 或Action , 那么就可以使用拉姆达表达式了!
我们拿linq里面的方法举例!
public static IEnumerable<TSource> Where<TSource>(thisIEnumerable<TSource> source , Func<TSource , bool>predicate); 可写成 var temp =_List. Where(P = >{return true;});
public static int Sum<TSource>(thisIEnumerable<TSource> source , Func<TSource , int>selector); 可写成 var temp =_List. Sum(P = >P. count);