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

扫描二维码,关注我的公众号,能够第一时间获得最新的我的文章和内容推送

本文版权归做者全部,转载请保留此声明和原文连接

相关文章
相关标签/搜索