C#允许我执行以下操作(来自MSDN的示例):
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
font4 = new Font
抛出后会发生什么?从我的理解,font3将泄漏资源,不会被处置。
using(... , ...)
支持嵌套使用?答案 0 :(得分:158)
没有
编译器将为每个变量生成一个单独的finally
块。
spec(§8.13)说:
当资源获取采取的形式 local-variable-declaration,有可能获得多个 给定类型的资源。表单的
using
声明using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement
正是如此 等同于嵌套的using语句序列:
using (ResourceType r1 = e1) using (ResourceType r2 = e2) ... using (ResourceType rN = eN) statement
答案 1 :(得分:67)
更新:我使用此问题作为可以找到here的文章的基础;请参阅此处以进一步讨论此问题。谢谢你的好问题!
虽然Schabse's answer当然是正确的并且回答了问题,但是您提出的问题有一个重要的变体:
如果{/ 1}} 在构造函数分配了非托管资源之后 ctor返回并填充
font4 = new Font()
之后会发生什么?参考
让我更清楚一点。假设我们有:
font4
现在我们有了
public sealed class Foo : IDisposable
{
private int handle = 0;
private bool disposed = false;
public Foo()
{
Blah1();
int x = AllocateResource();
Blah2();
this.handle = x;
Blah3();
}
~Foo()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
if (this.handle != 0)
DeallocateResource(this.handle);
this.handle = 0;
this.disposed = true;
}
}
}
这与
相同using(Foo foo = new Foo())
Whatever(foo);
行。假设{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
抛出。然后运行Whatever
块并释放资源。没问题。
假设finally
抛出。然后在分配资源之前抛出。该对象已被分配但ctor永远不会返回,因此永远不会填写Blah1()
。我们从未输入foo
所以我们也从未输入try
。对象引用已经成为孤立的。最终GC会发现并将其放在终结器队列中。 finally
仍为零,因此终结器不执行任何操作。 请注意,面对正在完成其构造函数从未完成的对象,终结器必须是健壮的。您必需来编写强大的终结器。这也是为什么你应该把写作终结者留给专家而不是试图自己做的另一个原因。
假设handle
抛出。抛出在资源分配后发生。但同样,Blah3()
永远不会被填充,我们永远不会进入foo
,并且终结器线程会清理对象。这次句柄非零,终结器清理它。同样,终结器在一个对象上运行,该对象的构造函数从未成功,但终结器仍在运行。显然它必须因为这一次,它有工作要做。
现在假设finally
抛出。在分配资源之后抛出,但在 Blah2()
填充之前!同样,终结器将运行,但现在handle
仍为零,我们泄漏了句柄!
您需要编写极聪明的代码,以防止发生此类泄漏。现在,就你的handle
资源而言,他究竟关心谁?我们泄漏了一个字体句柄,很重要。但是,如果您绝对肯定要求 每个非托管资源都要清理,无论例外的时间是什么,那么您就会遇到一个非常棘手的问题你的双手。
CLR必须通过锁来解决这个问题。从C#4开始,使用Font
语句的锁已经实现如下:
lock
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
已经非常仔细地编写,以便无论抛出什么异常,Enter
设置为true 当且仅当锁实际上已被采取。如果您有类似的要求,那么您需要实际写的是:
lockEntered
然后将 public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
巧妙地写为AllocateResource
,这样无论Monitor.Enter
内发生了什么,AllocateResource
都填入当且仅当它需要被解除分配。
描述这样做的技术超出了这个答案的范围。如果您有此要求,请咨询专家。
答案 2 :(得分:32)
作为@SLaks答案的补充,这里是代码的IL:
.method private hidebysig static
void Main (
string[] args
) cil managed
{
// Method begins at RVA 0x2050
// Code size 74 (0x4a)
.maxstack 2
.entrypoint
.locals init (
[0] class [System.Drawing]System.Drawing.Font font3,
[1] class [System.Drawing]System.Drawing.Font font4,
[2] bool CS$4$0000
)
IL_0000: nop
IL_0001: ldstr "Arial"
IL_0006: ldc.r4 10
IL_000b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
IL_0010: stloc.0
.try
{
IL_0011: ldstr "Arial"
IL_0016: ldc.r4 10
IL_001b: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string, float32)
IL_0020: stloc.1
.try
{
IL_0021: nop
IL_0022: nop
IL_0023: leave.s IL_0035
} // end .try
finally
{
IL_0025: ldloc.1
IL_0026: ldnull
IL_0027: ceq
IL_0029: stloc.2
IL_002a: ldloc.2
IL_002b: brtrue.s IL_0034
IL_002d: ldloc.1
IL_002e: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0033: nop
IL_0034: endfinally
} // end handler
IL_0035: nop
IL_0036: leave.s IL_0048
} // end .try
finally
{
IL_0038: ldloc.0
IL_0039: ldnull
IL_003a: ceq
IL_003c: stloc.2
IL_003d: ldloc.2
IL_003e: brtrue.s IL_0047
IL_0040: ldloc.0
IL_0041: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_0046: nop
IL_0047: endfinally
} // end handler
IL_0048: nop
IL_0049: ret
} // end of method Program::Main
注意嵌套的try / finally块。
答案 3 :(得分:17)
此代码(基于原始样本):
using System.Drawing;
public class Class1
{
public Class1()
{
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
}
}
它生成以下CIL(Visual Studio 2013中,定位.NET 4.5.1):
.method public hidebysig specialname rtspecialname
instance void .ctor() cil managed
{
// Code size 82 (0x52)
.maxstack 2
.locals init ([0] class [System.Drawing]System.Drawing.Font font3,
[1] class [System.Drawing]System.Drawing.Font font4,
[2] bool CS$4$0000)
IL_0000: ldarg.0
IL_0001: call instance void [mscorlib]System.Object::.ctor()
IL_0006: nop
IL_0007: nop
IL_0008: ldstr "Arial"
IL_000d: ldc.r4 10.
IL_0012: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string,
float32)
IL_0017: stloc.0
.try
{
IL_0018: ldstr "Arial"
IL_001d: ldc.r4 10.
IL_0022: newobj instance void [System.Drawing]System.Drawing.Font::.ctor(string,
float32)
IL_0027: stloc.1
.try
{
IL_0028: nop
IL_0029: nop
IL_002a: leave.s IL_003c
} // end .try
finally
{
IL_002c: ldloc.1
IL_002d: ldnull
IL_002e: ceq
IL_0030: stloc.2
IL_0031: ldloc.2
IL_0032: brtrue.s IL_003b
IL_0034: ldloc.1
IL_0035: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_003a: nop
IL_003b: endfinally
} // end handler
IL_003c: nop
IL_003d: leave.s IL_004f
} // end .try
finally
{
IL_003f: ldloc.0
IL_0040: ldnull
IL_0041: ceq
IL_0043: stloc.2
IL_0044: ldloc.2
IL_0045: brtrue.s IL_004e
IL_0047: ldloc.0
IL_0048: callvirt instance void [mscorlib]System.IDisposable::Dispose()
IL_004d: nop
IL_004e: endfinally
} // end handler
IL_004f: nop
IL_0050: nop
IL_0051: ret
} // end of method Class1::.ctor
如您所见,try {}
块在第一次分配之后才开始,该分配发生在IL_0012
。乍一看,出现以分配未受保护的代码中的第一项。但是,请注意结果存储在位置0.如果第二个分配失败,则执行外部 finally {}
块,并从位置0获取对象,即第一次分配font3
,并调用其Dispose()
方法。
有趣的是,使用dotPeek反编译此程序集会生成以下重构源:
using System.Drawing;
public class Class1
{
public Class1()
{
using (new Font("Arial", 10f))
{
using (new Font("Arial", 10f))
;
}
}
}
反编译代码确认一切正确,using
基本上扩展为嵌套using
。 CIL代码看起来有点令人困惑,在我正确理解发生的事情之前,我必须盯着它看几分钟,所以我并不惊讶一些“老太太的故事”已经开始萌芽了这个。但是,生成的代码是无懈可击的事实。
答案 4 :(得分:7)
以下是验证@SLaks答案的示例代码:
void Main()
{
try
{
using (TestUsing t1 = new TestUsing("t1"), t2 = new TestUsing("t2"))
{
}
}
catch(Exception ex)
{
Console.WriteLine("catch");
}
finally
{
Console.WriteLine("done");
}
/* outputs
Construct: t1
Construct: t2
Dispose: t1
catch
done
*/
}
public class TestUsing : IDisposable
{
public string Name {get; set;}
public TestUsing(string name)
{
Name = name;
Console.WriteLine("Construct: " + Name);
if (Name == "t2") throw new Exception();
}
public void Dispose()
{
Console.WriteLine("Dispose: " + Name);
}
}