Lambda表达式与委托直接相关,当参数是委托类型时,就可使用Lambda表达式引用方法.sql
委托的本质 是class,能够查看IL代码数据库
委托声明决定了可由该委托引用的方法,委托可指向一个与其具备相同标签的方法。
例如,假设有一个委托:express
public delegate int MyDelegate (string s);
理解委托,须要理解如下几点:编程
实例化委托:
给委托变量赋值时,千万不要将方法带括号,带括号意味赋值是方法的结果,因此直接赋值方法名。c#
Action:它表示引用一个void返回类型的方法。这个委托类存在不一样的变体,能够传递至多16种不一样的参数类型。数组
Action | 说明 |
---|---|
Action | 调用没有参数的方法 |
Action<in T1,> | 调用带一个参数的方法, T1后面不带逗号,由于网页显示不出来,因此加上去 |
Action<in T1,in T2> | 调用带两个参数的方法, |
Action<in T1,in T2,in T3> | 调用了带3个参数的方法 |
...... | 直到调用了带16个参数的方法 |
Func:它表示引用一个必有返回类型的方法。这个委托类存在不一样的变体,能够传递至多16种不一样的参数类型,但必须有一个返回值。安全
Func | 说明 |
---|---|
Func<out TResult,> | 调用没有参数的方法,可是有返回值,TResult后面不带逗号,由于网页显示不出来,因此加上去 |
Action<in T,out TResult > | 调用带一个参数的方法,返回一个值 |
Action<in T1,in T2,out TResult> | 调用带两个参数的方法,返回一个值 |
Action<in T1,in T2,in T3,out TResult> | 调用了带3个参数的方法,返回一个值 |
...... | 直到调用了带16个参数的方法 |
Predicate:表示定义一组条件并肯定指定对象是否符合这些条件的方法。
此委托由 Array 和 List 类的几种方法使用,用于在集合中搜索元素。数据结构
public delegate bool Predicate<in T>(T obj);
类型参数:T
要比较的对象的类型。参数:obj 。要按照由此委托表示的方法中定义的条件进行比较的对象。
返回值:System.Boolean。若是 obj 符合由此委托表示的方法中定义的条件,则为 true;不然为 false。
继承:ObjectDelegatePredicate
示例:
using System; using System.Drawing; public class Example { public static void Main() { // Create an array of Point structures. Point[] points = { new Point(100, 200), new Point(150, 250), new Point(250, 375), new Point(275, 395), new Point(295, 450) }; // Define the Predicate<T> delegate. Predicate<Point> predicate = FindPoints; // Find the first Point structure for which X times Y // is greater than 100000. Point first = Array.Find(points, predicate); // Display the first structure found. Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y); } private static bool FindPoints(Point obj) { return obj.X * obj.Y > 100000; } } // The example displays the following output: // Found: X = 275, Y = 395
下面的示例等同于上一示例中,只不过它使用 lambda 表达式来表示Predicate
using System; using System.Drawing; public class Example { public static void Main() { // Create an array of Point structures. Point[] points = { new Point(100, 200), new Point(150, 250), new Point(250, 375), new Point(275, 395), new Point(295, 450) }; // Find the first Point structure for which X times Y // is greater than 100000. Point first = Array.Find(points, x => x.X * x.Y > 100000 ); // Display the first structure found. Console.WriteLine("Found: X = {0}, Y = {1}", first.X, first.Y); } } // The example displays the following output: // Found: X = 275, Y = 395
若是要调用多个方法,就须要屡次显式调用这个委托。可是委托也能够包含多个方法,这种委托称为多播委托。
若是调用多播委托就能够按顺序连续调用多个方法。为此,委托的签名就必须返回void;不然,就只能获得委托调用的最后一个方法的结果。
例如:
因此当调用委托3时,委托1和委托2会同时执行,与第一种实现多播委托一个道理。
可是多播委托还有一个缺陷,一旦一个委托发生异常,其余委托都会中止。为了不这个问题,应本身迭代方法列表。Delegate类定义GetInvocationList()方法,它返回一个Delegate对象数组。如今可使用这个委托调用与委托直接相关的方法,捕获异常,并继续下一次迭代。(.net core 中间件)
到目前为止,要想使委托工做,方法名必须已经存在。但还有另一种使用委托的方式:匿名方法。匿名方法是用做委托的参数的一段代码,用匿名方法定义委托的语法与前面的定义并无区别,但在实例化委托时,就有了区别。下面是一个很是简单的代码,它说明了如何使用匿名方法:
Func<string,string> anonDel=delegate(string param) { param+=mid; param+=" and this was added to the string."; return param; }
使用匿名方法的规则很明显,它前面是关键字delegate,后面是一个字符串参数.
使用匿名方法
匿名方法语法
delegate (parameters ){implementationcode};
关键字 参数 方法体
匿名方法不会声明返回值类型。可是匿名方法返回值类型必须和委托返回值同样。
进阶
咱们可使圆括号为空,或省略圆括号来简化匿名方法的参数列表。可是仅在下面两项都为真的状况下才能够这么作。
class Program { delegate int otherdel(int param); public static void Main() { otherdel del = delegate { cleanup(); printMessage(); }; } }
params参数
若是委托参数包含params参数,那么params关键字就会被匿名方法的参数列表忽略。以下:
delegate int otherdel(int x,params int y); otherdel del = delegate(int x,int y) { };
从c#3.0开始,就可使用一种新语法把实现代码赋予委托:Lambda表达式
只要有委托参数类型的地方,就可使用Lambda表达式。前面使用匿名方法的例子能够改成使用Lambda表达式。
Lambda表达式的语法比匿名方法简单.若是所调用的方法有参数,且不须要参数,匿名方法的语法就比较简单,由于这样不须要提供参数
定义:Labmda表达式有几种定义参数的方式.
Func<string,string> oneParam=s=>string.Format("change uppercase{0}",s.ToUpper()); Console.WriteLine(oneParam("test"));
Func<double,double,double> twoParams=(x,y)=>x*y; console.WriteLine(twoParams(3,2));
func<double,double,double> twoParamsWithTypes=(double x,double y)=>x*y ; Console.Write(twoParamsWithTypes(2,3));
Func<double,double> square=x=>{ return x*x };
闭包
经过Lambda表达式能够访问表达式外部的变量,这称为闭包。闭包是一个很是好的功能,但若是未正确使用,也会很是危险。 多线程使用时就会出兮兮,由于不知道此时外部变量是什么。
方法泛型集合定义:List<Func<int>>();
lambda表达式是被视为一个对象的代码(表达式或语句块)的块。它能够做为参数传递给方法,也能够经过方法调用返回。Lambda表达式普遍用于:
lambda表达式除了能够分配给委托类型外,还能够分配给表达式树类型,如:
System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x; Console.WriteLine(e); // Output: // x => (x * x)
或者能够直接将其做为方法参数传递:
int[] numbers = { 2, 3, 4, 5 }; var squaredNumbers = numbers.Select(x => x * x); Console.WriteLine(string.Join(" ", squaredNumbers)); // Output: // 4 9 16 25
当您使用基于方法的语法来调用System.Linq.Enumerable类中的Enumerable.Select方法时(就像在LINQ to Objects和LINQ to XML中那样),参数是委托类型System.Func <T,TResult>。lambda表达式是建立该委托的最便捷方式。
当您在System.Linq.Queryable类中调用Queryable.Select方法时(就像在LINQ to SQL中那样),参数类型是表达式树类型。一样,lambda表达式只是构造表达式树的一种很是简洁的方式。lambda容许调用看起来相似,但实际上从lambda建立的对象类型是不一样的。Expression<Func<TSource,TResult>>Select
在=>运算符右侧具备表达式的lambda表达式称为表达式lambda。表达式lambda普遍用于构建表达树。表达式lambda返回表达式的结果,并采用如下基本形式:
(inputParameters) => expression
只有当lambda有一个输入参数时,括号才是可选的。不然他们是必需的。
使用空括号指定零输入参数:
Action line = () => Console.WriteLine();
两个或多个输入参数用括在括号中的逗号分隔:
Func<int, int, bool> testForEquality = (x, y) => x == y;
有时,编译器没法推断输入类型。您能够显式指定类型,如如下示例所示:
Func<int, string, bool> isTooLong = (int x, string s) => s.Length > x;
输入参数类型必须所有显式或所有隐式; 不然,会发生CS0748编译器错误。
表达式lambda的主体能够包含方法调用。可是,若是要建立在.NET公共语言运行库的上下文以外计算的表达式树(例如在SQL Server中),则不该在lambda表达式中使用方法调用。这些方法在.NET公共语言运行库的上下文以外没有任何意义。
语句lambda相似于表达式lambda,除了语句括在括号中:
(input-parameters) => { statement; }
语句lambda的主体能够包含任意数量的语句; 然而,在实践中一般不超过两个或三个。
Action<string> greet = name => { string greeting = $"Hello {name}!"; Console.WriteLine(greeting); }; greet("World"); // Output: // Hello World!
语句lambda与匿名方法同样,不能用于建立表达式树。
您可使用async和await关键字轻松建立包含异步处理的lambda表达式和语句。例如,如下Windows窗体示例包含一个调用和等待异步方法的事件处理程序ExampleMethodAsync。
public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click += button1_Click; } private async void button1_Click(object sender, EventArgs e) { await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; } private async Task ExampleMethodAsync() { // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); } }
可使用异步lambda添加相同的事件处理程序。要添加此处理程序,请async在lambda参数列表以前添加修饰符,如如下示例所示:
public partial class Form1 : Form { public Form1() { InitializeComponent(); button1.Click += async (sender, e) => { await ExampleMethodAsync(); textBox1.Text += "\r\nControl returned to Click event handler.\n"; }; } private async Task ExampleMethodAsync() { // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); } }
从C#7.0开始,C#语言为元组提供内置支持。你能够提供一个元组做为lambda表达式的参数,你的lambda表达式也能够返回一个元组。在某些状况下,C#编译器使用类型推断来肯定元组组件的类型。
您能够经过在括号中包含逗号分隔的组件列表来定义元组。下面的示例使用带有三个组件的元组将一系列数字传递给lambda表达式,该表达式将每一个值加倍并返回包含三个包含乘法结果的组件的元组。
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3); var numbers = (2, 3, 4); var doubledNumbers = doubleThem(numbers); Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}"); // Output: // The set (2, 3, 4) doubled: (4, 6, 8)
除了其余实现以外,LINQ to Objects还有一个输入参数,其类型是Func
当参数类型是Expression
如下示例使用Count标准查询运算符:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; int oddNumbers = numbers.Count(n => n % 2 == 1); Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
编译器能够推断输入参数的类型,也能够显式指定它。这个特殊的lambda表达式计算那些整数(n),当除以2时,余数为1。
如下示例生成一个序列,其中包含numbers数组中位于9以前的全部元素,由于这是序列中不符合条件的第一个数字:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6); Console.WriteLine(string.Join(" ", firstNumbersLessThanSix)); // Output: // 5 4 1 3
编写lambda时,一般没必要为输入参数指定类型,由于编译器能够根据lambda正文,参数类型和C#语言规范中描述的其余因素推断类型。对于大多数标准查询运算符,第一个输入是源序列中元素的类型。若是要查询IEnumerable
customers.Where(c => c.City == "London");
lambdas类型推断的通常规则以下:
lambda的返回值(若是有的话)必须能够隐式转换为委托的返回类型。
请注意,lambda表达式自己没有类型,由于公共类型系统没有“lambda表达式”的内在概念。然而,有时很是方便地谈论lambda表达式的“类型”。在这些状况下,类型引用lambda表达式转换为的委托类型或表达式类型。
Lambda能够引用外部变量(请参阅匿名方法),这些变量位于定义lambda表达式的方法的范围内,或者在包含lambda表达式的类型的范围内。以这种方式捕获的变量被存储用于lambda表达式,即便变量不然将超出范围并被垃圾收集。必须明确赋值外部变量,而后才能在lambda表达式中使用它。如下示例演示了如下规则:
public static class VariableScopeWithLambdas { public class VariableCaptureGame { internal Action<int> updateCapturedLocalVariable; internal Func<int, bool> isEqualToCapturedLocalVariable; public void Run(int input) { int j = 0; updateCapturedLocalVariable = x => { j = x; bool result = j > input; Console.WriteLine($"{j} is greater than {input}: {result}"); }; isEqualToCapturedLocalVariable = x => x == j; Console.WriteLine($"Local variable before lambda invocation: {j}"); updateCapturedLocalVariable(10); Console.WriteLine($"Local variable after lambda invocation: {j}"); } } public static void Main() { var game = new VariableCaptureGame(); int gameInput = 5; game.Run(gameInput); int jTry = 10; bool result = game.isEqualToCapturedLocalVariable(jTry); Console.WriteLine($"Captured local variable is equal to {jTry}: {result}"); int anotherJ = 3; game.updateCapturedLocalVariable(anotherJ); bool equalToAnother = game.isEqualToCapturedLocalVariable(anotherJ); Console.WriteLine($"Another lambda observes a new value of captured variable: {equalToAnother}"); } // Output: // Local variable before lambda invocation: 0 // 10 is greater than 5: True // Local variable after lambda invocation: 10 // Captured local variable is equal to 10: True // 3 is greater than 5: False // Another lambda observes a new value of captured variable: True }
如下规则适用于lambda表达式中的变量做用域:
定义 :将强类型lambda表达式表示为表达式树形式的数据结构。这个类不能被继承。
场景: 目前被大量运行在linq to sql 中。将表达式树转换成表达式,而后转换成SQL。
public sealed class Expression<TDelegate> : System.Linq.Expressions.LambdaExpression
下面的代码示例演示如何将lambda表达式表示为委托形式的可执行代码和表达式树形式的数据。它还演示了如何使用Compile方法将表达式树转换回可执行代码。
// Lambda expression as executable code. Func<int, bool> deleg = i => i < 5; // Invoke the delegate and display the output. Console.WriteLine("deleg(4) = {0}", deleg(4)); // Lambda expression as data in the form of an expression tree. System.Linq.Expressions.Expression<Func<int, bool>> expr = i => i < 5; // Compile the expression tree into executable code. Func<int, bool> deleg2 = expr.Compile(); // Invoke the method and print the output. Console.WriteLine("deleg2(4) = {0}", deleg2(4)); /* This code produces the following output: deleg(4) = True deleg2(4) = True */
表达式树是lambda表达式的内存数据表示。表达式树使lambda表达式的结构透明和显式。您能够像处理任何其余数据结构同样与表达式树中的数据进行交互。
将表达式视为数据结构的能力使API可以以能够自定义方式检查,转换和处理的格式接收用户代码。例如,LINQ to SQL数据访问实现使用此工具将表达式树转换为可由数据库评估的Transact-SQL语句。
Queryable类中定义的许多标准查询运算符都有一个或多个Expression
所述的NodeType一个的表达
使用Lambda
方法