一篇文章厘清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);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/811530.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第十三届蓝桥杯省赛CC++ 研究生组2.0

裁纸刀 #include<iostream> using namespace std; const int n 20, m 22; int main(){cout << 4 (n - 1) n * (m - 1);//443return 0; }抠出来&#xff08;上下左右&#xff09; 剪开行 每行分开 灭鼠先锋 #include<iostream> using namespace std; …

汽车标定技术(二十) -- 移植标定栈时主机厂、供应商应该做什么?(2)

目录 1.概述 1.1 考虑标定参数实现方式 1.2 A2L生成 2. 主机厂视角 3.小结 1.概述

【算法刷题day23】Leetcode:669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树

文章目录 Leetcode 669. 修剪二叉搜索树解题思路代码总结 Leetcode 108. 将有序数组转换为二叉搜索树解题思路代码总结 Leetcode 538. 把二叉搜索树转换为累加树解题思路代码总结 草稿图网站 java的Deque Leetcode 669. 修剪二叉搜索树 题目&#xff1a;669. 修剪二叉搜索树 解…

全量知识系统 程序详细设计 之 先验逻辑-实现:从“平凡”回到“平凡” (QA 百度搜索)

Q1. 思考&#xff1a;数学中的平凡&#xff0c;和程序中的平凡&#xff08;比如POJO&#xff09;、语言中的平凡&#xff08;比如纯文本&#xff09;&#xff0c;数据中的平凡&#xff08;比如 Number&#xff09;。因为我设计中的全知系统将设计的三个方面刻画为语言设计、程序…

tencentcloud-sdk-python-iotexplorer和tencent-iot-device有什么区别

1. tencent-iot-device tencent-iot-device 是腾讯云提供的物联网设备 SDK&#xff0c;用于在物联网场景中开发和连接设备。这个 SDK 提供了丰富的功能和接口&#xff0c;可以帮助开发者快速构建稳定、高效的物联网应用。 主要功能和特点&#xff1a; 设备连接管理&#xff1…

【DM8】AWR报告

sys.WRM$_WR_CONTROL记录快照的相关控制信息 sys.wrm $_snapshot记录快照的相关信息 1.初始化awr快照包 创建或删除DBMS_WORKLOAD_REPOSTORY系统包为1的时候开启&#xff0c;0的时候关闭 SELECT sf_check_awr_sys; sp_init_awr_sys(1);2.设置时间 –间隔10min一次 CALL dbms_…

【FAQ】HarmonyOS SDK 闭源开放能力 —Push Kit(2)

1.问题描述&#xff1a; 开发服务端推送&#xff0c;客户端能收到离线推送&#xff0c;但是推送收到的通知只能从手机顶部下拉看到&#xff0c;无法收到一个顶部的弹框。请问是什么原因&#xff1f; 解决方案&#xff1a; 可能原因一&#xff1a; 消息提醒的方式与消息类别有…

未来汽车硬件安全的需求(1)

目录 1.概述 2.EVITA 2.1 EVITA HSM 2.2 EVITA保护范围 3.市场变化对车载网络安全的影响 3.1 非侵入式攻击的风险 3.2 量子计算机的蛮力攻击 3.3 整车E/E架构的变化 3.4 网络安全标准和认证 3.5 汽车工业的网络安全措施 4.汽车安全控制器 4.1 TPM2.0 4.2 安全控…

CLIP模型入门

简介 CLIP&#xff08;Contrastive Language-Image Pre-Training&#xff09;是OpenAI在2021年初发布的多模态预训练神经网络模型&#xff0c;用于匹配图像和文本。该模型的关键创新之一是将图像和文本映射到统一的向量空间&#xff0c;通过对比学习的方式进行预训练&#xff…

华为eNSP网络模拟器

华为eNSP网络模拟器安装详细教程&#xff08;2024&#xff09; 华为eNSP网络模拟器安装详细教程&#xff08;2024&#xff09;_ensp安装-CSDN博客 eNSP设备基础配置 eNSP设备基础配置_ensp中路由器交换机pc机配置ip地址-CSDN博客 ENSP学习 ENSP学习-CSDN博客 【华为eNSP模拟器】…

运用OSI模型提升排错能力

1. OSI模型有什么实际的应用价值&#xff1f; 2. 二层和三层网络的区别和应用&#xff1b; 3. 如何通过OSI模型提升组网排错能力&#xff1f; -- OSI - 开放式系统互联 - 一个互联标准 - 从软件和硬件 定义标准 - 不同厂商的设备 研发的技术 - 具备兼容性 -- O…

知识图谱入门到实战之1.知识图谱基础

知识图谱基础 1.为什么要用知识图谱2.知识图谱的前世今生&#xff08;1&#xff09;发展脉络&#xff08;2&#xff09;发展阶段&#xff08;3&#xff09;知识图谱定义&#xff08;4&#xff09;知识类型&#xff08;5&#xff09;常见的开源知识图谱 3.知识图谱的相关技术&am…

微服务learning

一、部分网址 有道云笔记 (youdao.com) Spring Cloud Alibaba| ProcessOn免费在线作图,在线流程图,在线思维导图 系统由单体架构到微服务架构到底是如何演进的&#xff1f;-腾讯云开发者社区-腾讯云 (tencent.com) 阿里限流神器Sentinel夺命连环 17 问&#xff1f; - 掘金 …

拥有一台阿里云服务器可以做什么?

阿里云ECS云服务器可以用来做什么&#xff1f;云服务器可以用来搭建网站、爬虫、邮件服务器、接口服务器、个人博客、企业官网、数据库应用、大数据计算、AI人工智能、论坛、电子商务、AI、LLM大语言模型、测试环境等&#xff0c;云服务器吧yunfuwuqiba.com整理阿里云服务器可以…

2024/4/5—力扣—下一个排列

代码实现&#xff1a; 思路&#xff1a;两遍扫描 void swap(int *a, int *b) {int t *a;*a *b;*b t; }void reverse(int *nums, int l, int r) {while (l < r) {swap(nums l, nums r);l;r--;} }void nextPermutation(int *nums, int numsSize) {int i numsSize - 2;wh…

陆面、生态、水文模拟与多源遥感数据同化

原文链接&#xff1a;陆面、生态、水文模拟与多源遥感数据同化https://mp.weixin.qq.com/s?__bizMzUzNTczMDMxMg&mid2247601198&idx6&sn51b9b26b75c9df1f11dcb9a187878261&chksmfa820dc9cdf584df9ac3b997c767d63fef263d79d30238a6523db94f68aec621e1f91df85f6…

算法-日期问题

算法-日期问题 1.判断是否闰年 int is_leap(int y) {if((y%4000)||(y%40&&y%100!0)){return 1;}return 0; }2.每个月的天数 const int months[]{0,31,28,31,30,31,30,31,31,30,31,30,31};3.计算当前年当前月的天数 int get_month_days(int year,int month) {int re…

一位来自河南农村,大学前从未接触电脑的大二学生

怎么说呢&#xff0c;在大学前从未接触过编程&#xff0c;总以为编程是特别高大上的东西&#xff08;现在依然以为编程是高大上的东西哈哈哈&#xff09;&#xff0c;父母借钱凑了一台2800的联想笔记本电脑&#xff08;说实话看到室友七八千的电脑玩游戏真羡慕&#xff09;&…

LANG 和 LC_ALL两者的区别

LANG 和 LC_ALL 都是环境变量&#xff0c;用于设置系统的语言环境。它们的区别如下&#xff1a; LANG 环境变量&#xff1a; LANG 是 Linux 系统中定义的最基本的语言环境变量。它用于设置默认的区域设置&#xff0c;包括语言、字符集和其他本地化设置。当系统需要在多个环境变…

Docker 学习笔记(五):梳理 Docker 镜像知识,附带 Commit 方式提交镜像副本,安装可视化面板 portainer

一、前言 记录时间 [2024-4-10] 前置文章&#xff1a; Docker学习笔记&#xff08;一&#xff09;&#xff1a;入门篇&#xff0c;Docker概述、基本组成等&#xff0c;对Docker有一个初步的认识 Docker学习笔记&#xff08;二&#xff09;&#xff1a;在Linux中部署Docker&…