本文转载自微信公众号“老王加”,老王加作者老王。转载本文请联系老王Plus公众号。将delegates和localfunctions放在前后两章是因为这两个内容非常相似,使用时容易混淆。假设一个使用佣金表达式(Lambda)的场景:我们有一个包含销售价格和购买价格的订单列表。我们需要计算所有项目的毛利率。publicclassOrderDetails{publicintId{get;set;}publicstringItemName{get;set;}publicdoublePurchasePrice{get;set;}publicdoubleSellingPrice{get;set;}}通过代理,我们可以计算出每个项目的毛利率:staticvoidMain(string[]args){ListlstOrderDetails=newList();lstOrderDetails.Add(newOrderDetails(){Id=1,ItemName="Item1",PurchasePrice=100,SellingPrice=120});lstOrderDetails.Add(newOrderDetails(){Id=2,ItemName="Item2",PurchasePrice=800,SellingPrice=1200});lstOrderDetails.Add(newOrderDetails(){Id=3,ItemName="Item3",PurchasePrice=150,SellingPrice=150});lstOrderDetails.Add(newOrderDetails(){Id=4,ItemName="Item4",PurchasePrice=155,SellingPrice=310});lstOrderDetails.Add(newOrderDetails(){Id=5,ItemName="Item5",采购价格=500,SellingPrice=550});FuncGetPercentageProfit=(purchasePrice,sellPrice)=>(((sellPrice-purchasePrice)/purchasePrice)*100);foreach(varorderinlstOrderDetails){Console.WriteLine($"ItemName:{order.ItemName},Profit(%):{GetPercentageProfit(order.PurchasePrice,order.SellingPrice)}");}}在示例中,我们创建了一个包含5个项目的产品我们还创建了一个委托表达式并在循环中调用它。我们来看看这个委托表达式在IL中是什么样子的:图中可以清楚地看到Lambda被转换成了一个类。等等,为什么lambda表达式变成了类而不是方法?这里需要强调一下。Lambda表达式将转换为IL中的委托。委托是一个类。关于delegate为什么是class,可以看之前的文章。很高兴知道这里的结论。所以,Lambda表达式变成了一个类,应该通过一个实例来使用。而这个实例是新的,所以分配在堆上。另外,通过IL代码我们也知道,IL就是虚方法callvirt调用的表达式。现在,我们知道一件事:Lambda将变成一个委托和一个类,供此类的实例使用。该对象的生命周期必须由GC处理。使用局部函数(LocalFunction)上面的示例代码,我们替换成局部函数:staticvoidMain(string[]args){ListlstOrderDetails=newList();lstOrderDetails.Add(newOrderDetails(){Id=1,ItemName="Item1",PurchasePrice=100,SellingPrice=120});lstOrderDetails.Add(newOrderDetails(){Id=2,ItemName="Item2",PurchasePrice=800,SellingPrice=1200});lstOrderDetails.Add(newOrderDetails(){Id=3,ItemName="Item3",PurchasePrice=150,SellingPrice=150});lstOrderDetails.Add(newOrderDetails(){Id=4,ItemName="Item4",PurchasePrice=155,SellingPrice=310});lstOrderDetails.Add(newOrderDetails(){Id=5,ItemName="Item5",PurchasePrice=500,SellingPrice=550});doubleGetPercentageProfit(doublepurchasePrice,doublesellPrice){return(((sellPrice-purchasePrice)/purchasePrice)*100);}foreach(varorderinlstOrderDetails){Console.WriteLine($"ItemName:{order.ItemName},Profit(%):{GetPercentageProfit(order.PurchasePrice,order.SellingPrice)}");}}现现在,我们将本地函数GetPercentageProfit放在Main方法中。让我们检查一下IL中的代码:没有新类,没有新对象,只是一个简单的函数调用。此外,lambda表达式和局部函数之间的一个重要区别是它们在IL中的调用方式。局部函数用call调用,比callvirt更快,因为它存储在栈上,而不是堆上。通常我们不需要关注IL是如何工作的,但优秀的开发人员确实需要了解框架的一些内部细节。call和callvert的区别是call不检查调用者实例是否存在,而callvert在调用的时候总是检查,所以callvert不能调用静态类方法,只能调用实例方法。还是上面的例子,这回我们用仿真器实现了:staticvoidMain(string[]args){ListlstOrderDetails=newList();lstOrderDetails.Add(newOrderDetails(){Id=1,ItemName="Item1",PurchasePrice=100,SellingPrice=120});lstOrderDetails.Add(newOrderDetails(){Id=2,ItemName="Item2",PurchasePrice=800,SellingPrice=1200});lstOrderDetails.Add(newOrderDetails(){Id=3,ItemName="Item3",PurchasePrice=150,SellingPrice=150});lstOrderDetails.Add(newOrderDetails(){Id=4,ItemName="Item4",PurchasePrice=155,SellingPrice=310});lstOrderDetails.Add(newOrderDetails(){Id=5,ItemName="Item5",PurchasePrice=500,SellingPrice=550});varresult=GetItemSellingPice(lstOrderDetails);foreach(stringsinresult){Console.WriteLine(s.ToString());}}privatestaticIEnumerableGetItemSellingPice(ListlstOrderDetails){if(lstOrderDetails==null)thrownewArgumentNullException();foreach(varorderinlstOrderDetails){yieldreturn($"ItemName:{order.ItemName},SellingPrice:{order.SellingPrice}");}}我们将列表传递给GetItemSellingPice我们在方法中检查列表不能为空,并在循环中使用yieldreturn返回数据。代码看起来没问题,不是吗?那么如果我们假设列表真的是空的呢?应按预期返回ArgumentNullException。执行一下看看,不是这样的。当我们使用迭代器时,方法不会立即执行并返回异常,但是当我们使用结果foreach(stringsinresult)时,它会执行并返回异常。在这种情况下,我们对异常的判断和处理就会出错。这时候,局部函数是一个很好的解决方案:(ListlstOrderDetails){if(lstOrderDetails==null)thrownewArgumentNullException();returnGetItemPrice();IEnumerableGetItemPrice(){foreach(varorderinlstOrderDetails){yieldreturn($"ItemName:{order.ItemPrice}:销售{order.SellingPrice}");}}}现在,我们第一次正确地得到了异常。总结局部函数是一个非常强大的存在。它类似于Lambda表达式,但性能更好。又是一件好事,对吧?