首页 > 解决方案 > 为什么方法组会导致堆分配?

问题描述

我正在阅读https://blog.jetbrains.com/dotnet/2019/01/23/c-classes-memory-snapshots/并提到使用“方法组而不是 lambda”,它正确地指出:

我们想知道这种方法是否会产生内存流量。快速查看反编译的代码会说是的,不幸的是,它会的。

但是,不幸的是,他们没有解释为什么。

我在https://sharplab.io/创建了自己的示例:

using System;
using System.Collections.Generic;
using System.Linq;

public class ClassWithAllocation {
    private string selector(string y){
        return y + this.GetHashCode(); //random example to demonstrate use of `this`
    }

    public void Method() {
        var source= new List<string>();
        source.Select(selector); //compiled to: Enumerable.Select(source, new Func<string, string>(selector));
    }
}

如评论中所述,编译为在每次调用selector时创建一个。new Func

当使用不带闭包的 lambda 时,C# 会缓存一个Func可用于每次调用的实例。(并且:使用闭包这是不可能的,因为每次调用都必须“保留”不同的上下文。)

但是为什么上面的代码不能编译成下面这样的东西呢?

public class ClassWithoutAllocation {
    private readonly Func<string,string> _selector;
    
    public ClassWithoutAllocation(){
        _selector = new Func<string, string>(selector);
    }
    
    private string selector(string y){
        return y + this.GetHashCode(); //random example to demonstrate use of `this`
    }
    
    public void Method() {
        var source= new List<string>();
        source.Select(_selector); //compiled to: Enumerable.Select(source, _selector);
    }
}

SharpLab 显示代码基本上是按原样编译的。

后续问题:就是这样,上述模式(存储Func要使用的而不是方法组)是避免堆分配的好模式吗?

编辑

这个问题被关闭作为调用 func 时为什么有内存分配的假设副本。但是,正如@JonSkeet 在他的回答中所说:

不幸的是,C# 5 规范目前要求在每次运行时创建一个新的委托实例。

所有这一切都让我感到难过,在最新版本的 ECMA C# 标准中,编译器将被允许缓存方法组转换的结果。我不知道它何时/是否会这样做

所以,我仍然想知道为什么编译器不会像我上面的示例那样缓存引用(这比链接问题中的更简单)?不这样做有什么好处?这样做有什么弊端?

标签: c#lambdacompiler-constructionallocation

解决方案


推荐阅读