如果我的struct实现了IDisposable,那么当在using语句中使用时它会被装箱吗?

时间:2010-03-09 22:13:59

标签: c#

如果我的struct实现了IDisposable,那么当它在using语句中使用时会被装箱吗?

由于

编辑: 这个timedlock是一个结构并实现Idisposable。 http://www.interact-sw.co.uk/iangblog/2004/04/26/yetmoretimedlocking

编辑2:看看IL似乎如果你的结构公开Dispose()为public,当你忘记调用Dispose()时,编译器会在结构的一个实例超出范围时调用Dispose(例如,你是不使用“使用”声明)?

4 个答案:

答案 0 :(得分:26)

Eric Lippert

对结构上的IDisposable.Dispose的调用是作为受约束的虚拟调用生成的,大多数情况下它不会对值进行设置。

如果虚拟方法未由类型实现,则对值类型的受约束虚拟调用仅会对该值进行设置。可以通过值类型实现虚拟方法的唯一情况是,该方法是,例如,ToString,并由基类System.ValueType实现。

有关更多详细信息,请参阅CLI文档的第III部分第2.1节。

答案 1 :(得分:21)

这是When does a using-statement box its argument, when it's a struct?

的副本

更新:这个问题是the subject of my blog in March of 2011。谢谢你提出的好问题。

几点:

  • 正如其他人已经正确指出的那样,实现IDisposable的值类型在由于控件离开using语句而被处置时不会被装箱。
  • 这在技术上违反了C#规范。规范声明finally块应该具有((IDisposable)resource).Dispose();的语义,这显然是一个拳击转换。我们实际上并没有生成拳击转换。因为大部分时间这都是你想要的,所以我们不会失去任何睡眠。
  • 一次性价值类型似乎是一个潜在的坏主意。很容易意外地制作一个值类型的副本;毕竟它们是按价值复制的。
  • 为什么你在乎这个盒子呢?我希望你不要问这个,因为你想让dispose方法改变包含值类型的变量。那确实是一个坏主意。可变值类型是邪恶的。

答案 2 :(得分:12)

不,它没有盒装。

using不是方法调用。它是语法糖,编译器只是将其转换为:

MyClass m = new MyClass()
try
{
    // ...
}
finally
{
    if (m != null) {
        m.Dispose();
    }
}

它从不在声明中使用IDisposable,也从不将实例传递给其他任何东西。对于结构体,编译器实际上生成了更小的东西:

MyStruct m = new MyStruct()
try
{
    // ...
}
finally
{
    m.Dispose();
}

由于结构不能为空。

现在,要100%确定它永远不会装箱,看看IL。

试试这个示例代码:

class StructBox
{
    public static void Test()
    {
        using(MyStruct m = new MyStruct())
        {

        }


        MyStruct m2 = new MyStruct();
        DisposeSomething(m2);
    }

    public static void DisposeSomething(IDisposable disposable)
    {
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    private struct MyStruct : IDisposable
    {           
        public void Dispose()
        {
            // just kidding
        }
    }
}

然后看看IL:

.method public hidebysig static void Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] valuetype ConsoleApplication1.StructBox/MyStruct m,
        [1] valuetype ConsoleApplication1.StructBox/MyStruct m2)
    L_0000: ldloca.s m
    L_0002: initobj ConsoleApplication1.StructBox/MyStruct
    L_0008: leave.s L_0018
    L_000a: ldloca.s m
    L_000c: constrained ConsoleApplication1.StructBox/MyStruct
    L_0012: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_0017: endfinally 
    L_0018: ldloca.s m2
    L_001a: initobj ConsoleApplication1.StructBox/MyStruct
    L_0020: ldloc.1 
    L_0021: box ConsoleApplication1.StructBox/MyStruct
    L_0026: call void ConsoleApplication1.StructBox::DisposeSomething(class [mscorlib]System.IDisposable)
    L_002b: ret 
    .try L_0008 to L_000a finally handler L_000a to L_0018
}

行L_0000到L_0017表示m声明和using。没有拳击。

行L_0018到L_0026表示m2声明并呼叫DisposeSomething。见L_0021 box

答案 3 :(得分:6)

这不会包装盒子(让我感到惊讶)。我认为bnkdev的解释涵盖了它。以下是我如何证明它:

在下面写了快速控制台应用程序(注意,我包含了BoxTest(),我知道将在框中,这样我就可以比较了)。

然后我使用Reflector将编译后的输出反汇编为IL(你可以使用ILDASM)。


namespace StructInterfaceBoxingTest
{
    public struct TestStruct : IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            System.Console.WriteLine("Boo!");
        }

        #endregion
    }


    class Program
    {
        static void Main(string[] args)
        {
            using (TestStruct str = new TestStruct())
            {

            }
        }

        static void BoxTest()
        {
            TestStruct str = new TestStruct();
            ThisWillBox(str);
        }

        static void ThisWillBox(object item) {}
    }
}

好的,首先,这是IL for BoxTest - 请注意L_000a线上的方框说明(astersik强调我的)


.method private hidebysig static void BoxTest() cil managed
{
    .maxstack 1
    .locals init (
        [0] valuetype StructInterfaceBoxingTest.TestStruct str)
    L_0000: nop 
    L_0001: ldloca.s str
    L_0003: initobj StructInterfaceBoxingTest.TestStruct
    L_0009: ldloc.0 
    L_000a: **box** StructInterfaceBoxingTest.TestStruct
    L_000f: call void StructInterfaceBoxingTest.Program::ThisWillBox(object)
    L_0014: nop 
    L_0015: ret 
}

现在看看Main(我们在IDisposable结构中使用using语句):


.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 1
    .locals init (
        [0] valuetype StructInterfaceBoxingTest.TestStruct str)
    L_0000: nop 
    L_0001: ldloca.s str
    L_0003: initobj StructInterfaceBoxingTest.TestStruct
    L_0009: nop 
    L_000a: nop 
    L_000b: leave.s L_001c
    L_000d: ldloca.s str
    L_000f: constrained StructInterfaceBoxingTest.TestStruct
    L_0015: callvirt instance void [mscorlib]System.IDisposable::Dispose()
    L_001a: nop 
    L_001b: endfinally 
    L_001c: nop 
    L_001d: ret 
    .try L_0009 to L_000d finally handler L_000d to L_001c
}

请注意L_000f行上的受约束关键字。我无法找到该关键字究竟含义的参考,但如果您阅读bnkdev的帖子,我认为这是正在描述的受约束的虚拟调用。