Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Meaning and problems of closure allocation

I'm using JetBrains Rider for programming C#, and I guess this warning will also appear when using ReSharper:

I wrote this function, GetTicket:

public async Task<IEnumerable<Ticket>> GetTicket(int id)
{
    return await _memoryCache.GetOrCreateAsync(_cachingFunctionalty.BuildCachingName(id), entry =>
    {
        entry.SlidingExpiration = TimeSpan.FromSeconds(10);
        return GetTicket_uncached(id);
    });
}

and GetTicket_uncached, which it calls:

private async Task<IEnumerable<Ticket>> GetTicket_uncached(int id)
{
    RestClient client = new RestClient(ServiceAdress);
    Request req = new Request
    {
        Method = Method.GET,
        Resource = "api/tickets/get",
        Parameters = new {ident = id}
    };

    return await client.ExecuteRequestAsync<Ticket[]>(req);
}

So, the parameter id in method public async Task<IEnumerable<Ticket>> GetTicket(int id) is highlighted with following warning:

Closure allocation: 'id' parameter and 'this' reference

I found a few things while Googling but I still don't know what this means and what's the problem?

like image 734
Matthias Burger Avatar asked Apr 16 '18 17:04

Matthias Burger


People also ask

What are closures in C#?

A closure as a first-class function in C# A closure is a particular type of function that is intrinsically linked to the environment in which it is referenced. As a result, closures can use variables pertaining to the referencing environment, despite these values being outside the scope of the closure.

Why a lambda expression forms a closure?

To prevent invalid references, the compiler creates what's called a closure for the Lambda expression. A closure is a wrapper class that contains the Lambda expression and fields that contain copies of the local variables. Since the closure is an object created with new, it's not cleaned up when the scope exits.


2 Answers

This message comes from the Heap Allocations Viewer plugin. When you're creating your lambda to pass as a Func to GetOrCreateAsync, you're capturing some values from the calling method (GetTicket) and using them later.

When compiling this code, the compiler will rewrite this lambda into a class that holds the values and also a method whose body is the same as the body of the lambda, although it will use the values captured in this new class, and not the original method call.

What the Heap Allocations Viewer plugin is saying is that there is a hidden allocation happening here at runtime - this new compiler-generated class is being allocated, the values assigned and the method called.

The plugin is telling you that id is being captured and allocated in this new class - this is obvious from the lambda because you see it in the code. But you're also capturing this, because GetTicket_uncached is an instance method and not a static method. You can't call an instance method without this, so both id and this are captured and allocated in the compiler generated class.

You can't get rid of the allocation of the id variable, but you could get rid of the this reference if you make GetTicket_uncached static (but this might require passing in ServiceAddress, in which case, the Heap Allocations Viewer will tell you that you're closure allocation is now id and ServiceAddress).

You can see some more detail in the ReSharper help page for the "Implicitly capture closure" warning. While it talks about a different scenario and warning message, the background details on allocating classes to capture variables is useful.

like image 96
citizenmatt Avatar answered Oct 19 '22 22:10

citizenmatt


It is expecting you to design as follows:

public async Task<IEnumerable<Ticket>> GetTicket(int id)
{

    return await _memoryCache.GetOrCreateAsync(_cachingFunctionalty.BuildCachingName(id), entry =>
    {
        var localId = id;
        entry.SlidingExpiration = TimeSpan.FromSeconds(10);
        return GetTicket_uncached(localId);
    });
}

Now you don't have a variable that can be modified outside the scope of closure, Re-sharper will give this warning, when ever it is theoretically possible to modify a variable outside the executing closure and thus leading to unpredictable results. Same will apply to other method. In act you shall follow the same for the all other objects, which can be modified outside the closure scope, creating a local version

like image 29
Mrinal Kamboj Avatar answered Oct 19 '22 22:10

Mrinal Kamboj