Dotnet的局部函数和委托的对比
上一篇说了一下委托,这篇来说说局部函数和委托的对比。
把委托和局部函数放成前后篇,是因为这两个内容很像,用起来容易混。
需要了解委托相关内容,可以看这一篇 【传送门】
使用委托表达式(Lambda)
假设一个场景:我们有一个订单列表,里面有售价和采购价。我们需要计算所有物品的毛利率。
public class OrderDetails
{
public int Id { get; set; }
public string ItemName { get; set; }
public double PurchasePrice { get; set; }
public double SellingPrice { get; set; }
}
通过迭代,我们可以计算出每个项目的毛利率:
static void Main(string[] args)
{
List<OrderDetails> lstOrderDetails = new List<OrderDetails>();
lstOrderDetails.Add(new OrderDetails() { Id = 1, ItemName = "Item 1", PurchasePrice = 100, SellingPrice = 120 });
lstOrderDetails.Add(new OrderDetails() { Id = 2, ItemName = "Item 2", PurchasePrice = 800, SellingPrice = 1200 });
lstOrderDetails.Add(new OrderDetails() { Id = 3, ItemName = "Item 3", PurchasePrice = 150, SellingPrice = 150 });
lstOrderDetails.Add(new OrderDetails() { Id = 4, ItemName = "Item 4", PurchasePrice = 155, SellingPrice = 310 });
lstOrderDetails.Add(new OrderDetails() { Id = 5, ItemName = "Item 5", PurchasePrice = 500, SellingPrice = 550 });
Func<double, double, double> GetPercentageProfit = (purchasePrice, sellPrice) => (((sellPrice - purchasePrice) / purchasePrice) * 100);
foreach (var order in lstOrderDetails)
{
Console.WriteLine($"Item Name: {order.ItemName}, Profit(%) : {GetPercentageProfit(order.PurchasePrice, order.SellingPrice)} ");
}
}
例子中,我们创建了一个有5个商品的列表。我们还创建了一个委托表达式,并在循环中调用。
为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/14361561.html
我们来看看这个委托表达式在IL中是什么样子:

图上能很清楚看到,Lambda被转换成了类。
等等,为什么lambda表达式被转成了类,而不是一个方法?
这里需要划重点。Lambda表达式,在IL中会被转为委托。而委托是一个类。关于委托为什么是一个类,可以去看上一篇。这儿知道结论就好。
所以,Lambda表达式会转成一个类,应该通过一个实例来使用。而这个实例是new出来的,所以是分配在堆上的。
另外,通过IL代码我们也知道,IL是使用虚方法callvirt来调用的这个表达式。
现在,我们知道了一件事:Lambda会被转成委托和类,由这个类的一个实例来使用。这个对象的生命周期必须由GC来处理。
使用局部函数(Local Function)
上面的示例代码,我们换成局部函数:
static void Main(string[] args)
{
List<OrderDetails> lstOrderDetails = new List<OrderDetails>();
lstOrderDetails.Add(new OrderDetails() { Id = 1, ItemName = "Item 1", PurchasePrice = 100, SellingPrice = 120 });
lstOrderDetails.Add(new OrderDetails() { Id = 2, ItemName = "Item 2", PurchasePrice = 800, SellingPrice = 1200 });
lstOrderDetails.Add(new OrderDetails() { Id = 3, ItemName = "Item 3", PurchasePrice = 150, SellingPrice = 150 });
lstOrderDetails.Add(new OrderDetails() { Id = 4, ItemName = "Item 4", PurchasePrice = 155, SellingPrice = 310 });
lstOrderDetails.Add(new OrderDetails() { Id = 5, ItemName = "Item 5", PurchasePrice = 500, SellingPrice = 550 });
double GetPercentageProfit(double purchasePrice, double sellPrice)
{
return (((sellPrice - purchasePrice) / purchasePrice) * 100);
}
foreach (var order in lstOrderDetails)
{
Console.WriteLine($"Item Name: {order.ItemName}, Profit(%) : {GetPercentageProfit(order.PurchasePrice, order.SellingPrice)} ");
}
}
现在,我们在Main方法中放入了局部函数GetPercentageProfit。
我们再检查下IL里的代码:

没有新类,没有新对象,只是一个简单的函数调用。
此外,Lambda表达式和局部函数的一个重要区别是IL中的调用方式。调用局部函数用call,它比callvirt要快,因为它是存储在堆栈上的,而不是堆上。
通常我们不需要关注IL如何运作,但好的开发人员真的需要了解一些框架的内部细节。
call和callvert的区别在于,call不检查调用者实例是否存在,而且callvert总是在调用时检查,所以callvert不能调用静态类方法,只能调用实例方法。
还是上面的例子,这回我们用迭代器实现:
static void Main(string[] args)
{
List<OrderDetails> lstOrderDetails = new List<OrderDetails>();
lstOrderDetails.Add(new OrderDetails() { Id = 1, ItemName = "Item 1", PurchasePrice = 100, SellingPrice = 120 });
lstOrderDetails.Add(new OrderDetails() { Id = 2, ItemName = "Item 2", PurchasePrice = 800, SellingPrice = 1200 });
lstOrderDetails.Add(new OrderDetails() { Id = 3, ItemName = "Item 3", PurchasePrice = 150, SellingPrice = 150 });
lstOrderDetails.Add(new OrderDetails() { Id = 4, ItemName = "Item 4", PurchasePrice = 155, SellingPrice = 310 });
lstOrderDetails.Add(new OrderDetails() { Id = 5, ItemName = "Item 5", PurchasePrice = 500, SellingPrice = 550 });
var result = GetItemSellingPice(lstOrderDetails);
foreach (string s in result)
{
Console.WriteLine(s.ToString());
}
}
private static IEnumerable<string> GetItemSellingPice(List<OrderDetails> lstOrderDetails)
{
if (lstOrderDetails == null) throw new ArgumentNullException();
foreach (var order in lstOrderDetails)
{
yield return ($"Item Name:{order.ItemName}, Selling Price:{order.SellingPrice}");
}
}
我们将列表传递给GetItemSellingPice。我们在方法中检查了列表不能为null,并在循环中使用yield return返回数据。
代码看起来没问题,是吧?
那我们假设列表真的为空,会怎么样呢?应该会返回ArgumentNullException,预期是这样。
执行一下看看,实际不是这样。当我们使用迭代器时,方法并没有立即执行并返回异常,而是在我们使用结果foreach (string s in result)时,才执行并返回异常。这种情况,会让我们对于异常的判断和处理出现错误。
这时候,局部函数就是一个好的解决方式:
static void Main(string[] args)
{
var result = GetItemSellingPice(null);
foreach (string s in result)
{
Console.WriteLine(s.ToString());
}
}
private static IEnumerable<string> GetItemSellingPice(List<OrderDetails> lstOrderDetails)
{
if (lstOrderDetails == null) throw new ArgumentNullException();
return GetItemPrice();
IEnumerable<string> GetItemPrice()
{
foreach (var order in lstOrderDetails)
{
yield return ($"Item Name:{order.ItemName}, Selling Price:{order.SellingPrice}");
}
}
}
现在,我们正确地在第一时间得到异常。
总结
局部函数是一个非常强大的存在。它与Lambda表达式类似,但有更优的性能。
又是一个好东西,是吧?
![]() |
微信公众号:老王Plus 扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送 本文版权归作者所有,转载请保留此声明和原文链接 |
Dotnet的局部函数和委托的对比的更多相关文章
- C# 7 局部函数剖析
局部函数是C# 7中的一个新功能,允许在一个函数中定义另一个函数. 何时使用局部函数? 局部函数的主要功能与匿名方法非常相似:在某些情况下,创建一个命名函数在读者的认知负担方面代价太大.有时,函数本身 ...
- javascript函数中的实例对象、类对象、局部变量(局部函数)
定义 function Person(national,age) { this.age = age; //实例对象,每个示例不同 Person.national = national; //类对象,所 ...
- C#函数式程序设计之函数、委托和Lambda表达式
C#函数式程序设计之函数.委托和Lambda表达式 C#函数式程序设计之函数.委托和Lambda表达式 相信很多人都听说过函数式编程,提到函数式程序设计,脑海里涌现出来更多的是Lisp.Haske ...
- Javascript-全局函数和局部函数作用域的理解
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- C#中的方法(函数),委托和事件
1. 先有方法,然后再有委托,最后再有事件 2. 思考能不能将 A方法 作为参数,传递给 B方法呢? eg: 在传统的编程方式中,A方法 调用 B方法的方式如下: //洗菜 public void W ...
- C# 局部函数与事件
本文告诉大家使用局部函数可能遇到的坑. 在以前,如果有一个事件public event EventHandler Foo和一个函数private void Program_Foo(object sen ...
- 2018-8-10-C#-局部函数与事件
title author date CreateTime categories C# 局部函数与事件 lindexi 2018-08-10 19:16:52 +0800 2018-2-13 17:23 ...
- js中的局部函数和全局函数的调用
//局部函数和全局函数的特点 function fc1(){ var name ="chenhao"; function fc2(){ var age = 30; alert(na ...
- Unity3d 协程、调用函数、委托
(一)协程 开启方法:StartCoroutine("函数名"): 结束方法StopCoroutine("函数名"),StopAllCoroutines(); ...
随机推荐
- [ABP教程]第七章 作者:数据库集成
Web开发教程7 作者:数据库集成 关于此教程 在这个教程系列中,你将要构建一个基于ABP框架的应用程序 Acme.BookStore.这个应用程序被用于甘丽图书页面机器作者.它将用以下开发技术: E ...
- 设计模式之单例模式(Singleton Pattern)深入浅出
单例模式介绍:单例模式是指确保一个类在任何情况下都绝对只有一个实例,并且提供一个全局的访问点.隐藏其所有构造方法,属于创新型模式. 常见的单例有:ServletContext.ServletConfi ...
- Solon rpc 之 SocketD 协议 - 消息加密模式
Solon rpc 之 SocketD 协议系列 Solon rpc 之 SocketD 协议 - 概述 Solon rpc 之 SocketD 协议 - 消息上报模式 Solon rpc 之 Soc ...
- 用python做youtube自动化下载器 代码
目录 项目地址 思路 流程 1. post i. 先把post中的headers格式化 ii.然后把参数也格式化 iii. 最后再执行requests库的post请求 iv. 封装成一个函数 2. 调 ...
- ThreadX移植——STM32H7+MDK-AC6平台
作者:zzssdd2 E-mail:zzssdd2@foxmail.com 一.前言 在uCOS全家桶宣布开源之后被微软收购的ThreadX也开源了,真是喜大普奔,对于我们这些嵌入式行业从业者来说,能 ...
- 通过DNSLOG回显验证漏洞
通过DNSLOG回显验证漏洞 前言 实际渗透测试中,有些漏洞因为没有回显导致无法准确判断漏洞是否存在,可能导致渗透测试人员浪费大量精力在一个并不存在的漏洞上,因此为了验证一些无回显漏洞,可结合DNSl ...
- Mac使用HomeBrew
前言 考虑许久终于决定入手mac耍耍,还是因为要找工作了,手上的win本大学入的,现在使用卡顿太多,另外就是mac作为程序员之友仰慕已久.决定在PDD入了.到手后发现mac真的跟win有很大差别.还是 ...
- Head First 设计模式 —— 11. 组合 (Composite) 模式
思考题 我们不仅仅要支持多个菜单,升值还要支持菜单中的菜单.你如何处理这个新的设计需求? P355 [提示]在我们的新设计中,真正需要以下三点: P354 我们需要某种属性结构,可以容纳菜单.子菜单和 ...
- 如何写一个自己的组件库,打成NPM包,并上传到NPM远程
1.首先使用vue create my_project 构建一个自己的Vue项目 2.vue.config.js和package.json配置如下,做了些修改 const path = require ...
- 当Django设置DEBUG为False时,发现admin和html的静态资源文件加载失败的解决办法
当Django设置DEBUG为False时,发现admin和html的静态资源文件加载失败,折腾一段时间终于找到解决办法: 1.先在setting文件增加BASE_DIR(项目的路径) BASE_DI ...
