闭包分配的意义和问题
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
就不能调用实例方法,因此 id
和 this
都被捕获并在编译器生成的 class.
中分配
您无法摆脱 id
变量的分配,但是如果您将 GetTicket_uncached
设为静态,则可以摆脱 this
引用(但这可能需要传入 ServiceAddress
,在这种情况下,Heap Allocations Viewer 会告诉您闭包分配现在是 id
和 ServiceAddress
).
您可以在 ReSharper help page for the "Implicitly capture closure" warning 中查看更多详细信息。虽然它讨论了不同的场景和警告消息,但有关分配 classes 以捕获变量的背景详细信息很有用。
我正在使用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
就不能调用实例方法,因此 id
和 this
都被捕获并在编译器生成的 class.
您无法摆脱 id
变量的分配,但是如果您将 GetTicket_uncached
设为静态,则可以摆脱 this
引用(但这可能需要传入 ServiceAddress
,在这种情况下,Heap Allocations Viewer 会告诉您闭包分配现在是 id
和 ServiceAddress
).
您可以在 ReSharper help page for the "Implicitly capture closure" warning 中查看更多详细信息。虽然它讨论了不同的场景和警告消息,但有关分配 classes 以捕获变量的背景详细信息很有用。