コード内のある場所で、ロックの内側と外側のどちらかにreturnステートメントがあることに気付きました。どれが最高ですか?
1)
void example()
{
lock (mutex)
{
//...
}
return myData;
}
2)
void example()
{
lock (mutex)
{
//...
return myData;
}
}
どちらを使用すればよいですか?
基本的に、どちらでもコードが簡単になります。シングルポイントの出口は理想的ですが、それを達成するためだけにコードを変形させません...そして、ローカル変数(ロック外)を宣言し、それを初期化(ロック内)し、 (ロックの外で)それを返すと、ロック内の単純な "fooを返す"の方がずっと簡単だと思います。
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
}
vs.
void example() {
lock (foo) {
return ...;
}
}
私は、特に短いスニペットの場合、ケース2の方が読みやすく、ねじ込みにくいと感じています。
価値のあるものとして、 MSDNのドキュメント には、ロックの内側から戻る例があります。ここでの他の回答から、それはILにかなり似ているように見えますが、私にとっては、ロックの内側から戻る方が安全だと思われます。
仲間の開発者がコードを読みやすくするために、最初の選択肢を提案します。
外はきれいに見えます。
lock() return <expression>
ステートメントは常に:
1)ロックを入力します
2)指定された型の値のローカル(スレッドセーフ)ストアを作成します。
3)ストアに_<expression>
_によって返された値を入力します。
4)出口ロック
5)店舗を返却します。
これは、lockステートメントから返された値が、返される前に常に「調理済み」であることを意味します。
lock() return
について心配する必要はありません。ここでは誰も聞いてはいけません))