397

switchSwitchステートメントのフォールスルーは、構成概念を愛する私の個人的な主な理由の1つですif/else if。例はここにあります:

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

sは関数の外で宣言する必要があるため、賢い人々はうんざりしています。そうですねstring[]、これは単なる例です。

コンパイラは次のエラーで失敗します。

コントロールは、あるケースラベル(「ケース3:」)から別のケースラベルにフォールスルーすることはできません
コントロールは、あるケースラベル(「ケース2:」)から別のケースラベルにフォールスルーすることはできません

なんで?そして、3つを持たずにこの種の動作を取得する方法はありますifか?

4

13 に答える 13

705

(他の場所で提供した回答のコピー/貼り付け)

フォールスルー-sは、 switch(を参照)にcaseコードを含まないか、 (を参照)または(を参照)特殊な形式を使用することで実現できます。casecase 0goto casecase 1goto defaultcase 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;
}
于 2008-10-06T13:13:18.647 に答える
47

「なぜ」は偶発的な転倒を避けるためであり、私は感謝しています。これは、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に近すぎました-スコープなどに関して行うことができるいくつかの有用な変更があります。おそらくパターンマッチングなどを行うことができるよりスマートなスイッチが役立つでしょうが、それは実際にはスイッチから「条件のシーケンスをチェックする」に変わります。 -その時点で、おそらく別の名前が必要になります。

于 2008-10-06T13:13:42.797 に答える
28

ここでの回答に追加するには、これと組み合わせて反対の質問を検討する価値があると思います。そもそもなぜCはフォールスルーを許可したのですか?

もちろん、どのプログラミング言語も次の 2 つの目標に役立ちます。

  1. コンピューターに指示を出します。
  2. プログラマーの意図の記録を残します。

したがって、プログラミング言語の作成は、これら 2 つの目標をどのように達成するのが最善かのバランスを取ることになります。一方では、コンピューター命令 (機械語、IL のようなバイトコード、命令が実行時に解釈されるもの) への変換が容易になればなるほど、コンパイルまたは解釈のプロセスが効率的で信頼性が高くなり、より効率的になります。出力をコンパクトに。極端に言えば、この目標は、アセンブリ、IL、または生のオペコードでさえも書くことになります。なぜなら、最も簡単なコンパイルは、コンパイルがまったくない場所だからです。

逆に言えば、言語がプログラマーの意図を表現する手段ではなく、その意図を表現する言語であるほど、プログラムを作成するときも保守するときも、プログラムはより理解しやすくなります。

今では、それをブロックswitchの同等のチェーンまたは類似のものに変換することによって常にコンパイルできましたが、値を取得し、それからオフセットを計算する特定の一般的なアセンブリパターンにコンパイルできるように設計されました (テーブルを検索するかどうかにかかわらず)if-else値の完全なハッシュ、または値の実際の算術演算によってインデックス付けされます*)。この時点で注目に値するのは、現在、C# のコンパイルがswitch同等のに変わることもあればif-else、ハッシュ ベースのジャンプ アプローチを使用することもあります (C、C++、および同等の構文を持つ他の言語でも同様です)。

この場合、フォールスルーを許可する正当な理由が 2 つあります。

  1. いずれにせよ、それは自然に発生します。ジャンプ テーブルを一連の命令に組み込み、以前の命令のバッチの 1 つに何らかのジャンプまたはリターンが含まれていない場合、実行は自然に次のバッチに進みます。switchマシン コードを使用して、C を使用してジャンプ テーブルに変換した場合、フォールスルーを許可することは「ただ起こる」ことでした。

  2. アセンブリで記述したコーダーは、すでに同等の作業に慣れていました。アセンブリでジャンプ テーブルを手動で記述する場合、特定のコード ブロックが戻り値で終了するか、テーブル外へのジャンプで終了するか、そのまま続行するかを考慮する必要があります。次のブロックへ。そのため、必要に応じてコーダーに明示的に追加させることはbreak、コーダーにとっても「自然」でした。

したがって、当時は、生成されたマシン コードとソース コードの表現力の両方に関連するコンピューター言語の 2 つの目標のバランスを取ることが妥当な試みでした。

40 年経った今でも、状況はまったく同じではありません。それにはいくつかの理由があります。

  1. 今日の C 言語のコーダーは、アセンブリの経験がほとんど、またはまったくない場合があります。他の多くの C スタイル言語のコーダーは、(特に Javascript!) する可能性はさらに低くなります。「アセンブリから慣れ親しんだもの」という概念は、もはや関係ありません。
  2. 最適化の改善は、アプローチが最も効率的である可能性が高いと見なさswitchif-elseたために変更されるか、そうでなければジャンプ テーブル アプローチの特に難解なバリアントに変更される可能性が高いことを意味します。高レベルのアプローチと低レベルのアプローチの間のマッピングは、以前ほど強力ではありません。
  3. 経験上、フォールスルーは標準ではなく少数のケースである傾向があることが示されています (Sun のコンパイラの調査では、ブロックの 3% がswitch同じブロックで複数のラベル以外のフォールスルーを使用していることがわかりました。ここでのケースは、この 3% が実際には通常よりもはるかに高かったことを意味します)。したがって、研究された言語は、一般的なものよりも珍しいものをより容易に提供します。
  4. 経験上、フォールスルーが誤って実行された場合と、コードを保守している誰かが正しいフォールスルーを見落とした場合の両方で、フォールスルーが問題の原因になる傾向があることが示されています。この後者は、フォールスルーに関連するバグへの微妙な追加です。コードに完全にバグがない場合でも、フォールスルーが問題を引き起こす可能性があるためです。

これらの最後の 2 つのポイントに関連して、K&R の最新版からの次の引用を検討してください。

あるケースから別のケースへのフォールスルーは堅牢ではなく、プログラムが変更されたときに崩壊する傾向があります。1 つの計算に複数のラベルを付ける場合を除き、フォールスルーは慎重に使用し、コメントを付ける必要があります。

適切な形式の問題として、論理的には不要ですが、最後のケース (ここではデフォルト) の後に区切りを入れます。ある日、最後に別のケースが追加されたときに、この防御的なプログラミングのビットがあなたを救います。

したがって、馬の口から、C でのフォールスルーは問題です。フォールスルーを常にコメントで文書化することは良い習慣と考えられています。これは、何か異常なことをした場所を文書化する必要があるという一般原則の適用です。実際には正しいのに、初心者のバグがあります。

考えてみると、次のようにコーディングします。

switch(x)
{
  case 1:
   foo();
   /* FALLTHRU */
  case 2:
    bar();
    break;
}

コードでフォールスルーを明示的にするために何かを追加していますが、それはコンパイラによって検出できる (または存在しないことを検出できる) ものではありません

そのため、C# のフォールスルーで明示的でなければならないという事実は、他の C スタイルの言語で適切に記述した人々にペナルティを追加するものではありません。 /p>

最後に、gotohere の使用は、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();
}

この種の場合、前のブロックに 1 をもたらす値以外の値に対して実行されるコードにブロックを含めたい場合は、既に を使用する必要がありますgoto。(もちろん、さまざまな条件でこれを回避する手段と方法がありますが、それはこの質問に関連するほぼすべてに当てはまります)。そのため、C# は、 で複数のコード ブロックをヒットする必要がある 1 つの状況に対処するために、既に通常の方法を構築し、switchフォールスルーもカバーするように一般化しました。caseまた、C では新しいラベルを追加する必要がありますが、C# ではラベルとして使用できるため、両方のケースがより便利で自己文書化されました。C# では、ラベルを削除して、何をしているのか明確なbelow_sixラベルを使用できます。goto case 5(追加する必要もありますbreakについてはdefault、上記の C コードを C# コードではないことを明確にするために省略しました)。

したがって、要約すると:

  1. C# は、40 年前の C コードのように最適化されていないコンパイラの出力に直接関係しなくなりました (最近の C もそうではありません)。
  2. C# は、単に Implicit を持っているだけでなく、C との互換性を維持しておりbreak、同様の言語に精通しているユーザーによる言語の学習が容易になり、移植が容易になります。
  3. C# は、過去 40 年間、問題の原因として十分に文書化されてきたバグや誤解されたコードの潜在的な原因を取り除きます。
  4. C# では、C を使用した既存のベスト プラクティス (ドキュメント フォール スルー) がコンパイラによって適用可能になります。
  5. C# は、より明示的なコードを使用するケースを異常なケースにし、自動的に記述したコードを使用する通常のケースを作成します。
  6. C# は、C で使用されているのと同じgotoベースのアプローチを使用して、異なるcaseラベルから同じブロックをヒットします。他のケースに一般化するだけです。
  7. C# では、ステートメントがラベルとして機能gotoできるようにすることで、C よりもベースのアプローチがより便利で明確になります。case

全体として、かなり合理的な設計上の決定


*BASIC のいくつかの形式は、GOTO (x AND 7) * 50 + 240脆弱であり、したがって禁止の特に説得力のあるケースであるgoto一方で、以下のようなことを行うことを可能にします。これは、手動で維持する必要があるものではなく、コンパイルの結果である場合にはるかに合理的です。特に、ダフのデバイスの実装は、同等のマシン コードまたは IL に適しています。これは、多くの場合、命令の各ブロックがnopフィラーの追加を必要とせずに同じ長さになるためです。

†ダフの装置は、妥当な例外として、ここでも登場します。それと同様のパターンで操作の繰り返しがあるという事実は、その旨の明示的なコメントがなくても、フォールスルーの使用を比較的明確にするのに役立ちます。

于 2013-12-09T12:15:29.340 に答える
27

スイッチのフォールスルーは、歴史的に、最新のソフトウェアのバグの主な原因の1つです。言語設計者は、処理せずに直接次のケースにデフォルト設定する場合を除いて、ケースの最後にジャンプすることを必須にすることを決定しました。

switch(value)
{
    case 1:// this is still legal
    case 2:
}
于 2008-10-06T13:06:10.533 に答える
17

'goto case label' http://www.blackwasp.co.uk/CSharpGoto.aspx

gotoステートメントは、プログラムの制御を無条件に別のステートメントに移す単純なコマンドです。このコマンドは、スパゲッティコードにつながる可能性があるため、すべての高級プログラミング言語からの削除を提唱する一部の開発者から批判されることがよくあります。これは、gotoステートメントまたは同様のjumpステートメントが非常に多く、コードの読み取りと保守が困難になる場合に発生します。ただし、gotoステートメントを注意深く使用すると、いくつかの問題に対するエレガントなソリューションが提供されると指摘するプログラマーがいます...

于 2008-10-06T13:08:37.143 に答える
7

彼らは、意志によって使用されなかったが問題を引き起こした場合を避けるために、この動作を意図的に省略しました。

次のように、case部分にステートメントがない場合にのみ使用できます。

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}
于 2008-10-06T13:04:34.343 に答える
4

彼らは、c#のswitchステートメント(C / Java / C ++から)の動作を変更しました。その理由は、人々がフォールスルーを忘れてエラーが発生したためだと思います。私が読んだある本は、gotoを使ってシミュレートすると言っていましたが、これは私にとって良い解決策のようには思えません。

于 2008-10-06T13:03:03.270 に答える
0

ケースステートメントであるかデフォルトステートメントであるかにかかわらず、最後のブロックを含め、各ケースブロックの後にブレークなどのジャンプステートメントが必要です。1つの例外を除いて(C ++ switchステートメントとは異なり)、C#は1つのケースラベルから別のケースラベルへの暗黙的なフォールスルーをサポートしていません。唯一の例外は、caseステートメントにコードがない場合です。

--C #switch()ドキュメント

于 2008-10-06T13:03:34.110 に答える
0

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;
}
于 2010-07-28T20:30:50.597 に答える
0

デフォルトのケースであっても、各 case ステートメントの後にbreakまたはgotoステートメントが必要です。

于 2010-06-04T09:02:02.403 に答える
0

Xamarin のコンパイラが実際にこれを間違えたため、フォールスルーが可能になったことを簡単に付け加えておきます。修正されたと思われますが、リリースされていません。実際に失敗していたいくつかのコードでこれを発見し、コンパイラは文句を言いませんでした。

于 2014-02-14T22:19:05.647 に答える
0

スイッチ(C#リファレンス)は言う

C# では、最後のセクションを含め、スイッチ セクションの最後が必要です。

break;したがって、セクションにa も追加する必要がありdefaultます。そうしないと、コンパイル エラーが発生します。

于 2014-10-07T09:33:53.613 に答える
-12

「休憩」を追加するのを忘れました。ステートメントをケース3に入れます。ケース2では、それをifブロックに書き込みました。したがって、これを試してください:

case 3:            
{
    ans += string.Format("{0} hundred and ", numbers[number / 100]);
    break;
}


case 2:            
{
    int t = (number / 10) % 10;            
    if (t == 1)            
    {                
        ans += teens[number % 10];                
    }            
    else if (t > 1)                
    {
        ans += string.Format("{0}-", tens[t]);        
    }
    break;
}

case 1:            
{
    int o = number % 10;            
    ans += numbers[o];            
    break;        
}

default:            
{
    throw new ArgumentException("number");
}
于 2008-10-06T13:10:37.340 に答える