使用委托表达式(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里的代码:
#914519b00189666b84edca75039805ec#
没有新类,没有新对象,只是一个简单的函数调用。
此外,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,预期是这样。