编译器如何在内部转换包含闭包的闭包?

How does the compiler internally translate a closure that contains a closure?

提问人:isakgo_ 提问时间:8/12/2022 最后编辑:isakgo_ 更新时间:8/12/2022 访问量:47

问:

对代码进行了部分修改和添加,以便您可以练习 EFFECTIVE C# (COVERS 6.0) 一书中的内容。


我有一个包含 cloure 的封口。为了理解这段代码,我尝试了编译器如何在内部翻译它。但是,它最终未能绑定到委托。c.importantStatistic

LeakingClosure是书中的代码,也是我另外编写的编译代码。ResourceHogFilterCheapNumberGeneratorLeakingClosure

private static IEnumerable<int> LeakingClosure(int mod)
{
    var filter = new ResourceHogFilter();
    var source = new CheapNumberGenerator();
    var results = new CheapNumberGenerator();

    var importantStatistic =
        (from num in source.GetNumbers(50)
            where filter.PassesFilter(num)
            select num).Average();

    return 
        from num in results.GetNumbers(100)
        where num > importantStatistic
        select num;
}
public class ResourceHogFilter
{
    public bool PassesFilter(int num)
    {
        return num < 30;
    }
}
public class CheapNumberGenerator
{
    public IEnumerable<int> GetNumbers(int size)
    {
        for (var i = 0; i < size; i++)
            yield return i++;
    }
}

首先,将查询表达式更改为方法调用语法。

private static IEnumerable<int> METHOD_LeakingClosure(int mod)
{
    var filter = new ResourceHogFilter();
    var source = new CheapNumberGenerator();
    var results = new CheapNumberGenerator();

    var importantStatistic = source
        .GetNumbers(50)
        .Where(num => filter.PassesFilter(num))
        .Average();

    return results
        .GetNumbers(100)
        .Where(num => num > importantStatistic);
}

然后,我尝试了以下操作,使其看起来类似于编译器将要翻译的内容:

private static IEnumerable<int> COMPILER_LeakingClosure(int mod)
{
    var source = new CheapNumberGenerator();
    var results = new CheapNumberGenerator();

    var c = new Closure();
    c.filter = new ResourceHogFilter();

    var importantStatistic = source
        .GetNumbers(50)
        .Where(new Func<int, bool>(c.filter.PassesFilter))
        .Average();

    c.importantStatistic = importantStatistic;

    return results
        .GetNumbers(100)
        .Where(new Func<int, bool>(c.importantstatistic)); // compile-time error
}
private sealed class Closure
{
    public ResourceHogFilter filter;
    public double importantStatistic;
}

但是,它最终未能绑定。因为,返回一个 ,返回一个 .c.importantStatisticAverage()doubleresults.GetNumbers()IEnumerable<int>

编译器正确地表示了类和类的哪些代码?COMPILER_LeakingClosureClosure

C# LINQ 闭包

评论


答:

1赞 isakgo_ 8/12/2022 #1

当编译器在内部创建嵌套类时,它包括局部变量以及使用局部变量的 lambda 表达式。

private static IEnumerable<int> COMPILER_LeakingClosure(int mod)
{
    var source = new CheapNumberGenerator();
    var results = new CheapNumberGenerator();

    var c = new Closure();
    c.filter = new ResourceHogFilter();
        
    var importantStatistic = source
        .GetNumbers(50)
        .Where(new Func<int, bool>(c.PassesFilter))
        .Average();

    c.importantStatistic = importantStatistic;

    return results
        .GetNumbers(100)
        .Where(new Func<int, bool>(c.GreaterThanImportantStatistic));
}
private sealed class Closure
{
    public ResourceHogFilter filter;
    public bool PassesFilter(int num) => filter.PassesFilter(num);

    public double importantStatistic;
    public bool GreaterThanImportantStatistic(int num) => num > importantStatistic;
}
1赞 Sweeper 8/12/2022 #2

您可以使用 SharpLab.io 来查看编译器将 lambda 降低到什么程度。通常,降低的代码将包含一堆带有不寻常字符的名称,并且很难阅读,但这并非不可能。

LeakingClosure翻译为:

public static IEnumerable<int> LeakingClosure(int mod)
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.filter = new ResourceHogFilter();
    CheapNumberGenerator cheapNumberGenerator = new CheapNumberGenerator();
    CheapNumberGenerator cheapNumberGenerator2 = new CheapNumberGenerator();
    <>c__DisplayClass0_.importantStatistic = Enumerable.Average(Enumerable.Where(cheapNumberGenerator.GetNumbers(50), new Func<int, bool>(<>c__DisplayClass0_.<LeakingClosure>b__0)));
    return Enumerable.Where(cheapNumberGenerator2.GetNumbers(100), new Func<int, bool>(<>c__DisplayClass0_.<LeakingClosure>b__1));
}

<>c__DisplayClass0_0这里类似于您的类 - 两者都用于捕获 和 。这里的区别在于,它还有两个方法,包含每个 lambda 的主体:ClosurefilterimportantStatistic

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public ResourceHogFilter filter;

    public double importantStatistic;

    internal bool <LeakingClosure>b__0(int num)
    {
        return filter.PassesFilter(num);
    }

    internal bool <LeakingClosure>b__1(int num)
    {
        return (double)num > importantStatistic;
    }
}

使名称更具可读性并改进格式,我们得到以下输出:

private static IEnumerable<int> COMPILER_LeakingClosure(int mod)
{
    Closure c = new Closure();
    c.filter = new ResourceHogFilter();
    CheapNumberGenerator cheapNumberGenerator = new CheapNumberGenerator();
    CheapNumberGenerator cheapNumberGenerator2 = new CheapNumberGenerator();
    c.importantStatistic = Enumerable.Average(
        Enumerable.Where(
            cheapNumberGenerator.GetNumbers(50), new Func<int, bool>(c.WhereLambda1)
        )
    );
    return Enumerable.Where(
        cheapNumberGenerator2.GetNumbers(100), new Func<int, bool>(c.WhereLambda2)
    );
}

private sealed class Closure
{
    public ResourceHogFilter filter;
    public double importantStatistic; 
    
    internal bool WhereLambda1(int num)
    {
        return filter.PassesFilter(num);
    }

    internal bool WhereLambda2(int num)
    {
        return num > importantStatistic;
    }
}

评论

0赞 isakgo_ 8/12/2022
使用 SharpLab.io,您可以尝试各种事情:D