Switchステートメントのフォールスルーは、switch
とif/else if
の組み合わせが大好きな私の個人的な主な理由の1つです。例はここに順番にあります:
static string NumberToWords(int number)
{
string[] numbers = new string[]
{ "", "one", "two", "three", "four", "five",
"six", "seven", "eight", "nine" };
string[] tens = new string[]
{ "", "", "twenty", "thirty", "forty", "fifty",
"sixty", "seventy", "eighty", "ninety" };
string[] teens = new string[]
{ "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen",
"sixteen", "seventeen", "eighteen", "nineteen" };
string ans = "";
switch (number.ToString().Length)
{
case 3:
ans += string.Format("{0} hundred and ", numbers[number / 100]);
case 2:
int t = (number / 10) % 10;
if (t == 1)
{
ans += teens[number % 10];
break;
}
else if (t > 1)
ans += string.Format("{0}-", tens[t]);
case 1:
int o = number % 10;
ans += numbers[o];
break;
default:
throw new ArgumentException("number");
}
return ans;
}
string[]
sは関数の外側で宣言されるべきだから賢い人々は興味を持っています。
コンパイラは次のエラーで失敗します。
統制があるケースラベル( 'ケース3:')から別のケースラベル( 'ケース2:')から別のケースラベル( 'case 2:')に移ることはできません
どうして?そして、3つのif
を持たずにこのような振る舞いをする方法はありますか?
(コピー/貼り付け 私が他の場所で提供した答え )
switch
-case
sを迂回するには、case
(case 0
を参照)または特別なgoto case
(case 1
を参照)またはgoto default
(case 2
を参照)の形式でコードを記述しないことで実現できます。
switch (/*...*/) {
case 0: // shares the exact same code as case 1
case 1:
// do something
goto case 2;
case 2:
// do something else
goto default;
default:
// do something entirely different
break;
}
「なぜ」とは、偶然のフォールスルーを防ぐためです。これは、CおよびJavaにおける珍しいことではないバグの原因です。
回避策はgotoを使うことです。
switch (number.ToString().Length)
{
case 3:
ans += string.Format("{0} hundred and ", numbers[number / 100]);
goto case 2;
case 2:
// Etc
}
スイッチ/ケースの一般的なデザインは私の見解では少し残念です。それはCにはあまりにも密接に行き詰まった - スコープ設定などに関してなされることができるいくつかの有用な変更があります。おそらくパターンマッチングなどをすることができるよりスマートなスイッチが役に立つでしょう - その時点でおそらく別の名前が求められるでしょう。
スイッチのフォールスルーは、歴史的には現代のソフトウェアにおける主要なバグの原因の1つです。あなたが処理せずに直接次の事件をデフォルトとしない限り、言語デザイナーは事件の終わりにジャンプすることを必須にすることにしました。
switch(value)
{
case 1:// this is still legal
case 2:
}
ここに答えを追加するために、私はこれと関連して反対の質問を検討する価値があると思います、すなわち。なぜCはそもそもフォールスルーを許可したのでしょうか。
どのプログラミング言語でももちろん2つの目標があります。
したがって、任意のプログラミング言語を作成することは、これら2つの目標を最大限に達成する方法のバランスです。一方では、コンピューターの命令(機械コード、ILのようなバイトコード、または実行時に解釈される命令など)に変換するのが簡単になればなるほど、コンパイルや解釈のプロセスは効率的、信頼性が高まります。出力がコンパクトです。極端に考えれば、この目標は、アセンブリ、イリノイ、さらには生のオペコードでさえ書くことになります。なぜなら、最も簡単なコンパイルは、コンパイルがまったくないところだからです。
逆に言えば、言語がその目的を達成するための手段ではなく、プログラマーの意図を表現するほど、プログラムの作成時と保守時の両方で理解しやすくなります。
switch
name__は、それをif-else
ブロックの同等のチェーンまたは類似のものに変換することによって常にコンパイルすることができましたが、ある特定の共通のAssemblyパターンにコンパイルできるように設計されました。値の完全なハッシュ、または値の実際の算術演算によって索引付けされた表*)。この時点で注目に値するのは、今日のC#コンパイルではswitch
name__が同等のif-else
に変換され、ハッシュベースのジャンプアプローチが使用されること(そして同様にC、C++、および同等の構文を持つ他の言語)です。
この場合、フォールスルーを許可する理由は2つあります。
それはとにかく自然に起こります。ジャンプテーブルを一連の命令に構築し、以前の一連の命令のいずれかに何らかのジャンプやリターンが含まれていない場合、実行は当然次のバッチに進みます。フォールスルーを許可することは、Cを使用してswitch
nameを__-jump-table-usingマシンコードに変換した場合に「発生する可能性がある」ことです。
Assemblyで手で書いたジャンプテーブルを書くときには、与えられたコードブロックがreturnで終わるのか、テーブルの外側でジャンプするのか、それとも続行するのかを考えなければならないでしょう。次のブロックへそのため、必要に応じてコーダーに明示的なbreak
name__を追加させることは、コーダーにとっても「自然なこと」でした。
したがって、当時は、生成されたマシンコードとソースコードの表現力の両方に関連するため、コンピュータ言語の2つの目標のバランスをとることは合理的な試みでした。
40年経っても、いくつかの理由で、状況はまったく同じではありません。
switch
name__がif-else
に変換される可能性が最も高いと考えられるため、またはジャンプテーブルアプローチの特に難解な変種に変換される可能性が高いことを意味します。上位レベルのアプローチと下位レベルのアプローチの間のマッピングは、以前ほど強力ではありません。switch
name__ブロックが同じブロック上の複数のラベル以外のフォールスルーを使用していたことがわかりました)。ここで大文字小文字の区別は、この3%が実際には通常よりもはるかに高いことを意味します。そのため、研究されている言語は、普通のものよりも珍しいものをより簡単に提供します。これらの最後の2点に関連して、K&Rの最新版からの以下の引用を考慮してください。
ある症例から別の症例への転倒は頑強ではなく、プログラムが変更されたときに崩壊する傾向があります。単一の計算に対する複数のラベルを除いて、フォールスルーは控えめに使用され、コメントされるべきです。
論理的には不要ですが、良い形式の問題として、最後のケース(ここでのデフォルト)の後に休憩を入れてください。いつか最後に別のケースが追加されるとき、このちょっとした防御的プログラミングはあなたを救うでしょう。
そのため、馬の口から見て、Cのフォールスルーは問題があります。コメントでフォールスルーを常に文書化することは、グッドプラクティスと考えられています。これは、何か異常なことを行った場所を文書化するという一般原則の適用です。それが実際に正しいときそれに初心者のバグがあります。
そして、あなたがそれについて考えるとき、このようなコードを書く:
switch(x)
{
case 1:
foo();
/* FALLTHRU */
case 2:
bar();
break;
}
Isコードでフォールスルーを明示的にするために何かを追加しても、それはコンパイラによって検出できる(またはその欠如を検出できる)ものではありません。
そのため、C#でフォールスルーを使用してonを明示的に指定する必要があるという事実は、他のCスタイル言語でうまく記述されているユーザーにはすでに影響を与えません。
最後に、ここでのgoto
name__の使用は、すでにCや他のそのような言語からの標準です。
switch(x)
{
case 0:
case 1:
case 2:
foo();
goto below_six;
case 3:
bar();
goto below_six;
case 4:
baz();
/* FALLTHRU */
case 5:
below_six:
qux();
break;
default:
quux();
}
このような場合に、ブロックを前のブロックに移動する値以外の値に対して実行されるコードに含めたい場合は、goto
name__を使用する必要があります。 (もちろん、異なる条件付きでこれを避けるための手段と方法がありますが、それはこの問題に関連するほぼすべてのことに当てはまります)。そのため、C#はswitch
name__内で複数のコードブロックをヒットさせたいという1つの状況に対処するためのすでに通常の方法を基に構築され、フォールスルーもカバーするように一般化しました。 Cで新しいラベルを追加する必要がありますが、C#ではラベルとしてcase
name__を使用できるため、どちらの場合もより便利で自己文書化されています。 C#では、below_six
ラベルを削除して、goto case 5
を使用できます。これにより、作業内容が明確になります。 (break
name__にもdefault
name__を追加する必要があります。これは、上記のCコードをC#コードではなく明確にするためだけに省略しました)。
要約すると:
break
name__だけでなく、Cと互換性があります。これは、似たような言語に精通している人が言語を簡単に習得でき、移植が容易だからです。goto
name__ベースのアプローチを使用して、異なるcase
name__ラベルから同じブロックをヒットします。goto
name__ステートメントをラベルとして機能させることができるため、case
name__ベースのアプローチがCよりも便利で明確になります。全体として、かなり合理的な設計上の決定
*ある形式のBASICでは、脆弱で、特にgoto
name__を禁止するための説得力のあるケースであるが、低レベルのコードがジャンプすることができるような、より高い言語の同等物を示すのに役立つGOTO (x AND 7) * 50 + 240
のようなことができます。値を基にした算術に基づいています。これは、手作業で保守する必要があるものではなく、コンパイルの結果である場合にはるかに合理的です。特にDuff's Deviceの実装は、nop
name__フィラーの追加を必要とせずに各命令ブロックが同じ長さになることが多いため、同等のマシンコードまたはILに適しています。
†Duff's Deviceが妥当な例外として再度登場します。それと同様のパターンで操作の繰り返しがあるという事実は、その効果への明確なコメントがなくてもフォールスルーの使用を比較的明確にするのに役立ちます。
あなたは '後藤ラベル'をすることができます http://www.blackwasp.co.uk/CSharpGoto.aspx
Gotoステートメントは、無条件にプログラムの制御を別のステートメントに転送する単純なコマンドです。このコマンドは スパゲッティコード につながる可能性があるため、すべての高級プログラミング言語からの削除を推奨する一部の開発者にはしばしば批判されます。 。これは、gotoステートメントや類似のジャンプステートメントが非常に多く、コードの読み取りや保守が困難になる場合に発生します。しかし、慎重に使用すると、gotoステートメントがいくつかの問題に対して優雅な解決策を提供することを指摘するプログラマーもいます...
彼らは意志によって使用されていないが問題を引き起こしていたときを避けるために設計によってこの動作を省いた。
Case部分に次のような文がない場合にのみ使用できます。
switch (whatever)
{
case 1:
case 2:
case 3: boo; break;
}
彼らはc#のswitch文(C/Java/C++から)の振る舞いを変更しました。私はその推論が人々が転倒について忘れていたためにエラーが発生したと思います。私が読んだある本はgotoを使ってシミュレーションすると言っていましたが、これは私にとって良い解決策のようには思えません。
各case文の後に、デフォルトのcaseであってもbreakまたはgoto文が必要です。
Gotoキーワードを使用すると、c ++のようにフォールスルーすることができます。
例:
switch(num)
{
case 1:
goto case 3;
case 2:
goto case 3;
case 3:
//do something
break;
case 4:
//do something else
break;
case default:
break;
}
Xamarin用のコンパイラが実際にこれを誤解しており、フォールスルーを可能にすることを付け加えるためのちょっとしたメモです。おそらく修正されていますが、まだリリースされていません。実際には失敗し、コンパイラが文句を言わなかったいくつかのコードでこれを発見しました。
C#は最後のものも含めてスイッチセクションの終わりを必要とします、
そのため、default
セクションにbreak;
を追加する必要があります。そうしないと、コンパイラエラーが発生します。
Caseステートメントでもdefaultステートメントでも、最後のブロックを含め、各caseブロックの後にはbreakなどのジャンプステートメントが必要です。 1つの例外はありますが(C++のswitchステートメントとは異なり)、C#では、あるケースラベルから別のケースラベルへの暗黙のフォールスルーはサポートされていません。 1つの例外は、caseステートメントにコードがない場合です。