上一篇說了一下委託,這篇來說說區域性函式和委託的對比。
把委託和區域性函式放成前後篇,是因為這兩個內容很像,用起來容易混。
需要了解委託相關內容,可以看這一篇 【傳送門】
使用委託表示式(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 掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送 本文版權歸作者所有,轉載請保留此宣告和原文連結 |