上一篇說了一下委托,這篇來說說區域函式和委托的對比,
?
把委托和區域函式放成前后篇,是因為這兩個內容很像,用起來容易混,
需要了解委托相關內容,可以看這一篇 【傳送門】
?
使用委托運算式(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 掃描二維碼,關注個人公眾號,可以第一時間得到最新的個人文章和內容推送 本文著作權歸作者所有,轉載請保留此宣告和原文鏈接 |
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/256146.html
標籤:.NET技术

