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(... , ...)
を完全に回避する必要があるということですか?番号。
コンパイラーは、変数ごとに個別のfinally
ブロックを生成します。
spec (§8.13)によると:
リソース取得がローカル変数宣言の形式をとる場合、特定のタイプの複数のリソースを取得することができます。次の形式の
using
ステートメントusing (ResourceType r1 = e1, r2 = e2, ..., rN = eN) statement
ネストされたusingステートメントのシーケンスとまったく同じです。
using (ResourceType r1 = e1) using (ResourceType r2 = e2) ... using (ResourceType rN = eN) statement
[〜#〜] update [〜#〜]:見つけられる記事の基礎としてこの質問を使用しました here ;この問題の詳細については、それを参照してください。良い質問をありがとう!
Schabseの答え はもちろん正しいものであり、尋ねられた質問に答えますが、あなたが尋ねなかった質問には重要なバリエーションがあります:
font4 = new Font()
がafterをスローした場合に起こること---アンマネージリソースはコンストラクターによって割り当てられましたが、before ctorは戻り、_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();
}
}
_
OK。 Whatever
がスローするとします。次にfinally
ブロックが実行され、リソースの割り当てが解除されます。問題ない。
Blah1()
がスローするとします。次に、リソースが割り当てられる前にスローが発生します。オブジェクトは割り当てられましたが、ctorは戻らないため、foo
は入力されません。try
を入力したことがないため、finally
も入力しません。オブジェクト参照は孤立しています。最終的に、GCはそれを検出し、ファイナライザキューに入れます。 handle
はまだゼロなので、ファイナライザは何もしません。 ファイナライザは、コンストラクタが完了しなかったファイナライズされるオブジェクトに直面して堅牢である必要があることに注意してください。あなたは、この強力なファイナライザを書くためにrequiredです。これは、ファイナライザの作成を専門家に任せ、自分でやろうとしない別の理由です。
Blah3()
がスローするとします。リソースが割り当てられた後にスローが発生します。しかし、再び、foo
は入力されず、finally
を入力することはなく、オブジェクトはファイナライザースレッドによってクリーンアップされます。今回はハンドルがゼロ以外であり、ファイナライザがそれをクリーンアップします。繰り返しますが、ファイナライザはコンストラクタが成功しなかったオブジェクトで実行されていますが、ファイナライザはとにかく実行されます。明らかに、今回はやるべき仕事があったからです。
Blah2()
がスローするとします。リソースが割り当てられた後にスローが発生しますが、beforehandle
が入力されます!繰り返しますが、ファイナライザは実行されますが、handle
はまだゼロであり、ハンドルをリークします!
このリークの発生を防ぐために、extremely巧妙なコードを記述する必要があります。さて、あなたの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);
}
_
Enter
は非常に慎重に記述されているため、例外がスローされても、lockEntered
はtrueifおよびifロックが実際に取得されました。同様の要件がある場合は、実際に書く必要があります:
_ public Foo()
{
Blah1();
AllocateResource(ref handle);
Blah2();
Blah3();
}
_
AllocateResource
を_Monitor.Enter
_のように巧みに書くと、AllocateResource
の内部で何が起こっても、handle
はifとonly if割り当てを解除する必要があります。
そのためのテクニックを説明することは、この答えの範囲を超えています。この要件がある場合は、専門家に相談してください。
@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ブロックに注意してください。
このコード(元のサンプルに基づく):
_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 [〜#〜] (in Visual Studio 201 、ターゲティング 。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
_で行われる最初の割り当ての後まで開始されません。一見すると、これはappearを実行して、保護されていないコードの最初の項目を割り当てます。ただし、結果はロケーション0に格納されていることに注意してください。2番目の割り当てが失敗すると、outer_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
sに展開されていることを確認します。 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);
}
}