Ben*_*aum 106 c# using using-statement
C#允许我执行以下操作(来自MSDN的示例):
using (Font font3 = new Font("Arial", 10.0f),
font4 = new Font("Arial", 10.0f))
{
// Use font3 and font4.
}
Run Code Online (Sandbox Code Playgroud)
如果发生什么font4 = new Font抛出?从我的理解,font3将泄漏资源,不会被处置.
using(... , ...)应该完全避免使用嵌套使用?SLa*_*aks 158
没有.
编译器将为finally每个变量生成一个单独的块.
该规范(§8.13)说:
当资源获取采用局部变量声明的形式时,可以获取给定类型的多个资源.一个
using形式的声明Run Code Online (Sandbox Code Playgroud)using (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement恰好等同于嵌套的using语句序列:
Run Code Online (Sandbox Code Playgroud)using (ResourceType r1 = e1) using (ResourceType r2 = e2) ... using (ResourceType rN = eN) statement
Eri*_*ert 67
更新:我用这个问题作为一篇文章的基础,可以在这里找到; 请参阅此处以进一步讨论此问题.谢谢你的好问题!
虽然Schabse的答案当然是正确的并回答了所提出的问题,但是你提出的问题有一个重要的变体:
如果在构造函数分配非托管资源之后但在 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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在我们有
using(Foo foo = new Foo())
Whatever(foo);
Run Code Online (Sandbox Code Playgroud)
这是一样的
{
Foo foo = new Foo();
try
{
Whatever(foo);
}
finally
{
IDisposable d = foo as IDisposable;
if (d != null)
d.Dispose();
}
}
Run Code Online (Sandbox Code Playgroud)
好.假设Whatever抛出.然后finally块运行并释放资源.没问题.
假设Blah1()抛出.然后在分配资源之前抛出.该对象已被分配但ctor永远不会返回,因此foo永远不会被填充.我们从未进入过,try所以我们永远不会进入finally.对象引用已经成为孤立的.最终GC会发现并将其放在终结器队列中. handle仍然为零,所以终结器什么都不做. 请注意,终结器在面对正在完成的构造函数永远不会完成的对象时需要是健壮的.您需要编写强大的终结器.这也是为什么你应该把写作终结者留给专家而不是试图自己做的另一个原因.
假设Blah3()抛出.抛出在资源分配后发生.但是,再次,foo永远不会填写,我们永远不会进入finally,并且终结器线程清理对象.这次句柄非零,终结器清理它.同样,终结器在一个对象上运行,该对象的构造函数从未成功,但终结器仍然运行.显然它必须因为这一次,它有工作要做.
现在假设Blah2()抛出.抛出在资源分配之后但在 handle填写之前!同样,终结器将运行,但现在handle仍然为零,我们泄漏手柄!
您需要编写非常聪明的代码以防止此泄漏发生.现在,在您的Font资源的情况下,谁在乎?我们泄漏了一个字体句柄,很重要.但是,如果你绝对肯定需要的是每一个非托管资源清理无论什么异常的时机,那么你有你的手一个非常棘手的问题.
CLR必须用锁来解决这个问题.从C#4开始,使用该lock语句的锁已经实现如下:
bool lockEntered = false;
object lockObject = whatever;
try
{
Monitor.Enter(lockObject, ref lockEntered);
lock body here
}
finally
{
if (lockEntered) Monitor.Exit(lockObject);
}
Run Code Online (Sandbox Code Playgroud)
Enter已经非常仔细地编写,因此无论抛出什么异常,当且仅当实际执行锁定时才lockEntered设置为true .如果您有类似的要求,那么您需要实际写入:
public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
Run Code Online (Sandbox Code Playgroud)
并AllocateResource巧妙地写Monitor.Enter出来,无论内部发生什么AllocateResource,当且仅当需要解除分配时,才会handle填写.
描述这样做的技术超出了这个答案的范围.如果您有此要求,请咨询专家.
Dav*_*nan 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
Run Code Online (Sandbox Code Playgroud)
注意嵌套的try/finally块.
Tim*_*ong 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.
}
}
}
Run Code Online (Sandbox Code Playgroud)
它生成以下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
Run Code Online (Sandbox Code Playgroud)
如您所见,该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))
;
}
}
}
Run Code Online (Sandbox Code Playgroud)
反编译的代码确认一切都是正确的,并且using基本上扩展为嵌套的usings.CIL代码看起来有点令人困惑,在我正确理解发生的事情之前,我必须盯着它看几分钟,所以我并不惊讶一些"老太太的故事"已经开始萌芽了这个.但是,生成的代码是无懈可击的事实.
以下是验证@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);
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7516 次 |
| 最近记录: |