Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are closures different for variables from C# 8.0 using declarations?

I've noticed a difference in the way the C# 8.0 compiler builds closure classes for captured IDisposable variables that are declared with a C# 8.0 using declaration, as opposed to variables declared with the classic using statement.

Consider this simple class:

public class DisposableClass : IDisposable
{
    public void Dispose() { }
}

And this sample code:

public void Test()
{
    using var disposable1 = new DisposableClass();
    using var disposable2 = new DisposableClass();

    Action action = () => Console.Write($"{disposable1}{disposable2}");
}

The compiler generates this code:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public DisposableClass disposable1;

    public DisposableClass disposable2;

    internal void <Test>b__0()
    {
        Console.Write(string.Format("{0}{1}", disposable1, disposable2));
    }
}

public void Test()
{
    <>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
    <>c__DisplayClass0_.disposable1 = new DisposableClass();
    try
    {
        <>c__DisplayClass0_.disposable2 = new DisposableClass();
        try
        {
            Action action = new Action(<>c__DisplayClass0_.<Test>b__0);
        }
        finally
        {
            if (<>c__DisplayClass0_.disposable2 != null)
            {
                ((IDisposable)<>c__DisplayClass0_.disposable2).Dispose();
            }
        }
    }
    finally
    {
        if (<>c__DisplayClass0_.disposable1 != null)
        {
            ((IDisposable)<>c__DisplayClass0_.disposable1).Dispose();
        }
    }
}

This looks perfectly ok. But then I noticed that if I declare those two variables with a using statement, the closure class is generated pretty differently. This is the sample code:

public void Test()
{
    using (var disposable1 = new DisposableClass())
    using (var disposable2 = new DisposableClass())
    {
        Action action = () => Console.Write($"{disposable1}{disposable2}");
    }
}

And this is what I get:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
    public DisposableClass disposable1;
}

[CompilerGenerated]
private sealed class <>c__DisplayClass0_1
{
    public DisposableClass disposable2;

    public <>c__DisplayClass0_0 CS$<>8__locals1;

    internal void <Test>b__0()
    {
        Console.Write(string.Format("{0}{1}", CS$<>8__locals1.disposable1, disposable2));
    }
}

Why does this happen? The rest of the code looks identical, and I thought that a using declaration was supposed to be exactly the same as a using statement that considered as block the current block it's declared within.

Not to mention that the way the closure class is generated for using declarations looks way clearer, and most importantly, much easier to explore through reflection.

I'd love some insights, if anyone knows why this is happening.

Thanks!

like image 786
Sergio0694 Avatar asked Aug 21 '19 13:08

Sergio0694


People also ask

What is the difference between closures and functions?

Difference between Function and ClosureFunction is declared using func keyword whereas Closure doesn't have func keyword. Function has always name but Closure doesn't have. Function doesn't have in keyword but closure has in the keyword.

Are C functions closures?

Although C was created two decades after Lisp, it nonetheless lacks support for closures.

What are closures in C?

A closure is a kind of object that contains a pointer or reference of some kind to a function to be executed along with the an instance of the data needed by the function.

What are closure variables?

A closure is the combination of a function and the lexical environment within which that function was declared. This environment consists of any local variables that were in-scope at the time the closure was created.

Why do we use closure in C++?

Applications. The use of closures is associated with languages where functions are first-class objects, in which functions can be returned as results from higher-order functions, or passed as arguments to other function calls; if functions with free variables are first-class, then returning one creates a closure.

What is the difference between a and B closures?

the values of a and b are closures, in both cases produced by returning a nested function with a free variable from the enclosing function, so that the free variable binds to the value of parameter x of the enclosing function. The closures in a and b are functionally identical.

What is the closure of a variable?

In other words, the variable a is closed by (or bound in) the execution environment. That’s where the term Closure comes from. Note that the described behavior is not C# specific at all.

When is a closure distinct from a function with free variables?

Lastly, a closure is only distinct from a function with free variables when outside of the scope of the non-local variables, otherwise the defining environment and the execution environment coincide and there is nothing to distinguish these (static and dynamic binding cannot be distinguished because the names resolve to the same values).


1 Answers

The compiler is generating a [CompilerGenerated] class for each "scope"... In the first example, there is a single scope, the whole Test() method. In the second example (that you don't give), there are two scopes, the two using.

The code of the second example is probably:

public void Test()
{
    using (var disposable1 = new DisposableClass())
    {
        using (var disposable2 = new DisposableClass())
        {
            Action action = () => Console.Write($"{disposable1}{disposable2}");
        }
    }
}

As noted by juharr, these two blocks of code produce the same code:

using (DisposableClass disposable1 = new DisposableClass(), disposable2 = new DisposableClass())
{
    Action action = () => Console.Write($"{disposable1}{disposable2}");
}

and

using var disposable1 = new DisposableClass();
using var disposable2 = new DisposableClass();

Action action = () => Console.Write($"{disposable1}{disposable2}");
like image 78
xanatos Avatar answered Oct 13 '22 09:10

xanatos