Skip to content

dotnet C# 使用无捕获的委托可以获得编译器缓存减少对象创建

Updated: at 08:22,Created: at 10:52

本文也叫跟着 Stephen Toub 大佬学性能优化系列,这是我从 Stephen Toub 大佬给 WPF 框架做性能优化学到的知识,通过让委托无捕获来进行性能优化

在 .NET 应用中,咱可以通过委托将某个方法传入到某个模块里面,让这个模块在合适的时候,执行传入的方法。如果传入的是实例的方法,那在编译器生成委托时,将会自动加上捕获包,本质上的捕获包是一个对象,也就是每次调用都需要分配对象

如以下代码

public void Foo(object _)
{
}
public void AddFoo(Action<object> action, object state)
{
ActionList.Add((action, state));
}

咱需要将 Foo 方法调用 AddFoo 方法,将 Foo 方法作为委托传入到 AddFoo 方法。此时咱有多个不同的方法来实现,为了让大家看到效果,咱来新建一个空白的 WPF 项目,在界面上放两个按钮

<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Margin="10,10,10,10" Click="Button1_OnClick">点击</Button>
<Button Margin="10,10,10,10" Click="Button2_OnClick">民与名扬</Button>
</StackPanel>
</Grid>

点击第一个按钮,此时咱使用简单的方法

private void Button1_OnClick(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i++)
{
AddFoo(Foo, null);
}
}

大部分的逻辑都是采用上面的方法传入的

Stephen Toub 大佬在性能优化上,是使用了传入对象自身,减少委托捕获的方法来优化性能。在点击 民与名扬 按钮时,触发以下代码

private void Button2_OnClick(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i++)
{
AddFoo(s => ((MainWindow) s).Foo(null), this);
}
}

以上的逻辑有点绕,大概就是在 AddFoo 方法在设计上允许传入最后一个参数,最后的一个参数将会自动作为参数传入给委托,也就是代码的 s 变量的值就是 this 的值。此时的优化在于调用了 AddFoo 方法加入的委托不需要对 this 有任何的引用,因此就可以让 编译器 进行缓存,不需要每次都创建新的委托对象

咱来运行代码对比一下性能,运行代码,在看到 WPF 应用打开时,点击内存的获取快照,然后点击第一个按钮,再点击内存的获取快照,可以看到下图

可以看到加了 100 多个对象

然后点击 民与名扬 按钮,点击内存的获取快照,可以看到下图

可以看到内存几乎没有添加任何对象。再多点击 民与名扬 按钮几次,点击内存的获取快照,可以看到几乎没有对象的分配。但是如果点击第一个按钮,点击内存的获取快照,可以看到内存加了很多对象

在性能优化时,可以考虑减少委托的捕获,如在传入实例的方法,也就是非静态的方法时,将会让委托捕获了 this 变量,需要创建委托。而如果 this 的变量是通过参数重新传入给委托的,此时可以做到不需要创建新的委托

这就是为什么有一些方法设计了委托传入,同时支持再传入一个 object 对象的原因。如 WPF 的 BeginInvoke 方法就是这样设计的,虽然咱现在推荐更多使用的是 InvokeAsync 方法

本文所有代码放在 githubgitee 欢迎访问

可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin fd1c53fefa4a6a27afeb785ee69f581f0af6ec5c

以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

获取代码之后,进入 NewhearfarairchaylucoLerhejuche 文件夹


知识共享许可协议

原文链接: http://blog.lindexi.com/post/dotnet-C-%E4%BD%BF%E7%94%A8%E6%97%A0%E6%8D%95%E8%8E%B7%E7%9A%84%E5%A7%94%E6%89%98%E5%8F%AF%E4%BB%A5%E8%8E%B7%E5%BE%97%E7%BC%96%E8%AF%91%E5%99%A8%E7%BC%93%E5%AD%98%E5%87%8F%E5%B0%91%E5%AF%B9%E8%B1%A1%E5%88%9B%E5%BB%BA

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。 欢迎转载、使用、重新发布,但务必保留文章署名 林德熙 (包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请与我 联系