一篇文章厘清C#中的lambda表达式

2024-04-13 04:04

本文主要是介绍一篇文章厘清C#中的lambda表达式,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一篇文章厘清C#中的lambda表达式

链接: 源码

说C#的匿名函数,就要先说一下匿名函数.

Lambda表达式

  • 1 lambda表达式演变史
    • 1. **C# 1.0 (2002)**
    • 2. **C# 2.0 (2005)**
    • 3. **C# 3.0 (2007)**
    • 4. **C# 4.0及以后**
  • 2 lambda表达式使用方法
    • 1 **基本语法**
    • 2 **使用场景和示例**
      • **作为参数**
      • **匿名委托**
      • **LINQ 查询表达式**
      • **事件处理**
      • **并行编程与任务调度**
      • **延迟执行与 Lazy<T>**
    • 3 **注意事项**
  • 3 系统自带的两种委托:Action和Func
    • **Action**
      • **用法示例**
      • **应用场景**
    • **Func**
      • **用法示例**
      • **应用场景**
  • 4 综合案列

1 lambda表达式演变史

C# 匿名函数的演变历史可以追溯到 C# 语言的不同版本,随着语言特性的不断丰富和发展,匿名函数经历了以下几个主要阶段:

1. C# 1.0 (2002)

在 C# 1.0 中,虽然还没有直接支持匿名函数的概念,但已经引入了委托(Delegate)这一关键概念。委托允许将方法作为参数传递或存储为变量,为后续匿名函数的引入奠定了基础。在这个版本中,若要创建委托实例,必须先定义一个具有匹配签名的方法,然后使用该方法的名称来初始化委托。

例如:

public delegate int MyDelegate(int x, int y);public static int AddNumbers(int a, int b)
{return a + b;
}MyDelegate add = new MyDelegate(AddNumbers);

2. C# 2.0 (2005)

C# 2.0 引入了匿名方法,这是对匿名函数功能的初步实现。匿名方法允许开发者在需要委托的地方直接编写一段代码块(内联),而无需事先定义一个命名方法。这种语法简化了在特定上下文中临时创建和使用简单功能的过程,特别是在事件处理和回调场景中。

匿名方法的语法如下:

MyDelegate add = delegate(int a, int b)
{return a + b;
};

3. C# 3.0 (2007)

C# 3.0 引入了更强大的匿名函数形式——Lambda 表达式。Lambda 表达式进一步简化了匿名方法的语法,使其更加简洁且易于阅读。Lambda 表达式可以直接表示输入参数、箭头符号(=>)以及要执行的表达式或语句块。它们在LINQ(Language Integrated Query)中扮演了核心角色,极大地增强了C#的函数式编程能力。

Lambda 表达式的语法示例:

// 单行表达式形式
MyDelegate add = (int a, int b) => a + b;// 多行语句块形式
MyDelegate process = (int a, int b) =>
{int result = a * b;Console.WriteLine("Processing numbers...");return result;
};

4. C# 4.0及以后

随着Lambda表达式的普及和广泛使用,匿名方法在新项目中的使用逐渐减少,Lambda表达式成为编写匿名函数的首选方式。后续版本的C#(如4.0、5.0、6.0、7.0、8.0、9.0、10.0等)继续强化和扩展了Lambda表达式的能力,包括:

  • 类型推断:Lambda表达式中的参数类型可以根据上下文自动推断,进一步减少了代码冗余。
  • 可变数量参数:Lambda表达式支持可变数量参数,方便处理不定长度的参数列表。
  • async/await支持:C# 支持异步Lambda表达式,便于编写非阻塞的异步代码。
  • Expression-bodied members:C# 6.0引入了表达式体成员语法,使得Lambda风格的简短表达式可以用于方法、属性、构造函数等更多场景。
  • Local functions(局部函数):虽然不是匿名函数,但C# 7.0引入的局部函数提供了另一种在方法内部定义私有、嵌套函数的方式,有时可以作为匿名函数的替代方案,尤其是在需要复用或避免闭包副作用的情况下。

综上所述,C# 匿名函数的演变历史始于C# 2.0的匿名方法,经由C# 3.0的Lambda表达式实现了重大飞跃,并在后续版本中持续得到增强和完善,成为现代C#编程中不可或缺的一部分。尽管匿名方法在早期版本中有其作用,但在当前实践中,Lambda表达式已成为编写匿名函数的标准方式。

2 lambda表达式使用方法

C# Lambda 表达式是一种简洁、灵活的匿名函数表示形式,广泛应用于各种编程场景,如 LINQ 查询、事件处理、委托实例化、高阶函数应用等。以下是使用 C# Lambda 表达式的一些基本方法和常见用例:

1 基本语法

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

Lambda 表达式的语法由三部分组成:

  1. 参数列表:位于圆括号 () 内,可以为空(表示无参数)、包含一个或多个参数,参数类型可以显式声明或根据上下文推断。

    • 显式类型:(int x, string y)
    • 类型推断:(x, y) —— 当Lambda表达式赋值给已知委托类型或在编译器可以确定类型的情境下,可以省略参数类型。
  2. 箭头操作符 =>:将参数列表与表达式或语句块分隔开。

  3. 表达式或语句块:表示Lambda表达式的行为。

    • 单行表达式:直接返回表达式的计算结果,不需要使用 return 关键字。

      int[] numbers = { 1, 2, 3 };
      var evenNumbers = numbers.Where(n => n % 2 == 0);
      
    • 多行语句块:使用花括号 {} 包围,如果需要执行多条语句或需要显式 return 语句,则使用语句块形式。

      Func<int, int> squareAndLog = number =>
      {int squared = number * number;Console.WriteLine($"Squared: {squared}");return squared;
      };
      

2 使用场景和示例

作为参数

Lambda 表达式常被用作需要函数作为参数的方法或操作符的参数,如 LINQ 方法、事件处理器、ActionFunc 委托实例化等。

// LINQ 查询中的 Where 方法
var filteredItems = items.Where(item => item.IsAvailable && item.Price > 100);// 使用 Action 委托
Action<string> logMessage = message => Console.WriteLine($"Logging: {message}");
logMessage("Hello, Lambda!");// 使用 Func 委托
Func<int, int, int> add = (a, b) => a + b;
int sum = add(3, 5);

匿名委托

Lambda 表达式可以替代传统的匿名方法,创建不需显式定义的、临时使用的委托实例。

// 传统匿名方法
button.Click += delegate (object sender, EventArgs e)
{MessageBox.Show("Button clicked!");
};// Lambda 表达式形式
button.Click += (sender, e) => MessageBox.Show("Button clicked!");

LINQ 查询表达式

Lambda 表达式是 LINQ 查询语法的核心组成部分,用于定义筛选、投影、聚合等操作。

var customers = context.Customers.Where(c => c.Country == "USA").Select(c => new { Name = c.Name, TotalPurchases = c.Purchases.Sum(p => p.Amount) }).OrderByDescending(x => x.TotalPurchases);

事件处理

Lambda 表达式简化了事件处理器的注册,尤其在需要访问外部变量时,可以利用闭包特性。

string greeting = "Welcome!";
button.Click += (sender, e) => MessageBox.Show(greeting);

并行编程与任务调度

Lambda 表达式在 Task.RunParallel.ForEach 等并行编程场景中非常有用。

// 创建并启动一个后台任务
Task.Run(() =>
{DoSomeWork();UpdateUI();
});// 并行处理集合
Parallel.ForEach(numbers, number =>
{if (IsPrime(number))primes.Add(number);
});

延迟执行与 Lazy

Lambda 表达式用于初始化 Lazy<T> 对象,确保资源仅在首次访问时才进行计算或加载。

Lazy<int> expensiveComputation = new Lazy<int>(() => ComputeExpensiveValue());
int result = expensiveComputation.Value; // 计算仅在此处发生

3 注意事项

  • 类型推断:Lambda 表达式的参数类型和返回类型通常可以由编译器推断,无需显式声明。但在某些情况下,可能需要显式提供类型信息以消除歧义。

  • 闭包:Lambda 表达式可以捕获其封闭作用域内的变量,形成闭包。理解闭包行为对于避免潜在的并发问题和资源管理问题至关重要。

  • 性能:Lambda 表达式通常编译为高效代码,但在某些情况下(如大型循环中的复杂Lambda表达式),可能会导致编译器生成额外的类和方法,影响性能。适当优化或使用局部函数替代可能有助于提升效率。

总之,C# Lambda 表达式提供了简洁、直观的方式来编写匿名函数,极大地提高了代码的可读性和可维护性,尤其在处理函数式编程、事件处理、委托、LINQ 查询等方面发挥着重要作用。理解和熟练运用Lambda表达式是现代C#开发中的重要技能。

3 系统自带的两种委托:Action和Func

C# 中的 ActionFunc 是预定义的泛型委托类型,它们简化了委托的使用,避免了手动声明相似用途的自定义委托。今后我们使用时,没有必要自定义委托了,全部使用系统自带的委托就可以了,方便省事.

下面分别介绍 ActionFunc 的用法:

Action

Action 代表一个无返回值的方法,只用于封装需要执行的操作。根据需要传递的参数数量,C# 提供了一系列预定义的 Action 类型,从 Action(无参数)到 Action<typeparamref name="T1">, ..., T16</typeparamref></typeparamref></typeparamref>(最多16个参数)。

用法示例

  • 无参数

    Action noParamAction = () => Console.WriteLine("No parameter action called.");
    noParamAction(); // 输出 "No parameter action called."
    
  • 带参数

    Action<string, int> paramAction = (message, count) => Console.WriteLine($"{message}, count: {count}");
    paramAction("Action with parameters", 5); // 输出 "Action with parameters, count: 5"
    

应用场景

  • 事件处理:作为事件处理器,执行某种操作,无需返回值。

    button.Click += (sender, e) => Console.WriteLine("Button clicked!");
    
  • 回调:传递给异步操作或其他方法作为完成时的回调函数。

    Task.Run(() => LongRunningOperation()).ContinueWith(_ => Console.WriteLine("Long running operation completed."));
    

Func

Func 代表一个有返回值的方法,除了封装操作外,还返回一个指定类型的值。Func 类型同样有一系列预定义版本,格式为 Func<typeparamref name="T1">, ..., Tn</typeparamref>, TResult>,其中 T1Tn 代表输入参数类型,TResult 代表返回值类型。

用法示例

  • 无参数,返回整数

    Func<int> noParamFunc = () => DateTime.Now.Second;
    int currentSecond = noParamFunc(); // 获取当前秒数
    
  • 带参数,返回字符串

    Func<int, string, string> paramFunc = (num, text) => $"{text} {num}";
    string combined = paramFunc(42, "The answer is"); // 结果为 "The answer is 42"
    

应用场景

  • LINQ 查询:作为查询表达式中的选择器(Select)、谓词(Where)等方法的参数。

    List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };
    var squares = numbers.Select(n => n * n); // 返回平方数的集合
    
  • 工厂方法:传递给需要动态创建对象的函数。

    Func<string, MyClass> factory = name => new MyClass { Name = name };
    MyClass instance = factory("Instance created by Func");
    
  • 计算或转换:在需要简单计算或数据转换的场景中作为参数传递。

    double average = CalculateAverage(scores, score => score / 10.0); // 转换分数为小数
    

总结来说,Action 用于封装无需返回值的操作,常用于事件处理、回调等场景;而 Func 用于封装带有返回值的方法,适用于需要计算、转换或作为函数参数返回结果的情况。两者均通过泛型参数来适应不同数量和类型的参数需求,大大简化了委托的使用。

4 综合案列

{    //.netframework 1.0MyDelgate myDelgate = new MyDelgate(MyFunc);myDelgate("keson");void MyFunc(string s){Console.WriteLine($"hello,{s}");}
}{    //.netframework 2.0MyDelgate myDelgate = new MyDelgate(delegate (string s){Console.WriteLine($"hello,{s}");});myDelgate("keson");
}{    //.netframework 3.0MyDelgate myDelgate = new MyDelgate((string s) =>{Console.WriteLine($"hello,{s}");});myDelgate("keson");
}{//省略参数关键字MyDelgate2 myDelgate = new MyDelgate2((s, n) =>{Console.WriteLine($"hello,{s},I am {n} years old");});myDelgate("keson", 18);
}{ //省略方法体的大括号MyDelgate2 myDelgate = new MyDelgate2((s, n) =>Console.WriteLine($"hello,{s},I am {n} years old"));myDelgate("keson", 18);
}{//匿名类object ImplicitClass = new{Id = 1,Name = "jack"};Console.WriteLine(ImplicitClass.GetType().Name);//C#是强类型语言,编译时会确定类型,ImplicitClass编译时是object类型,//object类本身没有Id属性,所以无法访问//Console.WriteLine(ImplicitClass.Id);//dynamic避开编译器检查dynamic ImplicitClass2 = new{Id = 99,Name = "keson"};Console.WriteLine(ImplicitClass2.Id);Console.WriteLine(ImplicitClass2.GetType().Name);// ImplicitClass2.瞎写();//编译时不检查,但是运行时会抛出异常.var ImplicitClass3 = new{Id = 89,Name = "keson",Age = 18};Console.WriteLine(ImplicitClass3.Id);Console.WriteLine(ImplicitClass3.GetType().Name);//ImplicitClass3.Id = 1; //匿名类是只读类型,只能初始化是设置
}{//使用系统自带的委托action和funcAction action = () => { Console.WriteLine("无参"); }; //无入参Action<string> action2 = s => { Console.WriteLine($"有参: {s}"); };//有入参Func<string> func = () => { return "keson"; };//无入参,有返回值Func<int,int> func2 = i => { return i * i; };//有入参,有返回值Func<int, int> func3 = i =>  i + i;//当方法体只有一行,关键字return可以省略,大括号可以省略Console.WriteLine(func3(3));
}public delegate void MyDelgate(string s);
public delegate void MyDelgate2(string s, int num);

这篇关于一篇文章厘清C#中的lambda表达式的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/899096

相关文章

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

2. c#从不同cs的文件调用函数

1.文件目录如下: 2. Program.cs文件的主函数如下 using System;using System.Collections.Generic;using System.Linq;using System.Threading.Tasks;using System.Windows.Forms;namespace datasAnalysis{internal static

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

C# dateTimePicker 显示年月日,时分秒

dateTimePicker默认只显示日期,如果需要显示年月日,时分秒,只需要以下两步: 1.dateTimePicker1.Format = DateTimePickerFormat.Time 2.dateTimePicker1.CustomFormat = yyyy-MM-dd HH:mm:ss Tips:  a. dateTimePicker1.ShowUpDown = t

C#关闭指定时间段的Excel进程的方法

private DateTime beforeTime;            //Excel启动之前时间          private DateTime afterTime;               //Excel启动之后时间          //举例          beforeTime = DateTime.Now;          Excel.Applicat

C# 防止按钮botton重复“点击”的方法

在使用C#的按钮控件的时候,经常我们想如果出现了多次点击的时候只让其在执行的时候只响应一次。这个时候很多人可能会想到使用Enable=false, 但是实际情况是还是会被多次触发,因为C#采用的是消息队列机制,这个时候我们只需要在Enable = true 之前加一句 Application.DoEvents();就能达到防止重复点击的问题。 private void btnGenerateSh

C# double[] 和Matlab数组MWArray[]转换

C# double[] 转换成MWArray[], 直接赋值就行             MWNumericArray[] ma = new MWNumericArray[4];             double[] dT = new double[] { 0 };             double[] dT1 = new double[] { 0,2 };