Try-Finally Dispose 和 Using-statement 的区别

5

我一直在深入研究微软的代码分析,偶然发现了一些有趣的事情。.NET似乎会根据调用方式使用两种不同类型的Dispose方法。请看以下两个选项:

public void SqlConnectionUsing()
{
    using (SqlConnection connection = new SqlConnection())
    {
    }
}

public void SqlConnectionFinally()
{
    SqlConnection connection = new SqlConnection();
    try
    {
    }
    finally
    {
        connection.Dispose();
    }
}

两个选项在编译时被翻译为完全相同的内容。使用语句变成了一个try-finally语句,try块中包含对Dispose方法的调用,finally块中也包含对Dispose方法的调用。
我说“一个”Dispose方法,因为Dispose方法的种类取决于您编写代码的方式。
当选择“using语句”时,将调用callvirt instance void [mscorlib]System.IDisposable::Dispose()(这是确切的IL代码行)。
而手动选择try-finally选项,则Dispose语句更改为:callvirt instance void [System]System.ComponentModel.Component::Dispose()
为什么会有调用不同dispose函数的区别?
如果需要,我可以添加整个IL代码。
2个回答

4

在编译时,using语句的翻译如下:

try
{
}
finally
{
    ((IDisposable)connection).Dispose();
}

您可以在同一类中定义两个Dispose()方法,一个明确针对IDisposable接口,另一个为类方法:

public class X : IDisposable
{
    void IDisposable.Dispose() { } 
    public void Dispose() { }
}

如果让这些方法的行为不同,你可能会让某人的一天非常糟糕。

此外,您可以在一个没有实现IDisposable的类中创建一个Dispose()方法,但是您将无法将其放在using语句中。


2
FYI,using 附带了 null 检查。 - weston
1
这是C#规范中的8.13节。 - usr
这解释了很多!谢谢。还有@weston:我不知道那个,知道了很好。确实有道理,using会自动调用Dispose;不进行空值检查可能会导致异常。 - Matthijs
@Matthijs - 还有一个需要IDisposable实现者注意的陷阱,请参见Using clause fails to call Dispose? - groverboy
实际上,这种行为与((IDisposable)connection).Dispose()并不完全等价,而是CallDispose(connection),假设void CallDispose<T>(T it) where T:IDisposable { if (it != null) it.Dispose(); }。如果it是一个带有(可能是无用的)IDisposable实现的结构体,则前者将对其进行装箱,但后者不会。请注意,由于C#会处理using语句中参数的副本,因此即使在可以提高效率的情况下使用具有非平凡Dispose方法的结构体也是不可能有用的。 - supercat

0

这是因为 using 总是使用 IDisposable.Dispose() 并从那里向上走(因此它实际上是一个接口方法调用)。

实际上是这样的:

using (IDisposable x = ...)
{ }

最后,实际上你正在调用Component.Dispose()方法,因为它是SqlConnection的最高可用Dispose方法。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接