Home > Mobile >  How does the compiler internally translate a closure that contains a closure?
How does the compiler internally translate a closure that contains a closure?

Time:08-12

The code is partially modified and added so that you can practice the contents of the EFFECTIVE C# (COVERS 6.0) book.


I have a closure that contains a cloure. To understand that code, I tried How the compiler translates it internally. However, it failed to bind c.importantStatistic to the delegate at the end.

LeakingClosure is the code from the book, ResourceHogFilter and CheapNumberGenerator are what I additionally wrote to compile LeakingClosure.

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

First, The Query Expression was changed to the Method Call Syntax.

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

I then tried the following to make it appear similar to what the compiler would translate:

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

However it failed to bind c.importantStatistic at the end. Because, Average() returns a double, results.GetNumbers() returns an IEnumerable<int>.

What code did the compiler correctly represent the COMPILER_LeakingClosure class and the Closure class?

CodePudding user response:

You can use SharpLab.io to see what the compiler lowers your lambdas into. Usually, the lowered code will contain a bunch of names with unusual characters and is quite hard to read, but it is not impossible.

LeakingClosure is translated into:

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 here is analogous to your Closure class - both are used to capture the filter and importantStatistic. The difference here is that it also has two methods that contains the bodies of each of your lambdas:

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

Making the names more readable and improving the formatting, we get the following output:

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

CodePudding user response:

When the compiler creates nested classes internally, it includes local variables as well as lambda expressions that use local variables.

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;
}
  • Related