C#でif / elseとswitch-caseを使用する間に大きな違いはありますか?


219

switchステートメントif/elseとC#を使用する場合の利点/欠点は何ですか。コードの外観以外に、それほど大きな違いがあるとは思えません。

結果のILまたは関連するランタイムパフォーマンスが根本的に異なる理由はありますか?

関連:文字列をオンにするか、タイプを別の方法でオンにする方が速いですか?



3
この質問は、自分で10億回繰り返していることに気付かない限り、大部分の開発者にとって理論的には興味深いものです。(その後、switchステートメントを使用して、48秒から43秒に進みます...)または、ドナルドクヌースの言葉で:「小さな効率については忘れる必要があります。時間の約97%を言います。時期尚早な最適化がすべての悪の根源です」en.wikipedia.org/wiki/Program_optimization#When_to_optimize
種牡馬

スイッチの不安定な共有スコープのため、私はしばしばスイッチの代わりにif / elseを使用します。
Josh

回答:


341

SWITCHステートメントは、デバッグモードまたは互換モードのIFと同じアセンブリのみを生成します。リリースでは、ジャンプテーブルにコンパイルされます(MSILの「switch」ステートメントを使用)。これはO(1)です。

C#(他の多くの言語とは異なり)でも文字列定数をオンにすることができます-これは少し異なる動作をします。任意の長さの文字列のジャンプテーブルを作成することは明らかに実用的ではないため、ほとんどの場合、このようなスイッチはIFのスタックにコンパイルされます。

ただし、条件の数がオーバーヘッドをカバーするのに十分な大きさである場合、C#コンパイラはHashTableオブジェクトを作成し、それに文字列定数を設定して、そのテーブルを検索してからジャンプします。ハッシュテーブルルックアップは厳密にO(1)ではなく、顕著な定数コストがありますが、ケースラベルの数が多い場合、IFの各文字列定数と比較するよりも大幅に高速になります。

要約すると、条件の数が5を超える場合は、IFよりもSWITCHを優先します。それ以外の場合は、見栄えのよいものを使用します。


1
C#コンパイラはハッシュテーブルを生成しますか?上記のコメントディスカッションでハッシュテーブルについて私が指摘した点は、C#コンパイラではなく、ネイティブコンパイラに関するものでした。C#コンパイラはハッシュテーブルを生成するためにどのようなしきい値を使用しますか?
スコットWisniewski

8
10くらいだと思います。安全側に20。ところで、私の怒りはあなたではなく、賛成票を投じ、受け入れてくれる人たちです。
ima

48
少し実験すると、カウント<= 6: "if"; カウント> = 7:辞書。これは、MS .NET 3.5 C#コンパイラの場合です。もちろん、バージョンやベンダーによって異なる可能性があります。
Jon Skeet

37
ねえ、お詫び申し上げます。骨頭でごめんなさい。
スコットWisniewski

フォローアップとして、実際のアプリケーションでは、ほとんどの場合、実際の世界との違いはありますか?私はC#のswitchステートメントが奇妙であると思います、それらの構文は他のものとまったく似ていません、そしてそれらは私のコードを読みにくくします、switchステートメントを使用するのは面倒です、あるいは私はただelse ifsでプログラムし、そして来るだけですパフォーマンスのボトルネックに遭遇したら、元に戻して交換しますか?
ジェイソンマスターズ

54

一般に(すべての言語とすべてのコンパイラを考慮して)、switchステートメントはif / elseステートメントよりも効率的です。これは、コンパイラがswitchステートメントからジャンプテーブルを生成するのが簡単だからです。適切な制約があれば、if / elseステートメントに対して同じことを実行できますが、それははるかに困難です。

C#の場合も同様ですが、他の理由があります。

コンパイラはハッシュテーブルを使用してジャンプを実装するため、文字列の数が多い場合、switchステートメントを使用するとパフォーマンスが大幅に向上します。

ストリングの数が少ない場合、2つの間のパフォーマンスは同じです。

これは、C#コンパイラがジャンプテーブルを生成しないためです。代わりに、IF / ELSEブロックと同等のMSILを生成します。

"switchステートメント" MSIL命令があり、jittedの場合、ジャンプテーブルを使用してswitchステートメントを実装します。ただし、整数型でのみ機能します(この質問は文字列について尋ねます)。

ストリングの数が少ない場合、ハッシュテーブルを使用するよりも、コンパイラーがIF / ELSEブロックを生成する方が効率的です。

私が最初にこれに気付いたとき、IF / ELSEブロックは少数の文字列で使用されているため、コンパイラは多数の文字列に対して同じ変換を行うと仮定しました。

これは間違っていました。「IMA」は私にこれを指摘するのに十分親切でした(まあ...彼はそれについて親切ではなかったが、彼は正しかった、そして私は間違っていた、それが重要な部分である)

また、MSILに「切り替え」命令がないことについて骨の折れる仮定をしました(切り替えプリミティブがある場合、なぜハッシュテーブルでそれを使用しないのかと考えたため、切り替えプリミティブがあってはなりません。 ...)。これは間違っていて、私の側では信じられないほど愚かでした。再び「IMA」はこれを私に指摘しました。

最高評価の投稿であり、承認された回答であるため、ここで更新しました。

しかし、私はそれをコミュニティWikiにしました。私が間違っていることでREPに値しないと考えているからです。機会があれば 'ima'の投稿に投票してください。


3
MSILにはスイッチプリミティブがあり、c#ステートメントはコンパイルして一般にCのようなルックアップを行います。特定の状況(ターゲットプラットフォーム、clスイッチなど)の下では、コンパイル中にスイッチがIFに展開される場合がありますが、これはフォールバック互換性の尺度にすぎません。
ima

6
私にできることは、愚かな間違いをお詫びすることです。私を信じて、私はそれについて馬鹿げていると感じます。真剣に、しかし、私はそれがまだ最良の答えだと思います。ネイティブコンパイラでは、ハッシュテーブルを使用してジャンプを実装することができるので、これはひどく間違ったことではありません。私はひとつ間違いを犯しました。
Scott Wisniewski、

9
ima、エラーがある場合は指摘してください。スコットのような音が投稿を修正してくれるでしょう。そうでない場合、答えを修正する能力を獲得した他の人がそうします。これがこのようなサイトが機能する唯一の方法であり、一般的には機能しているようです。または、ボールを持って帰宅します:)
jwalkerjr 2008

2
@スコット:2番目と3番目の段落を編集して、「文字列用」を明示的に示すことをお勧めします。人々は下部にある更新を読んでいないかもしれません。
Jon Skeet

4
@ima答えが客観的に間違っていると思われる場合は、正しいものに編集してください。だれもが回答を編集できるのはそのためです。
Miles Rout

18

を選択する3つの理由switch

  • 多くの場合、ネイティブコードを対象とするコンパイラは、switchステートメントを1つの条件分岐と間接ジャンプにコンパイルできますが、ifsのシーケンスには条件分岐のシーケンスが必要です。ケースの密度に応じて、caseステートメントを効率的にコンパイルする方法について、非常に多くの学習論文が書かれています。一部はlccコンパイラページからリンクされています。(Lccには、スイッチ用のより革新的なコンパイラの1つがありました。)

  • switchステートメントは相互に排他的な選択肢の中から選択するものであり、switch構文により、この制御フローは、if-then-elseステートメントのネストよりも、プログラマに対して透過的になります

  • 確実にMLやHaskellを含む一部の言語では、コンパイラは、ケースを省略していないかどうかを確認します。私はこの機能をMLとHaskellの主要な利点の1つと見なしています。C#でこれができるかどうかわかりません。

逸話:彼が生涯の功績を称える賞を受賞した講義で、トニー・ホアールは彼のキャリアの中で彼がしたすべてのことの中で、彼が最も誇りに思っていることを3つ挙げたと聞きました:

  • クイックソートの発明
  • switchステートメントの作成(Tonyはこのcaseステートメントを呼び出しました)
  • 業界での彼のキャリアの始まりと終わり

私はない生活を想像することはできませんswitch


16

コンパイラーは、ほとんどすべてを同じコードに最適化し、わずかな違いがあります(Knuthなど)。

違いは、他のステートメントがつながっている場合、switchステートメントは15よりもきれいであることです。

友達は、友達にif-elseステートメントをスタックさせません。


13
「友達は、友達にif-elseステートメントを積み重ねさせることはできません。」バンバーシッターを作成する必要があります:)
マシューM.​​オズボーン

14

実際には、switchステートメントの方が効率的です。コンパイラーは、if / elseステートメントではできないルックアップテーブルに最適化します。欠点は、switchステートメントを変数値で使用できないことです。
あなたはできません:

switch(variable)
{
   case someVariable
   break;
   default:
   break;
}

それはする必要があります

switch(variable)
{
  case CONSTANT_VALUE;
  break;
  default:
  break;
}

1
何か番号はありますか?コンパイラーがif / Elseよりもswtichステートメントをどれだけ適切に最適化できるかを知りたいと思います
Matthew M. Osborn

はい、switchステートメントは常にO(1)に最適化され、if elseステートメントはO(n)になりますが、nはif / else ifステートメント内の正しい値の位置です。
kemiller2002 2008

C#の場合、これは当てはまりません。詳細については、以下の投稿を参照してください。
スコットWisniewski

私はそれを完全に確信しているわけではありませんが、私がそれを見つけたと誓った本の中に情報を見つけることができません。最適化を有効にしてコンパイルしない限り、ジャンプテーブルは作成されません。
kemiller2002 2008

私はデバッグモードと小売モードの両方でコンパイルしましたが、どちらの場合もif / elseブロックを生成します。あなたが見ていた本がC#について話していたことを本当に確信していますか?この本はおそらくコンパイラ本か、CまたはC ++に関する本でした
Scott Wisniewski

14

私は、switchステートメントの想定される効率の利点は、さまざまなケースがほぼ同じように可能であることに依存しているという(明白な?)値の1つ(またはいくつか)の可能性がはるかに高い場合、最も一般的なケースが最初にチェックされるようにすることで、if-then-elseラダーの方がはるかに高速になります。

したがって、たとえば:

if (x==0) then {
  // do one thing
} else if (x==1) {
  // do the other thing
} else if (x==2) {
  // do the third thing
}

switch(x) {
  case 0: 
         // do one thing
         break;
  case 1: 
         // do the other thing
         break;
  case 2: 
         // do the third thing
         break;
}

xが90%の時間でゼロの場合、「if-else」コードはスイッチベースのコードの2倍の速度になります。コンパイラが「スイッチ」をある種の巧妙なテーブル駆動のgotoに変えたとしても、単純にゼロをチェックするほど速くはありません。


3
時期尚早の最適化はありません!一般的に、いくつかのケースより多くのケースがあり、それらにswitch互換性がある場合、switchステートメントはより優れています(より読みやすく、場合によってはより高速です)。 あなたが知っている場合は 1ケースがはるかに可能性があることを、あなたは形成するために、それを引き出すことができますif- else- switch構造をして、それはより速く測定可能だ場合、あなたは中にいることを去る(リピートを、必要に応じて。)IMOそれはまだの合理的に読めます。switch縮退して小さすぎる場合、正規表現置換はそれを- else ifチェーンに変換する作業のほとんどを行います。
2012

6
元の質問(3年前!)は、if / elseとswitchの間の利点と欠点を尋ねました。これは一例です。私は個人的に、この種の最適化がルーチンの実行時間に大きな違いをもたらすことを見てきました。
マークベッシー2012

7

多くの場合、見栄えがよくなります。つまり、何が起こっているのかを理解しやすくなります。パフォーマンスのメリットはせいぜいごくわずかであるため、コードのビューが最も重要な違いです。

したがって、if / elseの方が見栄えが良い場合はそれを使用し、そうでない場合はswitchステートメントを使用します。


4

余談ですが、私はよく心配します(そしてもっと頻繁に見ます)if/ elseおよびswitchステートメントが非常に多くなり、ケースが多すぎます。これらはしばしば保守性を損なう。

一般的な犯人は次のとおりです。

  1. 複数のifステートメント内でやりすぎ
  2. 人間が分析できるよりも多くのケースステートメント
  3. 何が求められているかを知るためのif評価の条件が多すぎる

修正するには:

  1. メソッドリファクタリングに抽出します。
  2. ケースの代わりにメソッドポインタを含むディクショナリを使用するか、IoCを使用して構成可能性を追加します。メソッドファクトリも役立ちます。
  3. 独自のメソッドに条件を抽出する

4

このリンクに従って、スイッチの対のIF 10億回の反復のために、時間が取らように反復テストの比較スイッチとif文を使用して、あるスイッチの声明= 43.0s&によってIF文 = 48.0s

これは文字通り毎秒20833333回の反復です。ですから、もっと集中する必要がある場合は、

PS:条件のリストが小さい場合のパフォーマンスの違いを知っておく必要があります。


それは私にとってそれを釘付けにします。
グレッグガム

3

ifまたはelseステートメントだけを使用している場合、基本ソリューションは比較を使用していますか?オペレーター

(value == value1) ? (type1)do this : (type1)or do this;

またはルーチンをスイッチで実行できます

switch(typeCode)
{
   case TypeCode:Int32:
   case TypeCode.Int64:
     //dosomething here
     break;
   default: return;
}

2

これは実際にあなたの質問に答えるものではありませんが、コンパイルされたバージョン間にほとんど違いがないことを考えると、あなたの意図を最もよく表す方法でコードを書くことをお勧めします。コンパイラが期待どおりの動作をする可能性が高くなるだけでなく、他の人がコードを保守しやすくなります。

1つの変数/属性の値に基づいてプログラムを分岐させることが目的である場合、switchステートメントはその意図を最もよく表します。

さまざまな変数/属性/条件に基づいてプログラムを分岐させることが目的である場合、if / else ifチェーンがその意図を最もよく表します。

ブレイクコマンドを忘れた人にはcodyが適切であることを認めますが、{}が間違っているブロックでifを実行すると複雑になるため、条件ステートメントにあるはずの行はそうではありません。それが1行でも、ifステートメントに常に {}を含める理由の1つです。読みやすくなるだけでなく、条件に別の行を追加する必要がある場合は、忘れずに追加することができます。


2

興味のある質問。これは数週間前に職場で発生し、サンプルスニペットを記述して.NET Reflectorで表示することで答えを見つけました(reflectorは素晴らしいです!!私はそれが大好きです)。

これは、私たちが発見したものです。文字列以外の有効なswitchステートメントは、switchステートメントとしてILにコンパイルされます。ただし、文字列の場合は、ILではif / else if / elseとして書き換えられます。したがって、私たちのケースでは、switchステートメントがどのように文字列を比較するかを知りたいと思っていました。これは知っておくと役に立ちました。

文字列で大文字と小文字を区別する比較を行う場合は、if / elseでString.Compareを実行するよりも高速なので、switchステートメントを使用できます。(編集:一部の実際のパフォーマンステストについては、「何がより速く、文字列またはelseifをタイプに切り替えますか?」をお読みください)。

switch (myString.ToLower())
{
  // not a good solution
}

経験則として、(真剣に)意味がある場合は、次のようにswitchステートメントを使用することをお勧めします。

  • コードの可読性が向上します
  • 値の範囲(float、int)または列挙型を比較している

値を操作してswitchステートメントにフィードする必要がある場合(切り替える一時変数を作成する場合)、おそらくif / else制御ステートメントを使用する必要があります。

アップデート:

文字列を大文字に変換すること(例えばToUpper())は、Just-In-Timeコンパイラーがと比較した場合に実行できるさらなる最適化があるようToLower()です。これはミクロの最適化ですが、タイトなループでは役立ちます。


少し注意:

switchステートメントの読みやすさを改善するには、次のことを試してください。

  • 最も可能性の高いブランチを最初に置く、つまり最もアクセスされるブランチ
  • それらがすべて発生する可能性が高い場合は、それらをアルファベット順にリストして、見つけやすくします。
  • 最後の残りの条件にはデフォルトのキャッチオールを使用しないでください。これは遅延であり、コードのライフの後半で問題を引き起こします。
  • 発生する可能性が非常に低い場合でも、デフォルトのキャッチオールを使用して不明な条件をアサートします。それがアサートが良いことです。

多くの場合、特に多くのケースがあり、ハッシュテーブルが生成される場合は、ToLower()の使用が適切なソリューションです。
Blaisorblade、2009年

「switchステートメントにフィードする値を操作する必要がある場合(切り替える一時変数を作成する場合)、おそらくif / else制御ステートメントを使用する必要があります。」-良いアドバイス、ありがとう。
卑劣さ2009

2

switchステートメントは、if if if if if if else ifです。BlackWaspから提供されたスピードテストがあります

http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx

- 見てみな

しかし、説明しようとしている可能性に大きく依存しますが、私は可能な限りswitchステートメントを使用するようにしています。


1

C#だけでなく、すべてのCベースの言語だと思います。スイッチは定数に限定されているため、「ジャンプテーブル」を使用して非常に効率的なコードを生成できます。Cのケースは実際には古き良きFORTRANで計算されたGOTOですが、C#のケースは定数に対するテストです。

オプティマイザが同じコードを作成できるとは限りません。たとえば、

if(a == 3){ //...
} else if (a == 5 || a == 7){ //...
} else {//...
}

これらは複合ブール値であるため、生成されたコードは値を計算して短絡する必要があります。今度は同等のものを検討してください

switch(a){
   case 3: // ...
    break;
   case 5:
   case 7: //...
    break;
   default: //...
}

これは次のようにコンパイルできます

BTABL: *
B3:   addr of 3 code
B5:
B7:   addr of 5,7 code
      load 0,1 ino reg X based on value
      jump indirect through BTABL+x

ORと等式のテストを計算する必要がないことをコンパイラーに暗黙的に伝えているためです。


最適化が実装されている限り、優れたオプティマイザが最初のコードを処理できない理由はありません。「コンパイラーは最適化できません」は、人間だけが調整できる意味の違いに依存します(つまり、f()が呼び出された場合、f()が常に0または1を返すことはわかりません)。
Blaisorblade、2009年

0

私のcs教授はステートメントを切り替えないように提案しました。私は彼の言ったことを正確に思い出すことはできませんが、switchステートメント(数年前)の例を示したいくつかの独創的なコードベースを見ると、間違いがたくさんありました。


C#の問題ではありません。参照:stackoverflow.com/questions/174155/… ...また、stackoverflow.com
questions / 188461 /

0

私が気付いたのは、if / elseステートメントとswitchステートメントを組み合わせることができるということです。前提条件を確認する必要がある場合に非常に役立ちます。

if (string.IsNullOrEmpty(line))
{
    //skip empty lines
}
else switch (line.Substring(0,1))
{
    case "1":
        Console.WriteLine(line);
        break;
    case "9":
        Console.WriteLine(line);
        break;
    default:
        break;
}

3
私はこれが古いことを知っていますが、技術的にはあなたは何も「組み合わせ」ていないと思います。中括弧なしの「else」がある場合は常に、次のステートメントが実行されます。そのステートメントは、通常次の行にインデントされて表示される1行のステートメント、またはif、switch、using、lockなどの場合のような複合ステートメントの場合があります。つまり、「else if」、「 else switch」、「else using」など。そうは言っても、私はそれがどのように見え、ほとんど意図的に見えているのが好きです。(免責事項:私はこれらすべてを試していないので、私は間違っているかもしれません!)
ネルソンロザーメル

ネルソン、あなたは100%正しいです。この回答を投稿した後、なぜこれが起こるのかを理解しました。
Mienでも

0

次のようなプログラムがあるかどうかを確認するような条件よりも、スイッチの方が速いと思います。

任意の数値(1〜99)を入力するプログラムを作成し、それがどのスロットにあるかを確認します。a)1〜9、次にスロット1 b)11〜19、次にスロット2 c)21〜29、次にスロット3など、89〜 99

次に、多くの条件を設定する必要があるが、息子のケースを切り替える場合は、入力する必要があります

スイッチ(/ 10なし)

ケース0 = 1-9の場合、ケース1 = 11-19など

それはとても簡単になります

そのような例は他にもたくさんあります!


0

switchステートメントは、基本的には等価性の比較です。キーボードイベントは、switchステートメントよりもコードの読み書きが容易な場合に大きな利点があり、elseifステートメントの場合と同様に、{bracket}がないと問題が発生する可能性があります。

char abc;
switch(abc)
{
case a: break;
case b: break;
case c: break;
case d: break;
}

if elseifステートメントは、1つ以上のソリューションに最適ですif(theAmountOfApplesが5より大きい&& theAmountOfApplesが10より小さい)リンゴを保存しますelse if(theAmountOfApplesが10より大きい|| theAmountOfApples == 100)リンゴを販売します。私はc#やc ++を記述しませんが、Javaを学ぶ前にそれを学びました。それらは近い言語です。


0

switchステートメントの考えられる欠点の1つは、複数の条件がないことです。if(else)には複数の条件を設定できますが、スイッチの条件が異なる複数のcaseステートメントを設定することはできません。

switchステートメントは、単純なブール式/式の範囲を超える論理演算には適していません。これらのブール方程式/式については、非常に適していますが、他の論理演算には適していません。

Ifステートメントで使用できるロジックの自由度ははるかに高くなりますが、Ifステートメントが扱いにくくなるか、扱いが不十分になると、読みやすさが低下する可能性があります。

どちらもあなたが直面しているもののコンテキストに応じて場所があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.