コードのどこかにreturnステートメントがあり、ロックの内側と外側にあることに気づきました。どれが最高ですか?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
どちらを使うべきですか?
コードのどこかにreturnステートメントがあり、ロックの内側と外側にあることに気づきました。どれが最高ですか?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
どちらを使うべきですか?
基本的に、コードを単純化する方。単一の出口点は素晴らしい理想ですが、それを達成するためだけにコードを形から曲げることはしません...そして、代替手段がローカル変数を宣言する場合(ロックの外側)、それを初期化する(ロックの内側)と次に、それを(ロックの外で)返すと、ロック内の単純な「returnfoo」の方がはるかに簡単だと思います。
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)。そのため、唯一の賢明な比較は、ローカルコーディングスタイルの(非常に主観的な)法則です...私ReturnInside
は単純さを好みますが、どちらにも興奮しません。
違いはありません。それらは両方ともコンパイラによって同じものに変換されます。
明確にするために、どちらも次のセマンティクスを持つものに効果的に変換されます。
T myData;
Monitor.Enter(mutex)
try
{
myData= // something
}
finally
{
Monitor.Exit(mutex);
}
return myData;
私は間違いなくリターンをロックの中に入れます。そうしないと、別のスレッドがロックに入り、return ステートメントの前に変数を変更する危険性があるため、元の呼び出し元が予想とは異なる値を受け取ることになります。
外のロックの方が見栄えが良いと思う場合でも、コードを次のように変更することになった場合は注意してください。
return f(...)
ロックを保持したままf()を呼び出す必要がある場合は、一貫性を保つためにリターンをロック内に保持することが理にかなっているため、明らかにロック内にある必要があります。
場合によります、
私はここで穀物に反対するつもりです。私は通常、錠の中に戻ります。
通常、変数mydataはローカル変数です。ローカル変数を初期化するときに宣言するのが好きです。ロックの外で戻り値を初期化するためのデータを持っていることはめったにありません。
したがって、あなたの比較は実際には欠陥があります。理想的には、2つのオプションの違いはあなたが書いたとおりですが、これはケース1にうなずくように見えますが、実際には少し醜いです。
void example() {
int myData;
lock (foo) {
myData = ...;
}
return myData
}
対。
void example() {
lock (foo) {
return ...;
}
}
ケース2は、特に短いスニペットの場合、かなり読みやすく、失敗しにくいと思います。
価値があるのは、MSDN のドキュメントに、ロックの内側から戻る例があります。ここでの他の回答から、それは非常によく似たILのように見えますが、私にとっては、戻り変数が別のスレッドによって上書きされるリスクを冒さないため、ロック内から戻る方が安全に思えます。
仲間の開発者がコードを読みやすくするために、最初の代替案を提案します。