当一个struct是一个struct时,using语句是什么时候用它的参数?

ang*_*son 6 c# struct boxing idisposable using

我对以下代码有一些疑问:

using System;

namespace ConsoleApplication2
{
    public struct Disposable : IDisposable
    {
        public void Dispose() { }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (Test()) { }
        }

        static Disposable Test()
        {
            return new Disposable();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  • Disposable结构进行操作的using语句是否会从Test()结构框中返回?
  • 我怎样才能找到自己的答案?

为了试图找出自己,我检查了上面代码生成的IL,这里是Main(...)方法的IL :

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype ConsoleApplication2.Disposable CS$3$0000)
    L_0000: call valuetype ConsoleApplication2.Disposable ConsoleApplication2.Program::Test()
    L_0005: stloc.0 
    L_0006: leave.s L_0016
    L_0008: ldloca.s CS$3$0000
    L_000a: constrained ConsoleApplication2.Disposable
    L_0010: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0015: endfinally 
    L_0016: ret 
    .try L_0006 to L_0008 finally handler L_0008 to L_0016
}
Run Code Online (Sandbox Code Playgroud)

我怀疑那里调用虚方法,on L_0010会引入装箱操作,但实际box指令不在这里.

我问的原因是,不久前,大概1 - 2年,我在网上看到了某人评论的使用声明的"优化".这种情况是使用using语句作为对象的短时间锁定的语法,其中在方法中获取了锁,并且返回了一个struct,当处理掉时,会释放锁,代码就像这样:

using (LockTheObject())
{
    // use the object
}
Run Code Online (Sandbox Code Playgroud)

和评论是,通过改变的返回类型LockTheObject,从方法IDisposable到实际使用的结构,避免了拳击.

但我想知道这是真的还是真的.

谁能指出我正确的方向?如果,为了看到框操作,我将不得不检查运行时汇编代码,请给我一个示例来查找,我非常精通汇编代码,所以这不是问题,但没有跳出来当我看着那个时,在我身边.

And*_*are 7

看起来好像放入using语句中的任何值类型都不会被装箱.这似乎是一个C#优化,因为当实现的值类型IDisposableusing语句中而不在任何其他上下文中时,才会省略装箱.

有关详细信息,请参阅使用声明和一次性值类型:

不久之前,Ian Griffiths写了一篇关于他的TimedLock类的改进,其中他将它从一个类改为一个struct.此更改导致实现IDisposable的值类型.当我很快忘记的时候,脑子里有一个唠叨的问题.问题是,在调用Dispose时,该类型的实例不会被装箱吗?

而且哦不!不是TimedLock了!:

John Sands指出我在最近的博客中展示的代码中存在一个缺陷,即在不放弃C#lock关键字的大部分便利性的情况下使用锁定超时.


Sam*_*ell 5

值类型上的实例方法将this形参作为其第一个参数,类似于引用类型上的实例方法。但是,本例中的参数是指向对象数据的托管指针,而不是对装箱对象的引用。你可能会发现它在内存中的布局如下:

\n\n
Unboxed object:\n-----------------------------------------\n|              DATA                     |\n-----------------------------------------\n ^ managed pointer to struct\n\nBoxed object:\n------------------------------------------------------------\n| GC/Object header |              [Boxed] DATA             |\n------------------------------------------------------------\n                    ^ The 'unbox' opcode gives a managed pointer to the boxed data\n ^ A *reference* to any instance of a reference type or boxed object, points here\n
Run Code Online (Sandbox Code Playgroud)\n\n

DATA在这两种情况下都是相同的\xc2\xb9。

\n\n

值类型上的实例方法需要专门指向数据的托管指针,因此不需要对对象进行装箱。正如您在上面看到的,constrained操作码在调用之前使用。它告诉运行时以下callvirt指令正在接收指向结构的托管指针,ConsoleApplication2.Disposable而不是它通常接收的对象引用。这样做,JIT 可以解析Dispose()结构实现的密封重载并直接调用它,而无需装箱对象。如果没有constrained前缀,传递给指令的对象callvirt必须是对象引用,因为标准虚拟调用动态解析过程基于 GC/对象标头始终位于预期位置的事实 - 是的,这将强制值类型的装箱。

\n\n

Nullable<T>\xc2\xb9 我们暂时忽略。

\n


Eri*_*ert 5

这是如果我的结构实现IDisposable的副本,那么在using语句中使用它时是否将其装箱?

更新:这个问题是我2011年3月博客的主题。感谢您提出的好问题!

安德鲁·黑尔的答案是正确的。我只想添加一个有趣的注释。我们发出的优化-在可能的情况下使用受约束的callvirt跳过装箱-实际上严格来说违反了C#规范。规范指出,我们为值类型资源生成的finally块是:

     finally 
     {
         ((IDisposable)resource).Dispose();
     }
Run Code Online (Sandbox Code Playgroud)

这显然是对值类型的装箱转换。可以构造人为的方案,其中在实现中缺少装箱是可见的。

(感谢Vladimir Reshetnikov向我指出了此规范违规行为。)