Dotnet的局部函數和委託的對比

上一篇說了一下委託,這篇來講說局部函數和委託的對比。html

把委託和局部函數放成先後篇,是由於這兩個內容很像,用起來容易混。c#

須要瞭解委託相關內容,能夠看這一篇 【傳送門微信

使用委託表達式(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.htmlcode

咱們來看看這個委託表達式在IL中是什麼樣子:htm

圖上能很清楚看到,Lambda被轉換成了類。對象

等等,爲何lambda表達式被轉成了類,而不是一個方法?blog

這裏須要劃重點。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表達式相似,但有更優的性能。

又是一個好東西,是吧?

微信公衆號:老王Plus

掃描二維碼,關注我的公衆號,能夠第一時間獲得最新的我的文章和內容推送

本文版權歸做者全部,轉載請保留此聲明和原文連接

相關文章
相關標籤/搜索