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到实际使用的结构,避免了拳击.
但我想知道这是真的还是真的.
谁能指出我正确的方向?如果,为了看到框操作,我将不得不检查运行时汇编代码,请给我一个示例来查找,我非常精通汇编代码,所以这不是问题,但没有跳出来当我看着那个时,在我身边.
看起来好像放入using语句中的任何值类型都不会被装箱.这似乎是一个C#优化,因为当实现的值类型IDisposable在using语句中而不在任何其他上下文中时,才会省略装箱.
有关详细信息,请参阅使用声明和一次性值类型:
不久之前,Ian Griffiths写了一篇关于他的TimedLock类的改进,其中他将它从一个类改为一个struct.此更改导致实现IDisposable的值类型.当我很快忘记的时候,脑子里有一个唠叨的问题.问题是,在调用Dispose时,该类型的实例不会被装箱吗?
John Sands指出我在最近的博客中展示的代码中存在一个缺陷,即在不放弃C#
lock关键字的大部分便利性的情况下使用锁定超时.
值类型上的实例方法将this形参作为其第一个参数,类似于引用类型上的实例方法。但是,本例中的参数是指向对象数据的托管指针,而不是对装箱对象的引用。你可能会发现它在内存中的布局如下:
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\nRun Code Online (Sandbox Code Playgroud)\n\nDATA在这两种情况下都是相同的\xc2\xb9。
值类型上的实例方法需要专门指向数据的托管指针,因此不需要对对象进行装箱。正如您在上面看到的,constrained操作码在调用之前使用。它告诉运行时以下callvirt指令正在接收指向结构的托管指针,ConsoleApplication2.Disposable而不是它通常接收的对象引用。这样做,JIT 可以解析Dispose()结构实现的密封重载并直接调用它,而无需装箱对象。如果没有constrained前缀,传递给指令的对象callvirt必须是对象引用,因为标准虚拟调用动态解析过程基于 GC/对象标头始终位于预期位置的事实 - 是的,这将强制值类型的装箱。
Nullable<T>\xc2\xb9 我们暂时忽略。
这是如果我的结构实现IDisposable的副本,那么在using语句中使用它时是否将其装箱?
更新:这个问题是我2011年3月博客的主题。感谢您提出的好问题!
安德鲁·黑尔的答案是正确的。我只想添加一个有趣的注释。我们发出的优化-在可能的情况下使用受约束的callvirt跳过装箱-实际上严格来说违反了C#规范。规范指出,我们为值类型资源生成的finally块是:
finally
{
((IDisposable)resource).Dispose();
}
Run Code Online (Sandbox Code Playgroud)
这显然是对值类型的装箱转换。可以构造人为的方案,其中在实现中缺少装箱是可见的。
(感谢Vladimir Reshetnikov向我指出了此规范违规行为。)