Comparison of dotnet’s local function and delegate

Time:2021-7-23

The previous article talked about delegation. This article talks about the comparison between local functions and delegation.

The reason for putting delegates and local functions in the front and back parts is that they are very similar and easy to mix up.

You need to know more about delegation【Portal

Use delegate expression (lambda)

Suppose a scenario: we have a list of orders with selling price and purchase price. We need to calculate the gross margin of all items.

public class OrderDetails
{
    public int Id { get; set; }
    public string ItemName { get; set; }
    public double PurchasePrice { get; set; }
    public double SellingPrice { get; set; }
}

Through iteration, we can calculate the gross profit rate of each project

static void Main(string[] args)
{
    List lstOrderDetails = new List();

    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 GetPercentageProfit = (purchasePrice, sellPrice) => (((sellPrice - purchasePrice) / purchasePrice) * 100);

    foreach (var order in lstOrderDetails)
    {
        Console.WriteLine($"Item Name: {order.ItemName}, Profit(%) : {GetPercentageProfit(order.PurchasePrice, order.SellingPrice)} ");
    }
}

In this example, we create a list of five items. We also created a delegate expression and called it in the loop.

    In order to prevent not providing the reprint of the original website, the original link is added here:https://www.cnblogs.com/tiger-wang/p/14361561.html

Let’s see what this delegate expression looks like in IL

As you can see clearly, lambda is converted to a class.

Wait, why is a lambda expression turned into a class instead of a method?

We need to focus here.Lambda expressions, which are converted to delegates in IL. A delegate is a class。 As for why a delegate is a class, you can go to the previous article. It’s good to know the conclusion here.

Therefore, the lambda expression will be converted into a class and should be used through an instance. And here’s an examplenewSo it’s distributed on the heap.

In addition, through the IL code, we also know that IL uses virtual methodscallvirtTo call.

Now, we know one thing: lambda will be converted to a delegate and class, and used by an instance of this class. The life cycle of this object must be handled by GC.

Use local function

For the above example code, let’s replace it with a local function:

static void Main(string[] args)
{
    List lstOrderDetails = new List();

    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)} ");
    }
}

Now, we areMainLocal functions are added to the methodGetPercentageProfit

Let’s check the code in IL again

There are no new classes, no new objects, just a simple function call.

In addition, an important difference between lambda expressions and local functions is the way they are called in IL. Call local function withcallIt’s better thancallvirtFaster, because it’s stored on the stack, not the heap.

Usually we don’t need to focus on how IL works, but good developers really need to understand some of the internal details of the framework.

callandcallvertThe difference is,callDoes not check whether the caller instance exists, andcallvertAlways check on call, socallvertStatic class methods cannot be called, only instance methods can be called.

In the above example, this time we use an iterator to implement:

static void Main(string[] args)
{
    List lstOrderDetails = new List();

    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 GetItemSellingPice(List lstOrderDetails)
{
    if (lstOrderDetails == null) throw new ArgumentNullException();

    foreach (var order in lstOrderDetails)
    {
        yield return ($"Item Name:{order.ItemName}, Selling Price:{order.SellingPrice}");
    }
}

We pass the list toGetItemSellingPice。 We checked in the method that the list cannot be emptynullAnd used in the loopyield returnReturn data.

The code looks ok, doesn’t it?

What happens if we assume that the list is really empty? It should be backArgumentNullException, expected.

Take a look. It’s not true. When we use an iterator, the method does not immediately execute and return an exception. Instead, we use the resultforeach (string s in result)Then execute and return the exception. In this case, we will make mistakes in judging and handling exceptions.

In this case, local function is a good solution

static void Main(string[] args)
{
    var result = GetItemSellingPice(null);

    foreach (string s in result)
    {
        Console.WriteLine(s.ToString());
    }
}

private static IEnumerable GetItemSellingPice(List lstOrderDetails)
{
    if (lstOrderDetails == null) throw new ArgumentNullException();

    return GetItemPrice();

    IEnumerable GetItemPrice()
    {
        foreach (var order in lstOrderDetails)
        {
            yield return ($"Item Name:{order.ItemName}, Selling Price:{order.SellingPrice}");
        }
    }
}

Now, we correctly get the exception in the first place.

summary

Local function is a very powerful existence. It is similar to lambda expression, but has better performance.

It’s another good thing, isn’t it?

WeChat official account: Lao Wang Plus

Scanning the two-dimensional code, paying attention to the official account, can get the latest personal articles and content push.

The copyright of this article belongs to the author. Please keep this announcement and the link of the original text