为啥 C# 8.0 中使用声明的变量的闭包不同?

Posted

技术标签:

【中文标题】为啥 C# 8.0 中使用声明的变量的闭包不同?【英文标题】:Why are closures different for variables from C# 8.0 using declarations?为什么 C# 8.0 中使用声明的变量的闭包不同? 【发布时间】:2019-12-26 19:47:26 【问题描述】:

我注意到 C# 8.0 编译器为使用 C# 8.0 using 声明声明的捕获的 IDisposable 变量构建闭包类的方式与使用经典 using 语句声明的变量不同。

考虑这个简单的类:

public class DisposableClass : IDisposable

    public void Dispose()  

还有这个示例代码:

public void Test()

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

    Action action = () => Console.Write($"disposable1disposable2");

编译器生成以下代码:

[CompilerGenerated]
private sealed class <>c__DisplayClass0_0

    public DisposableClass disposable1;

    public DisposableClass disposable2;

    internal void <Test>b__0()
    
        Console.Write(string.Format("01", 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();
        
    

这看起来完全没问题。但后来我注意到,如果我用 using 语句声明这两个变量,则生成的闭包类会完全不同。这是示例代码:

public void Test()

    using (var disposable1 = new DisposableClass())
    using (var disposable2 = new DisposableClass())
    
        Action action = () => Console.Write($"disposable1disposable2");
    

这就是我得到的:

[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("01", CS$<>8__locals1.disposable1, disposable2));
    

为什么会这样?其余的代码看起来是一样的,我认为 using 声明应该与一个 using 语句完全相同,该语句被认为是块它在其中声明的当前块。

更不用说使用声明生成闭包类的方式看起来更清晰,最重要的是,更容易通过反射进行探索。

如果有人知道为什么会发生这种情况,我希望得到一些见解。

谢谢!

【问题讨论】:

【参考方案1】:

编译器正在为每个“作用域”生成一个[CompilerGenerated]class...在第一个示例中,有一个作用域,即整个Test() 方法。在第二个示例中(您没有给出),有两个范围,两个 using

第二个例子的代码大概是:

public void Test()

    using (var disposable1 = new DisposableClass())
    
        using (var disposable2 = new DisposableClass())
        
            Action action = () => Console.Write($"disposable1disposable2");
        
    

正如 juharr 所指出的,这两个代码块产生相同的代码:

using (DisposableClass disposable1 = new DisposableClass(), disposable2 = new DisposableClass())

    Action action = () => Console.Write($"disposable1disposable2");

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

Action action = () => Console.Write($"disposable1disposable2");

【讨论】:

所以更好的比较是using(DisposableClass disposable1 = new DisposableClass(), disposable2 = new DisposableClass()) @juharr 是的,从我用sharplab 做的一个小测试来看,就像你写的:sharplab.io/… 和 还有sharplab.io/… 这很有道理,谢谢!此外,为了更清楚起见,我已经使用第二个示例中使用的代码更新了我的问题。我使用了两个堆叠的 using 语句,然后在第二个下方使用了一个块。

以上是关于为啥 C# 8.0 中使用声明的变量的闭包不同?的主要内容,如果未能解决你的问题,请参考以下文章

为啥实例变量在闭包内具有不同的值?

作用域闭包globalnonlocal

PHP 闭包获取外部变量和global关键字声明变量的区别

闭包-基础知识总结------彭记(011)

php 闭包:为啥绑定到静态类时匿名函数声明中的“静态”?

C#:为啥我必须在类的变量中使用公共访问修饰符?