web-dev-qa-db-ja.com

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

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

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

関連: 高速化、文字列をオンにするか、タイプをelseifにするか

203

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

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

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

まとめると、条件の数が5程度以上の場合、IFよりもSWITCHを優先し、そうでない場合はより見栄えの良いものを使用します。

314
ima

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

C#の場合、これも事実ですが、他の理由があります。

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

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

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

「スイッチステートメント」MSIL命令があり、これはjittされたときにジャンプテーブルを使用してスイッチステートメントを実装します。ただし、整数型でのみ機能します(この質問は文字列について尋ねます)。

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

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

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

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

ここで更新を行ったのは、最高評価の投稿であり、受け入れられた回答だからです。

ただし、REPに間違いがあるとは思わないため、コミュニティWikiにしました。機会があれば、投票してください。

49

switchを好む3つの理由:

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

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

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

逸話:彼が生涯功労賞を受賞した際に行った講演で、トニー・ホアが彼のキャリアの中で行ったことすべての中で、彼が最も誇りに思ったものが3つあったと聞いた:

  • クイックソートの発明
  • Switchステートメントの作成(Tonyがcaseステートメントと呼んだ)
  • 業界でのキャリアの開始と終了

switchなしで生きることは想像できません。

18
Norman Ramsey

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

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

でなければならない

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

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

違いは、elseステートメントが連結されている場合、switchステートメントは15よりもクリーンであることです。

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

14
Will

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に変えたとしても、単にゼロをチェックするほど速くはありません。

12
Mark Bessey

多くの場合、見栄えがよくなります。つまり、何が起こっているのかを理解しやすくなります。パフォーマンスの利点はせいぜい極度に最小限であることを考慮すると、コードの見方が最も重要な違いです。

したがって、if/elseの外観がよければ、それを使用し、そうでない場合はswitchステートメントを使用します。

7
gbjbaanb

副題ですが、多くの場合、if/elseおよびswitchステートメントが大きくなりすぎることを心配します(より頻繁に見ます)。これらはしばしば保守性を損ないます。

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

  1. 複数のifステートメントの内部でやりすぎ
  2. 人間が分析するよりも多くのケースステートメント
  3. If評価の条件が多すぎて、何が検索されているかがわかりません

修正するには:

  1. メソッドへの抽出リファクタリング。
  2. ケースの代わりにメソッドポインターを使用した辞書を使用するか、IoCを使用して構成を追加します。メソッドファクトリも役立ちます。
  3. 独自のメソッドに条件を抽出します
4
Chris Brandsma

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

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

あなたはスイッチでまたはルーチンを行うことができます

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

Switchステートメントは、if else ifよりも確実に高速です。 BlackWaspによって提供されたスピードテストがあります

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

- 見てみな

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

2
sleath

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

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

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

Breakコマンドを忘れた人についてはcodyが正しいことを認めますが、{}が間違っているブロックをifする人が複雑になっているのを見ることがほとんどです。それが、私がalwaysにifステートメントに{}を含める理由の1つです。読みやすいだけでなく、条件に別の行を追加する必要がある場合は、忘れずに追加できます。

2
dj_segfault

このリンクによると、 IF vs Switch switchおよびifステートメントを使用した反復テストの比較は1,000,000,000回の反復に似ています。所要時間はSwitchステートメント=43.0s&byIfステートメント=48.0s

文字通り208333331秒あたりの反復回数です。したがって、本当にもっと集中する必要がある場合は、

PS:条件の小さなリストのパフォーマンスの違いを知っているだけです。

2
Bretfort

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

これが私たちが発見したことです。文字列以外の有効なswitchステートメントは、switchステートメントとしてILにコンパイルされます。ただし、文字列の場合、ILではif/else if/elseに書き換えられます。したがって、私たちのケースでは、switchステートメントが文字列を比較する方法、たとえば大文字と小文字を区別するなどを知りたいと思い、リフレクターはすぐに答えを与えました。これは知っておくと役に立ちました。

文字列で大文字と小文字を区別して比較する場合は、couldとしてswitchステートメントを使用しますif/elseでString.Compareを実行するよりも高速です。 (編集:読み取り 文字列をオンにするか、タイプをオンにするか? 実際のパフォーマンステストの場合)==ただし、大文字と小文字を区別しない場合は、if/elseを使用することをお勧めします結果のコードはきれいではありません。

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

最良の経験則は、(真剣に)理にかなっている場合はswitchステートメントを使用することです。

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

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

更新:

文字列を大文字(例:ToUpper())に変換すると、ジャストインタイムコンパイラーがToLower()と比較した場合と同様にさらに最適化できることが明らかになっているため、実際にはより良い方法です。これはミクロな最適化ですが、タイトなループでは有用かもしれません。


ちょっとした注意:

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

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

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および等価テストを計算する必要がないことをコンパイラーに暗黙的に伝えているためです。

1
Charlie Martin

スイッチは次のようなプログラムがあるかどうかのような条件の場合よりも高速だと思います:

プログラムを作成して任意の番号(1〜99)を入力し、それがどのスロットa)1〜9、次にスロット1 b)11〜19、次にスロット2 c)21〜29、次にスロット3と続くかを確認します。 99

その後、あなたは多くの条件を作る必要があるが、息子のスイッチケースを入力する必要がある場合

スイッチ(/ 10なし)

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

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

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

0
user3957230

私はこれがまさに問題ではないことを知っていますが、そのレベルでの効率について考えるとき、コードをさらに抽象化する必要があるかもしれないことを本当に指摘する必要があります。特にロジックが含まれている場合は、スイッチケースは不要になります。 (私のPHPの例)。

    $feeMapping = [
        1000 => 1,
        2000 => 2,
        3000 => 3,
        4000 => 4,
        5000 => 5,
        6000 => 6,
        7000 => 7
    ];

    function findFee($feeMapping, $amount) {
        foreach ($feeMapping as $fee => $value) {
            if ($value >= $amount) {
                return $fee;
            }
        }

        return 7;
    }

    $feeValue = findFee($feeMapping, 200);

次に、同様のコードの冗長性を見てみましょう!

    if ($amount >= 1000) {
        return 1;
    } elseif ($amount >= 2000) {
        return 2;
    } elseif ($amount >= 3000) {
        return 3;
    } elseif ($amount >= 4000) {
        return 4;
    } elseif ($amount >= 5000) {
        return 5;
    } elseif ($amount >= 6000) {
        return 6;
    } else {
        return 7;
    }
0
SinisterGlitch

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

0
cody

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

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

Ifステートメントで使用できるロジックははるかに自由ですが、Ifステートメントが扱いにくくなったり、処理が不適切になったりすると、可読性が低下する可能性があります。

どちらも、あなたが直面しているものの文脈に応じて存在します。

0
Neil Meyer

私が気づいたのは、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;
}
0
Even Mien

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

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
Geen