闭包分配的意义和问题

Meaning and problems of closure allocation

我正在使用JetBrains Rider进行C#编程,估计在使用ReSharper时也会出现这个警告:

我写了这个函数,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);
    });
}

GetTicket_uncached,它调用:

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

因此,方法 public async Task<IEnumerable<Ticket>> GetTicket(int id) 中的参数 id 突出显示并带有以下警告:

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

我在谷歌搜索时发现了一些东西,但我仍然不知道这意味着什么,有什么问题?

期待您的设计如下:

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

现在你没有一个可以在闭包范围之外修改的变量,Re-sharper 会给出这个警告,只要理论上可以在执行闭包之外修改一个变量,从而导致不可预测结果。同样适用于其他方法。 实际上,您应对所有其他对象执行相同的操作,这些对象可以在闭包范围之外进行修改,创建本地版本

此消息来自堆分配查看器插件。当您创建要作为 Func 传递给 GetOrCreateAsync 的 lambda 时,您正在从调用方法 (GetTicket) 中捕获一些值并稍后使用它们。

编译此代码时,编译器会将此 lambda 重写为包含值的 class 以及主体与 lambda 主体相同的方法,尽管它将使用捕获的值在这个新的 class 中,而不是原来的方法调用中。

Heap Allocations Viewer 插件所说的是在运行时这里发生了一个隐藏的分配 - 这个新的编译器生成的 class 正在分配,分配的值和调用的方法。

插件告诉您 id 正在捕获并分配到这个新的 class - 这在 lambda 中很明显,因为您在代码中看到了它。但是您还捕获了 this,因为 GetTicket_uncached 是实例方法而不是静态方法。没有 this 就不能调用实例方法,因此 idthis 都被捕获并在编译器生成的 class.

中分配

您无法摆脱 id 变量的分配,但是如果您将 GetTicket_uncached 设为静态,则可以摆脱 this 引用(但这可能需要传入 ServiceAddress,在这种情况下,Heap Allocations Viewer 会告诉您闭包分配现在是 idServiceAddress).

您可以在 ReSharper help page for the "Implicitly capture closure" warning 中查看更多详细信息。虽然它讨论了不同的场景和警告消息,但有关分配 classes 以捕获变量的背景详细信息很有用。