returnステートメントはロックの内側または外側にあるべきですか?
-
06-07-2019 - |
質問
コード内のある場所では、ロックの内側と外側のいずれかにreturnステートメントがあることに気付きました。どれがベストですか?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
どちらを使用すべきですか?
解決
本質的に、どちらでもコードが簡単になります。単一出口は理想的ですが、それを達成するためだけにコードを曲げたりすることはありません...そして、ローカル変数(ロック外)を宣言し、それを初期化(ロック内)し、 (ロックの外で)それを返すと、単純な<!> quot; return foo <!> quot;ロックの内部はずっとシンプルです。
ILの違いを示すには、次のコードを使用します。
static class Program
{
static void Main() { }
static readonly object sync = new object();
static int GetValue() { return 5; }
static int ReturnInside()
{
lock (sync)
{
return GetValue();
}
}
static int ReturnOutside()
{
int val;
lock (sync)
{
val = GetValue();
}
return val;
}
}
(ReturnInside
はC#のシンプルでクリーンなビットであると喜んで主張します)
ILを確認します(リリースモードなど):
.method private hidebysig static int32 ReturnInside() cil managed
{
.maxstack 2
.locals init (
[0] int32 CS$1$0000,
[1] object CS$2$0001)
L_0000: ldsfld object Program::sync
L_0005: dup
L_0006: stloc.1
L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object)
L_000c: call int32 Program::GetValue()
L_0011: stloc.0
L_0012: leave.s L_001b
L_0014: ldloc.1
L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object)
L_001a: endfinally
L_001b: ldloc.0
L_001c: ret
.try L_000c to L_0014 finally handler L_0014 to L_001b
}
method private hidebysig static int32 ReturnOutside() cil managed
{
.maxstack 2
.locals init (
[0] int32 val,
[1] object CS$2$0000)
L_0000: ldsfld object Program::sync
L_0005: dup
L_0006: stloc.1
L_0007: call void [mscorlib]System.Threading.Monitor::Enter(object)
L_000c: call int32 Program::GetValue()
L_0011: stloc.0
L_0012: leave.s L_001b
L_0014: ldloc.1
L_0015: call void [mscorlib]System.Threading.Monitor::Exit(object)
L_001a: endfinally
L_001b: ldloc.0
L_001c: ret
.try L_000c to L_0014 finally handler L_0014 to L_001b
}
したがって、ILレベルでは、それらは[与えたり、名前を付けたり]同一です(何かを学びました;-p)。 そのため、唯一の実用的な比較は、ローカルコーディングスタイルの(非常に主観的な)法則です...簡単にするために<=>を好みますが、どちらにも興奮しません。
他のヒント
違いはありません。両方ともコンパイラーによって同じものに変換されます。
明確にするために、どちらも次のセマンティクスで効果的に変換されます:
T myData;
Monitor.Enter(mutex)
try
{
myData= // something
}
finally
{
Monitor.Exit(mutex);
}
return myData;
私は間違いなく戻り値をロックの中に入れました。そうしないと、別のスレッドがロックに入り、returnステートメントの前に変数を変更する危険があります。そのため、元の呼び出し元は予想とは異なる値を受け取ります。
外側のロックは見栄えが良いと思うが、コードを次のように変更する場合は注意してください:
return f(...)
ロックを保持したままf()を呼び出す必要がある場合、一貫性を保つためにロック内にリターンを保持するのが理にかなっているため、明らかにロック内にある必要があります。
依存します、
ここで穀物に反するつもりです。通常、ロックの内側に戻ります。
通常、変数mydataはローカル変数です。ローカル変数を初期化しながら宣言するのが好きです。ロック外で戻り値を初期化するデータがほとんどありません。
したがって、実際には比較に欠陥があります。理想的には、2つのオプションの違いは、あなたが書いたとおりであり、実際には少しugいことになります。
void example() {
int myData;
lock (foo) {
myData = ...;
}
return myData
}
vs。
void example() {
lock (foo) {
return ...;
}
}
特に短いスニペットの場合、ケース2の方が読みやすく、ねじ込みが難しいことがわかりました。
仲間の開発者がコードを読みやすくするために、最初の代替案を提案します。
価値があるものについては、ドキュメントMSDN には、ロックの内部から戻る例があります。ここの他の回答から、それはILにかなり似ているように見えますが、私にとっては、別のスレッドによって上書きされる戻り変数のリスクを実行しないため、ロックの内側から戻る方が安全なようです。 / p>
外はきれいに見えます。
lock() return <expression>
ステートメントは常に:
1)ロックを入力
2)指定された型の値のローカル(スレッドセーフ)ストアを作成します
3)<expression>
、
4)ロックの終了
5)ストアを返します。
これは、lockステートメントから返される値が常に<!> quot; cooked <!> quot;であることを意味します。戻る前。
lock() return
について心配する必要はありません。ここでは誰も聞かないでください))