try {return x;で実際に何が起こるか }最後に{x = null; }ステートメント?


259

私は別の質問でこのヒントを見て、誰かがこれが一体どのように機能するかを私に説明できるかどうか疑問に思っていましたか?

try { return x; } finally { x = null; }

つまり、finally文節はステートメントの後に実際に実行さreturnますか?このコードはどのようにスレッドセーフではありませんか?このtry-finallyハックに対して実行できる追加のハッカーについて考えることができますか?

回答:


235

いいえ-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;
}

10
これは正確にocdedioが言ったことではありません:最終的には、戻り値の計算後、実際に関数から戻る前に実行されますか?
mmmmmmmm

「例外処理ブロック」このシナリオは、例外や例外処理とは関係がないと思います。これは、.NETが最終的にリソースガードコンストラクトを実装する方法についてです。
g.pickardou 2015年

361

finallyステートメントは実行されますが、戻り値は影響を受けません。実行順序は次のとおりです。

  1. returnステートメントが実行される前のコード
  2. returnステートメントの式が評価されます
  3. 最後にブロックが実行されます
  4. 手順2で評価された結果が返されます

デモする短いプログラムを次に示します。

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ブロックのオブジェクトに加えられた変更は、戻り時に表示されます-これは戻り値自体には影響しません(これは単なる参照)。


質問したいのですが、Visual Studioには、実行時に書き込まれたC#コード用に生成された中間言語(IL)を表示するオプションはありますか?
Enigma State

「可変オブジェクト(StringBuilderなど)への参照を返す場合、finallyブロックでオブジェクトに加えられた変更は、finallyブロックでnullに設定されている場合は例外です」この場合、null以外のオブジェクトが返されます。
Nick

4
@Nick:それはオブジェクトへの変更ではありません-それは変数への変更です。変数の以前の値が参照していたオブジェクトにはまったく影響しません。ですから、例外ではありません。
Jon Skeet 2013

3
@Skeet「returnステートメントがコピーを返す」という意味ですか?
prabhakaran 2014年

4
@prabhakaran:それはreturnステートメントのポイントで式を評価し、その値が返されます。コントロールがメソッドを離れると、式は評価されません
Jon Skeet、2014年

19

finally節は、returnステートメントの後で、実際に関数から戻る前に実行されます。スレッドの安全性とはほとんど関係がないと思います。これはハックではありません。tryブロックやcatchブロックで何をしても、finallyは常に実行されることが保証されています。


13

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」はその場所にあり、実際にコンパイルする構造を書き込むよりも見栄えのよいコードを可能します。しかし、それは落とし穴を避けるために注意深く使われなければなりません。


4

場合はxローカル変数であるとして、私は、ポイントが表示されないx効果的方法が終了するとにかくnullに設定し、それはセットを呼び出す前にレジスタに格納されて以来、戻り値の値は(nullではないであろうxnullへ)。

戻り時(および戻り値が決定された後)にフィールドの値の変更を保証したい場合にのみ、これが行われているのを見ることができます。


ローカル変数もデリゲートによってキャプチャされない限り:)
Jon Skeet

次に、クロージャがあり、参照がまだあるため、オブジェクトをガベージコレクションすることはできません。
再帰的

しかし、その値を使用するつもりでない限り、デリゲートでローカル変数を使用する理由はまだわかりません。
再帰的
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.