Lambda Expressions(C#编程指南)

原文: Lambda Expressions (C# Programming Guide)
翻译参考:C# Lambda Expressions 简介

Lambda表达式是一个匿名函数,你可以用它来创建委托或表达式目录树类型。通过使用 lambda 表达式,你可以写本地函数作为参数传递或返回的函数调用的值。 Lambda 表达式对于编写LINQ查询表达式特别有用。 要创建一个lambda表达式,可以在 Lambda 运算符=>的左侧指定输入参数(如果有的话),把表达式或语句块放在另一侧。例如,在 lambda 表达式x=> X * X 中,指定x参数和返回x的平方值。您可以指定这个表达式作为委托类型,如下面的示例所示:

delegate int del(int i);
static void Main(string[] args)
{
    del myDelegate = x => x * x;
    int j = myDelegate(5); //j = 25
}

创建一个表达式树类型:
using System.Linq.Expressions;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Expression<del> myET = x => x * x;
        }
    }
}

=>运算符具有与赋值( = )相同的优先级,并且是右结合(见运算符文的“关联性”一节) 。

lambda 表达式使用于基于方法的 LINQ 查询中,作为参数传递给标准的查询操作方法,如 WHERE。

当您使用基于方法的语法来调用 Enumerable 类的 Where 方法(如你在做LINQ 转为 Objects 和 LINQ  转为 XML),参数是一个委托类型System.Func <T, TResult> 。 Lambda表达式是最便捷的方式来创建该委托。当你在调用相同的方法,例如,System.Linq.Queryable 类(如你在做 LINQ 到 SQL ),那么参数类型是 System.Linq.Expressions.Expression<func> ,其中Func是任何功能的可以多达16个输入参数的委托。同样, Lambda表达式只是一个很简洁的方式来构造表达式树。在lambda 表达式允许在 WHERE 调用看起来类似但事实上从lambda创建的对象的类型是不同的。

在前面的例子中,请注意委托签名具有一个隐式类型的输入类型为 int 的参数,并返回一个 int 。 Lambda 表达式可以被转换成该类型的委托,因为它也有一个输入参数( x)和一个返回值,编译器可以隐式转换为 int 类型。 (类型推理在下面的章节中进行更详细的讨论。 )当委托是通过使用5作为输入参数调用时,它会传回25的结果。

lambda 表达式是不允许 在左侧有 IS AS 运算符。


适用于匿名方法的所有限制也适用于Lambda 表达式。欲了解更多信息,请参见 匿名方法(C#编程指南)

1.表达式lambda


右侧=>操作符的 Lambda 表达式被称为表达式 lambda。表达式lambda表达式被广泛应用于表达式树的结构(C#和Visual Basic)。表达式 lambda 返回的表达式的结果,并采用以下基本形式:

(input parameters) => expression
如果只有一个输入参数时,括号可以省略。如果具有一个以上的输入参数,必需加上括号。
(x, y) => x == y
可以显式指定输入参数的类型
(int x, string s) => s.Length > x
也可以没有任何输入参数
() => SomeMethod1()
上面这段代码在Lambda式中调用了一个方法。需要注意的是,如果在创建.Net 框架外的求值表达式树的时候,比如:在 SQL Server 内执行,不宜在 Lambda 式中执行方法调用。一般来说,这些方法在在 .NET CLR 的上下文环境以外执行没有意义,也不能真正工作。

2.语句Lambda


语句lambda类似,只是语句(s)是花括号括起来的表达式lambda:

(input parameters) => {statement;}
声明的lambda的主体可以包含任意数量的语句;然而,在实践中,通常有不超过两个或三个。
delegate void TestDelegate(string s);
…
TestDelegate myDel = n => { string s = n + " " + "World"; Console.WriteLine(s); };
myDel("Hello");

lambda表达式,匿名方法一样,不能用来创建表达式树。

3.异步Lambda


您可以轻松地创建 lambda 表达式和语句,纳入通过使用 async await 关键字声明的异步处理。例如,下面的 Windows 窗体示例包含一个事件处理程序调用,并等待一个异步方法,ExampleMethodAsync

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        // ExampleMethodAsync returns a Task.
        await ExampleMethodAsync();
        textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}
您可以通过使用一个异步的 lambda 添加相同的事件处理程序。要添加此处理程序中,lambda 参数列表前添加 async 修饰符,如下面的示例所示。
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
        button1.Click += async (sender, e) =>
        {
            // ExampleMethodAsync returns a Task.
            await ExampleMethodAsync();
            textBox1.Text += "\r\nControl returned to Click event handler.\r\n";
        };
    }

    async Task ExampleMethodAsync()
    {
        // The following line simulates a task-returning asynchronous process.
        await Task.Delay(1000);
    }
}

如何创建异步方法,详细见: Asynchronous Programming with Async and Await (C# and Visual Basic).

4.lambda表达式与标准查询运算


许多标准查询操作有一个输入参数,其类型是 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 of course
当参数类型是一个表达式 <func> 时,您也可以提供一个lambda 表达式。例如,在 System.Linq.Queryable 定义的标准查询操作中,当您指定一个表达式 <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)除以二有一个余数为1 的个数。 
下面的代码行产生一个序列,该序列包含在数字数组,它是到9的左侧的所有元素,因为9是不符合条件的序列中的第一个号码:
var firstNumbersLessThan9 = numbers.TakeWhile(n => n < 9); //译注: 原文中代码例子与描述不符
此示例演示如何将它们括在括号中指定多个输入参数。该方法返回的数字数组中的所有元素,直到其值小于它的位置。不要混淆lambda运算符(=>)与大于或等于运算符(> =)。
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);


5.Lambdas 中的类型推断


当你编写lambda表达式时,一般没有必要指定输入参数类型,因为编译器可以在的lambda代码基础上推断,参数的委托类型,和在C#语言规范中描述的其他因素的类型。对于大多数的标准查询操作,前面输入的元素类型就是该序列的类型。所以,如果你要查询一个 IEnumerable<Customer> ,然后输入变量推断为 Customer 对象,这意味着您可以访问其方法和属性:

customers.Where(c => c.City == "London");
lambda 表达式的一般规则如下: 
  • lambda 必须包含参数的委托类型相同的个数。
  • lambda 每个输入参数必须可隐式转换为其对应的委托参数。 
  • lambda 的返回值(如有)必须可隐式转换为委托的返回类型。 

请注意,在自己的 lambda 表达式没有一个类型,因为通用类型系统没有“lambda表达式”的内在概念。然而,有时说话非正式的 “type” 的lambda表达式的方便。在这些情况下,类型是指到lambda表达式转换为委托类型或表达式的类型。

6.Lambda式中的变量作用域


lambda表达式可以参考外部变量(请参见匿名方法(C#编程指南) )

Lambda式的定义仅仅是定义一个匿名方法,最终会生成一个委托对象。外部变量的引用将被“捕获”到委托对象内部,将会伴随委托对象的整个生命周期。在委托对象生命周期结束之前该变量都不会被垃圾回收。就算外部变量已经超过了原来的作用域,也还能继续在 Lambda 式中使用。所有会被引用的外部变量必须在 Lambda 式定义之前被显式赋值。见下例
delegate bool D();
delegate bool D2(int i);


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();
    }
}
下面是关于变量作用域的规则:
  • 被“捕获”的变量在委托的生命周期结束前都不会被垃圾回收;
  • 在 Lambda 式内部定义的变量对外不可见;
  • Lambda 式无法直接捕获一个具有 ref out 描述的参数变量;
  • Lambda 式中的 return 语句不会导致当前所在的方法返回;
  • Lambda 式中不允许包含会导致跳当前执行范围的 gotobreak continue 语句。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值