上一篇说了一下委托,这篇来说说局部函数和委托的对比。

把委托和局部函数放成前后篇,是因为这两个内容很像,用起来容易混。

需要了解委托相关内容,可以看这一篇 【传送门】

使用委托表达式(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个商品的列表。我们还创建了一个委托表达式,并在循环中调用。

我们来看看这个委托表达式在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如何运作,但好的开发人员真的需要了解一些框架的内部细节。

callcallvert的区别在于,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表达式类似,但有更优的性能。

又是一个好东西,是吧?

Dotnet的局部函数和委托的对比相关推荐

  1. 对C#下函数,委托,事件的一点理解!

    <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 今天一来是有点 ...

  2. 高阶函数、委托与匿名方法

    高阶函数.委托与匿名方法 高阶函数.委托与匿名方法 作者 赵劼 发布于 2009年4月17日 下午6时35分 高阶函数(higher-order function)是指把另一个函数作为参数或返回值的函 ...

  3. lua--函数深入:闭合函数,局部函数,尾调用

    lua函数具有两大特征:函数作为第一类值,函数具有特定的词法域(Lexical Scoping) 所谓第一类值:代表函数和其他传统类型的值是等价的(例如数字和字符串),函数可以同他们一样存储在变量,t ...

  4. R语言使用caretEnsemble包的caretList函数一次性构建多个机器学习模型、使用lattice包的bwplot函数使用箱图对比多个模型在多个指标上的性能差异

    R语言使用caretEnsemble包的caretList函数一次性构建多个机器学习模型.并使用caret包的resamples函数比较在同一数据集上多个机器学习模型的比较结果.使用lattice包的 ...

  5. 查看某个方法在哪里被调用_MATLAB局部函数公有化的方法: localfunctions

    知乎视频​www.zhihu.com MATLAB的一个函数文件里面, 开头第一个函数是可以被外部调用的, 而其他函数是无法直接被外部调用的, MATLAB称之为局部函数. 如果用OOP的术语来说, ...

  6. do语句转化为局部函数一例

    do: (do ((x a (b x))           (y c (d y)))          ((test x y) (z x y))        (f x y)) 局部函数: (lab ...

  7. python函数局部变量_Python局部函数– functoolspartial()

    python函数局部变量 什么是Python局部函数? (What is a Python Partial Function?) Sometimes a function accepts multip ...

  8. C++函数与java函数菜鸟级的对比理解

    C++函数与java函数菜鸟级的对比理解 转自:http://blog.csdn.net/stevenhu_223/article/details/9108571 Android源码底层的代码实现文件 ...

  9. R语言使用caret包的knnreg函数拟合KNN回归模型:使用predict函数和训练好的模型进行预测推理、使用plot函数可视化线图对比预测值和实际值曲线

    R语言使用caret包的knnreg函数拟合KNN回归模型:使用predict函数和训练好的模型进行预测推理.使用plot函数可视化线图对比预测值和实际值曲线 目录

最新文章

  1. 比较Spring AOP与AspectJ
  2. 电子书下载:Moving to Microsoft Visual Studio 2010
  3. ListBox,CheckBoxList,DropDownList,RadioButtonList的常见用法
  4. Oracle RESETLOGS 和 NORESETLOGS 区别说明
  5. ALGO-162——Airport Configuration
  6. CentOS离线安装gcc环境(附安装包+图文并茂)
  7. 数字化时代的K12与学前教育行业洞察
  8. RQNOJ36 数石子 并查集 简单应用
  9. 备考java二级_2017年计算机二级Java备考练习题库(2)
  10. 深度学习 tensorflow 计算图,会话,张量
  11. 电商后台、手机端、小程序、H5、电商原型、需求池、产品结构图、接口流程、高保真交互、PRD、布局说明、数据统计、店铺管理、商品管理、财务管理、售后、订单、会员、客服、标签、Axure原型、产品原型
  12. NOI Online能力测试2视频版,让我们看看出题专家怎么说!(入门组)
  13. Firefox6 使用 firebug 解决方法 以及迅雷(thunder)插件报错
  14. 背包之01背包、完全背包、多重背包详解
  15. 介绍鲜花视频的html模板,HTML黄色欧美形式鲜花介绍网页模板代码
  16. 如何用范德蒙行列式完成插值
  17. matlab中subs怎么用,【转】MATLAB中subs函数效率低
  18. 酷客多接受CCTV2财经频道专访
  19. 打开keil提示未安装xx器件包,实际上已经安装
  20. 成功解决笔记本重装系统后没有无线网

热门文章

  1. 【ACM-ICPC 2018 沈阳赛区网络预赛 I】Lattice's basics in digital electronics
  2. 17、字符设备控制技术
  3. 用POP动画引擎实现弹簧动画(POPSpringAnimation)
  4. hdu 2648 Shopping
  5. 如何在 Apple Silicon (M1) 上开发 Teams App
  6. Teams团队的成员列表API的已知问题
  7. discord linux_如何在Discord中应用文本格式
  8. 如何在Word,Excel和PowerPoint 2010中裁剪图片
  9. bzoj3160(FFT+回文自动机)
  10. Array 的一些常用 API