Lambda表达式详解(C#)

Lambda表达式可以采用以下任意一种形式的表达式:
1.表达式Lambda,表达式为其主体:

(input-parameters) => expression

2.语句Lambda,语句块作为其主体:

(input-parameters) => {<sequence-of-statements>}

使用Lambda声明运算符=>,从其主体中分离Lambda参数列表。若要创建Lambda表达式,需要在Lambda运算符左侧指定输入参数(如果有参数时),然后在另一侧输入表达式或语句块。
任何Lambda表达式都可以转换为委托类型,可以转换的委托类型由参数和返回值的类型定义。如果Lambda表达式不返回值,则可以将其转换为Action委托类型之一;否则,可将其转换为Func委托类型之一。例如,有两个参数且不返回值的Lambda表达式可转化为Action委托。有一个参数且不返回值的Lambda表达式可转换为Func委托。
例如:Lambda表达式x => x * x,指定名为x的参数,并返回x的平方值,并将表达式x => x * x分配给委托类型的变量。

Func<int, int> Square = x => x * x;
Console.WriteLine($"输出:{Square(10)}");
//输出:100

表达式Lambda还可以转换为表达式树类型,如下示例所示:

System.Linq.Expressions.Expression<Func<int, int>> e = x => x * x;
Console.WriteLine($"输出:{e}");
//输出:x => (x * x)

可在需要委托类型或表达式树的实列的任何代码中使用Lambda表达式。用C#编写LINQ时,还可以使用Lambda表达式,如下所示:

int[] numbers = { 1, 5, 3, 9, 28, 35, 5, 32, 34 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join("", squaredNumbers));

表达式Lambda
表达式位于=>运算符右侧的Lambda表达式称为“表达式Lambda”。表达式Lambda广泛用于表达式树的构造,表达式Lambda会返回表达式的结果,并采用以下基本形式:

(input-parameters) => experssion

仅当Lambda只有一个输入参数时,括号才是可选择的,否则括号是必须的。
使用空括号指定零个输入参数:

Action action = () => Console.WriteLine();

括号内的两个或更多的输入参数使用逗号加以分离:

Func<int, int, bool> test = (x, y) => x == y;

有时编译器无法推断输入类型,可以显示指定类型:

Func<int, string, bool> testTo = (int x, string str) => str.Length > x;

输入参数必须全部为显示或者全部为隐式;否则,编译器会产生报错。
表达式Lambda的主体可以包含方法调用。不过,若要创建在.NET公共语言运行时的上下文之外(如SQL Server中)计算的表达式树,不能在Lambda表达式中使用方法调用。因为在.NET公共语言运行时的上下文之外,方法将没有任何意义。

语句Lambda
语句Lambda与表达式Lambda表达类似,只是语句括在大括号中:
(input-parameters) => {}
语句Lambda的主体可以包含任意数量的语句;但是,通常不会多余两个或三个。
Action action = name =>
{
string greeting = $“你好,{name}”;
Console.WriteLine(greeting);
};
action(“莫扎特”);
语句Lambda也不能用于创建表达式目录树。

异步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添加同一事件处理程序。若要添加此程序,请在Lambda参数列表前添加async修饰符,如下面示例所示:

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);
         }
 }

Lambda表达式和元组
从C#7.0起,C#语言提供对元组的内置支持。可以提供一个元组作为Lambda表达式的参数,同时Lambda表达式也可以返回一个元组。在某些情况下,C#编译器使用类型推理来确定元组组件的类型。
可通过用括号括住用逗号分割的组件列表来定义元组。下面的示例使用包含三个组件的元组,将一系列数字传递给Lambda表达式,此表达式将每个值翻倍,然后返回包含乘法运算结果的元组(内含三个元组)。

Func<(int, int, int), (int, int, int)> Items = s => (2 * s.Item1, 2 * s.Item2, 2 * s.Item3);
var nums = (3, 7, 4);
var numbersItem = Items(nums);
Console.WriteLine($"The set {nums} doubled:{numbersItem}");

通常,元组字段命名为Item1、Item2等。但是,可以使用命名组件定义元组,如以下示例所示:

Func<(int n1, int n2, int n3), (int, int, int)> items = s => (2 * s.n1, 2 * s.n2, 2 * s.n3);
var num = (45, 34, 29);
var itemNums = items(num);
Console.WriteLine($"The set {num} doubled:{itemNums}");

Lambda表达式中的类型推理
编写Lambda时,通常不必为输入参数指定类型,因为编译器可以根据Lambda主体、参数类型以及C#语言规范中描述的其它因素来推断类型。对于大多数标准查询运算符,第一个输入是源序列中的元素类型。如要查询IEnumerable,则输入变量将被推断为Customer对象,这就意味着你可以访问其属性和方法。

customer.Where(c => c.City == "London");

Lambda类型推理的一般规则如下:

  1. Lambda包含的参数数量必须与委托类型包含的参数数量相同; Lambda
  2. 中的每个输入参数必须都能够隐式转换为其对应的委托参数;
  3. Lambda的返回值(如果有)必须能够隐式转换为委托的返回类型。

注意: Lambda表达式本身没有类型,因为通用类型系统没有Lambda表达式这一固有概念。不过,有时以一种非正式的方式谈论Lambda表达式的“类型”会很方便,在这些情况下,类型是指委托类型或Lambda表达式所转换到的Expression类型。

捕获Lambda表达式中的外部变量和变量范围
Lambda可以引用外部变量。这些变量是在定义Lambda表达式的方法中或包含Lambda表达式的类型中的范围内变量。以这种方式捕获的变量将进行存储以备在Lambda表达式中使用,即使在其它情况下,这些变量将超出范围并进行垃圾回收。必须明确地分配外部变量,然后才能在Lambda表达式中使用该变量。
如下示例所示:

public class Class1
    {

        public class VariableCaptureGame
        {
            internal Action<int> updateCapturedLocalVariable;
            internal Func<int, bool> isEqual;

            public void RunCaptureGame(int data)
            {
                int k = 0;
                updateCapturedLocalVariable = x =>
                {
                    k = x;
                    bool result = k > data;
                    Console.WriteLine($"{k} is greater than {data}:{result}");
                };
                isEqual = x => x == k;
                Console.WriteLine($"Local variable before lambda invocation:{k}");
                updateCapturedLocalVariable(20);
                Console.WriteLine($"Local variable after lambda invocation:{k}");
            }
        }

        public void InvokeMain()
        {
            var game = new VariableCaptureGame();
            int gameInput = 8;
            game.RunCaptureGame(gameInput);

            int jTry = 10;
            bool result = game.isEqual(jTry);
            Console.WriteLine($"Captured local variable is equal to {jTry}:{result}");

            int anotherData = 6;
            game.updateCapturedLocalVariable(anotherData);
            bool anotherResult = game.isEqual(anotherData);
            Console.WriteLine($"Another lambda observes a new value of captured variable:{anotherResult}");
        }
    }

下列规则适用于Lambda表达式中的变量范围:
捕获的变量将不会作为垃圾回收,直至引用变量的委托符合垃圾回收的条件;
在封闭方法中看不到Lambda表达式内引入的变量;
Lambda表达式无法从封闭方法中直接捕获in、refout参数;
Lambda表达式中的return语句不会导致封闭方法返回;
如果相应跳转语句的目标位于Lambda表达式块之外,Lambda表达式不得包含goto、break或continue语句。同样,如果目标在块内部,在Lambda表达式块外部使用跳转语句也是错误的。

你可能感兴趣的:(C#,c#,开发语言,microsoft)