私は別の質問でこのヒントを見て、誰かがこれが一体どのように機能するかを私に説明できるかどうか疑問に思っていましたか?
try { return x; } finally { x = null; }
つまり、finally
文節はステートメントの後に実際に実行されreturn
ますか?このコードはどのようにスレッドセーフではありませんか?このtry-finally
ハックに対して実行できる追加のハッカーについて考えることができますか?
私は別の質問でこのヒントを見て、誰かがこれが一体どのように機能するかを私に説明できるかどうか疑問に思っていましたか?
try { return x; } finally { x = null; }
つまり、finally
文節はステートメントの後に実際に実行されreturn
ますか?このコードはどのようにスレッドセーフではありませんか?このtry-finally
ハックに対して実行できる追加のハッカーについて考えることができますか?
回答:
いいえ-ILレベルでは、例外処理ブロック内から戻ることはできません。それは本質的にそれを変数に格納し、後で戻ります
つまり、次のようになります。
int tmp;
try {
tmp = ...
} finally {
...
}
return tmp;
たとえば(リフレクターを使用):
static int Test() {
try {
return SomeNumber();
} finally {
Foo();
}
}
コンパイル:
.method private hidebysig static int32 Test() cil managed
{
.maxstack 1
.locals init (
[0] int32 CS$1$0000)
L_0000: call int32 Program::SomeNumber()
L_0005: stloc.0
L_0006: leave.s L_000e
L_0008: call void Program::Foo()
L_000d: endfinally
L_000e: ldloc.0
L_000f: ret
.try L_0000 to L_0008 finally handler L_0008 to L_000e
}
これは基本的にローカル変数(CS$1$0000
)を宣言し、値を変数(処理されたブロック内)に配置し、ブロックを終了した後、変数をロードして返します。リフレクターはこれを次のようにレンダリングします。
private static int Test()
{
int CS$1$0000;
try
{
CS$1$0000 = SomeNumber();
}
finally
{
Foo();
}
return CS$1$0000;
}
finallyステートメントは実行されますが、戻り値は影響を受けません。実行順序は次のとおりです。
デモする短いプログラムを次に示します。
using System;
class Test
{
static string x;
static void Main()
{
Console.WriteLine(Method());
Console.WriteLine(x);
}
static string Method()
{
try
{
x = "try";
return x;
}
finally
{
x = "finally";
}
}
}
これは "try"(それが返されるため)を出力し、次に "finally"を出力します。これがxの新しい値だからです。
もちろん、可変オブジェクト(StringBuilderなど)への参照を返す場合、finallyブロックのオブジェクトに加えられた変更は、戻り時に表示されます-これは戻り値自体には影響しません(これは単なる参照)。
return
ステートメントのポイントで式を評価し、その値が返されます。コントロールがメソッドを離れると、式は評価されません。
Marc GravellとJon Skeetの回答に加えて、オブジェクトや他の参照型が返されたときに同様に動作するが、いくつかの違いがあることに注意することが重要です。
返される「何」は、単純型と同じロジックに従います。
class Test {
public static Exception AnException() {
Exception ex = new Exception("Me");
try {
return ex;
} finally {
// Reference unchanged, Local variable changed
ex = new Exception("Not Me");
}
}
}
返される参照は、ローカル変数にfinallyブロックで新しい参照が割り当てられる前に、既に評価されています。
実行は本質的に:
class Test {
public static Exception AnException() {
Exception ex = new Exception("Me");
Exception CS$1$0000 = null;
try {
CS$1$0000 = ex;
} finally {
// Reference unchanged, Local variable changed
ex = new Exception("Not Me");
}
return CS$1$0000;
}
}
違いは、オブジェクトのプロパティ/メソッドを使用して変更可能な型を変更することがまだ可能であり、注意しないと予期しない動作が発生する可能性があることです。
class Test2 {
public static System.IO.MemoryStream BadStream(byte[] buffer) {
System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
try {
return ms;
} finally {
// Reference unchanged, Referenced Object changed
ms.Dispose();
}
}
}
try-return-finallyについて考慮すべき2番目のことは、「参照によって」渡されたパラメーターは、戻り後も変更できることです。戻り値のみが評価され、返されるのを待っている一時変数に格納されます。他の変数は通常の方法で変更されます。最終的にこの方法でブロックするまで、outパラメータのコントラクトが満たされない場合さえあります。
class ByRefTests {
public static int One(out int i) {
try {
i = 1;
return i;
} finally {
// Return value unchanged, Store new value referenced variable
i = 1000;
}
}
public static int Two(ref int i) {
try {
i = 2;
return i;
} finally {
// Return value unchanged, Store new value referenced variable
i = 2000;
}
}
public static int Three(out int i) {
try {
return 3;
} finally {
// This is not a compile error!
// Return value unchanged, Store new value referenced variable
i = 3000;
}
}
}
他のフローコンストラクトと同様に、「try-return-finally」はその場所にあり、実際にコンパイルする構造を書き込むよりも見栄えのよいコードを可能にします。しかし、それは落とし穴を避けるために注意深く使われなければなりません。
場合はx
ローカル変数であるとして、私は、ポイントが表示されないx
効果的方法が終了するとにかくnullに設定し、それはセットを呼び出す前にレジスタに格納されて以来、戻り値の値は(nullではないであろうx
nullへ)。
戻り時(および戻り値が決定された後)にフィールドの値の変更を保証したい場合にのみ、これが行われているのを見ることができます。