web-dev-qa-db-ja.com

C#でのswitchステートメントのフォールスルー?

Switchステートメントのフォールスルーは、switchif/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を持たずにこのような振る舞いをする方法はありますか?

344

(コピー/貼り付け 私が他の場所で提供した答え

switch-casesを迂回するには、casecase 0を参照)または特別なgoto casecase 1を参照)またはgoto 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;
}
609
Alex Lyman

「なぜ」とは、偶然のフォールスルーを防ぐためです。これは、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にはあまりにも密接に行き詰まった - スコープ設定などに関してなされることができるいくつかの有用な変更があります。おそらくパターンマッチングなどをすることができるよりスマートなスイッチが役に立つでしょう - その時点でおそらく別の名前が求められるでしょう。

40
Jon Skeet

スイッチのフォールスルーは、歴史的には現代のソフトウェアにおける主要なバグの原因の1つです。あなたが処理せずに直接次の事件をデフォルトとしない限り、言語デザイナーは事件の終わりにジャンプすることを必須にすることにしました。

switch(value)
{
    case 1:// this is still legal
    case 2:
}
26
Coincoin

ここに答えを追加するために、私はこれと関連して反対の質問を検討する価値があると思います、すなわち。なぜCはそもそもフォールスルーを許可したのでしょうか。

どのプログラミング言語でももちろん2つの目標があります。

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

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

逆に言えば、言語がその目的を達成するための手段ではなく、プログラマーの意図を表現するほど、プログラムの作成時と保守時の両方で理解しやすくなります。

switchname__は、それをif-elseブロックの同等のチェーンまたは類似のものに変換することによって常にコンパイルすることができましたが、ある特定の共通のAssemblyパターンにコンパイルできるように設計されました。値の完全なハッシュ、または値の実際の算術演算によって索引付けされた表*)。この時点で注目に値するのは、今日のC#コンパイルではswitchname__が同等のif-elseに変換され、ハッシュベースのジャンプアプローチが使用されること(そして同様にC、C++、および同等の構文を持つ他の言語)です。

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

  1. それはとにかく自然に起こります。ジャンプテーブルを一連の命令に構築し、以前の一連の命令のいずれかに何らかのジャンプやリターンが含まれていない場合、実行は当然次のバッチに進みます。フォールスルーを許可することは、Cを使用してswitchnameを__-jump-table-usingマシンコードに変換した場合に「発生する可能性がある」ことです。

  2. Assemblyで手で書いたジャンプテーブルを書くときには、与えられたコードブロックがreturnで終わるのか、テーブルの外側でジャンプするのか、それとも続行するのかを考えなければならないでしょう。次のブロックへそのため、必要に応じてコーダーに明示的なbreakname__を追加させることは、コーダーにとっても「自然なこと」でした。

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

40年経っても、いくつかの理由で、状況はまったく同じではありません。

  1. 今日のCのコーダーは、アセンブリの経験がほとんどないかまったくないかもしれません。他の多くのCスタイル言語のコーダーは、それほど起こりそうにありません(特にJavascript!)。 「人々が議会から慣れ親しんでいるもの」という概念は、もはや関係ありません。
  2. 最適化の改善は、switchname__がif-elseに変換される可能性が最も高いと考えられるため、またはジャンプテーブルアプローチの特に難解な変種に変換される可能性が高いことを意味します。上位レベルのアプローチと下位レベルのアプローチの間のマッピングは、以前ほど強力ではありません。
  3. 経験によれば、フォールスルーは標準ではなく少数派のケースになる傾向があります(Sunのコンパイラの調査では、3つのswitchname__ブロックが同じブロック上の複数のラベル以外のフォールスルーを使用していたことがわかりました)。ここで大文字小文字の区別は、この3%が実際には通常よりもはるかに高いことを意味します。そのため、研究されている言語は、普通のものよりも珍しいものをより簡単に提供します。
  4. 誤って行われた場合も、コードを管理している誰かが正しいフォールスルーを見逃した場合も、フォールスルーが問題の原因になる傾向があることが経験的にわかっています。これはフォールスルーに関連するバグへの微妙な追加です。なぜなら、あなたのコードが完全にバグフリーであったとしても、あなたのフォールスルーはまだ問題を引き起こす可能性があるからです。

これらの最後の2点に関連して、K&Rの最新版からの以下の引用を考慮してください。

ある症例から別の症例への転倒は頑強ではなく、プログラムが変更されたときに崩壊する傾向があります。単一の計算に対する複数のラベルを除いて、フォールスルーは控えめに使用され、コメントされるべきです。

論理的には不要ですが、良い形式の問題として、最後のケース(ここでのデフォルト)の後に休憩を入れてください。いつか最後に別のケースが追加されるとき、このちょっとした防御的プログラミングはあなたを救うでしょう。

そのため、馬の口から見て、Cのフォールスルーは問題があります。コメントでフォールスルーを常に文書化することは、グッドプラクティスと考えられています。これは、何か異常なことを行った場所を文書化するという一般原則の適用です。それが実際に正しいときそれに初心者のバグがあります。

そして、あなたがそれについて考えるとき、このようなコードを書く:

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

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

そのため、C#でフォールスルーを使用してonを明示的に指定する必要があるという事実は、他のCスタイル言語でうまく記述されているユーザーにはすでに影響を与えません。

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

このような場合に、ブロックを前のブロックに移動する値以外の値に対して実行されるコードに含めたい場合は、gotoname__を使用する必要があります。 (もちろん、異なる条件付きでこれを避けるための手段と方法がありますが、それはこの問題に関連するほぼすべてのことに当てはまります)。そのため、C#はswitchname__内で複数のコードブロックをヒットさせたいという1つの状況に対処するためのすでに通常の方法を基に構築され、フォールスルーもカバーするように一般化しました。 Cで新しいラベルを追加する必要がありますが、C#ではラベルとしてcasename__を使用できるため、どちらの場合もより便利で自己文書化されています。 C#では、below_sixラベルを削除して、goto case 5を使用できます。これにより、作業内容が明確になります。 (breakname__にもdefaultname__を追加する必要があります。これは、上記のCコードをC#コードではなく明確にするためだけに省略しました)。

要約すると:

  1. C#は、40年前のCコードのように最適化されていないコンパイラ出力に直接関連しなくなりました(最近のCもそうではありません)。このため、フォールスルーの影響は関係ありません。
  2. C#は、暗黙のbreakname__だけでなく、Cと互換性があります。これは、似たような言語に精通している人が言語を簡単に習得でき、移植が容易だからです。
  3. C#は、過去40年間に問題を引き起こすものとしてよく文書化されてきたバグまたは誤解されたコードの可能性のある原因を取り除きます。
  4. C#は、C(ドキュメントのフォールスルー)に関する既存のベストプラクティスをコンパイラによって実行可能にします。
  5. C#は、異常な場合をより明示的なコードを含むものにし、通常の場合を単にコードを自動的に記述するコードにします。
  6. C#は、Cで使用されているのと同じgotoname__ベースのアプローチを使用して、異なるcasename__ラベルから同じブロックをヒットします。
  7. C#では、gotoname__ステートメントをラベルとして機能させることができるため、casename__ベースのアプローチがCよりも便利で明確になります。

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


*ある形式のBASICでは、脆弱で、特にgotoname__を禁止するための説得力のあるケースであるが、低レベルのコードがジャンプすることができるような、より高い言語の同等物を示すのに役立つGOTO (x AND 7) * 50 + 240のようなことができます。値を基にした算術に基づいています。これは、手作業で保守する必要があるものではなく、コンパイルの結果である場合にはるかに合理的です。特にDuff's Deviceの実装は、nopname__フィラーの追加を必要とせずに各命令ブロックが同じ長さになることが多いため、同等のマシンコードまたはILに適しています。

†Duff's Deviceが妥当な例外として再度登場します。それと同様のパターンで操作の繰り返しがあるという事実は、その効果への明確なコメントがなくてもフォールスルーの使用を比較的明確にするのに役立ちます。

19
Jon Hanna

あなたは '後藤ラベル'をすることができます http://www.blackwasp.co.uk/CSharpGoto.aspx

Gotoステートメントは、無条件にプログラムの制御を別のステートメントに転送する単純なコマンドです。このコマンドは スパゲッティコード につながる可能性があるため、すべての高級プログラミング言語からの削除を推奨する一部の開発者にはしばしば批判されます。 。これは、gotoステートメントや類似のジャンプステートメントが非常に多く、コードの読み取りや保守が困難になる場合に発生します。しかし、慎重に使用すると、gotoステートメントがいくつかの問題に対して優雅な解決策を提供することを指摘するプログラマーもいます...

15
kenny

彼らは意志によって使用されていないが問題を引き起こしていたときを避けるために設計によってこの動作を省いた。

Case部分に次のような文がない場合にのみ使用できます。

switch (whatever)
{
    case 1:
    case 2:
    case 3: boo; break;
}
8
Biri

彼らはc#のswitch文(C/Java/C++から)の振る舞いを変更しました。私はその推論が人々が転倒について忘れていたためにエラーが発生したと思います。私が読んだある本はgotoを使ってシミュレーションすると言っていましたが、これは私にとって良い解決策のようには思えません。

4
Ken

各case文の後に、デフォルトのcaseであってもbreakまたはgoto文が必要です。

0
Shilpa gulati

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;
}
0
Dai Tran

Xamarin用のコンパイラが実際にこれを誤解しており、フォールスルーを可能にすることを付け加えるためのちょっとしたメモです。おそらく修正されていますが、まだリリースされていません。実際には失敗し、コンパイラが文句を言わなかったいくつかのコードでこれを発見しました。

0
user486646

switch(C#Reference)によると

C#は最後のものも含めてスイッチセクションの終わりを必要とします、

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

0
gm2008

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

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

0
Powerlord