私は別の質問でこのヒントを見て、誰かがこれがどのように機能するのかを私に説明できるかどうか疑問に思っていましたか?
try { return x; } finally { x = null; }
つまり、finally
節は実際にafterreturn
ステートメントを実行しますか?このコードはどの程度スレッドセーフではありませんか? w.r.tで実行できる追加のハッカーについて考えてください。この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
)を宣言し、値を変数に(処理されたブロック内に)配置し、ブロックを出た後に変数をロードし、それを返します。 Reflectorはこれを次のようにレンダリングします。
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"(返されたものだから)を出力し、それがxの新しい値なので "finally"を出力します。
もちろん、可変オブジェクト(StringBuilderなど)への参照を返す場合、finallyブロックのオブジェクトに加えられた変更は、戻り時に表示されます-これは戻り値自体には影響しません(これは単に参照)。
Finally節は、returnステートメントの後、実際に関数から戻る前に実行されます。スレッドの安全性とはほとんど関係ないと思います。これはハックではありません-tryブロックまたはcatchブロックで何をしても、最終的には常に実行されることが保証されています。
Marc GravellとJon Skeetの回答に加えて、オブジェクトや他の参照タイプが返されたときに同様に動作するが、いくつかの違いがあることに注意することが重要です。
返される「What」は、単純型と同じロジックに従います。
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に設定する呼び出しの前にレジスタに配置されました。
戻り時(および戻り値が決定された後)にフィールドの値の変更を保証したい場合にのみ、これが行われるのを見ることができます。