switch
ステートメントif/else
とC#を使用する場合の利点/欠点は何ですか。コードの外観以外に、それほど大きな違いがあるとは思えません。
結果のILまたは関連するランタイムパフォーマンスが根本的に異なる理由はありますか?
switch
ステートメントif/else
とC#を使用する場合の利点/欠点は何ですか。コードの外観以外に、それほど大きな違いがあるとは思えません。
結果のILまたは関連するランタイムパフォーマンスが根本的に異なる理由はありますか?
回答:
SWITCHステートメントは、デバッグモードまたは互換モードのIFと同じアセンブリのみを生成します。リリースでは、ジャンプテーブルにコンパイルされます(MSILの「switch」ステートメントを使用)。これはO(1)です。
C#(他の多くの言語とは異なり)でも文字列定数をオンにすることができます-これは少し異なる動作をします。任意の長さの文字列のジャンプテーブルを作成することは明らかに実用的ではないため、ほとんどの場合、このようなスイッチはIFのスタックにコンパイルされます。
ただし、条件の数がオーバーヘッドをカバーするのに十分な大きさである場合、C#コンパイラはHashTableオブジェクトを作成し、それに文字列定数を設定して、そのテーブルを検索してからジャンプします。ハッシュテーブルルックアップは厳密にO(1)ではなく、顕著な定数コストがありますが、ケースラベルの数が多い場合、IFの各文字列定数と比較するよりも大幅に高速になります。
要約すると、条件の数が5を超える場合は、IFよりもSWITCHを優先します。それ以外の場合は、見栄えのよいものを使用します。
一般に(すべての言語とすべてのコンパイラを考慮して)、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つの理由switch
:
多くの場合、ネイティブコードを対象とするコンパイラは、switchステートメントを1つの条件分岐と間接ジャンプにコンパイルできますが、if
sのシーケンスには条件分岐のシーケンスが必要です。ケースの密度に応じて、caseステートメントを効率的にコンパイルする方法について、非常に多くの学習論文が書かれています。一部はlccコンパイラページからリンクされています。(Lccには、スイッチ用のより革新的なコンパイラの1つがありました。)
switchステートメントは相互に排他的な選択肢の中から選択するものであり、switch構文により、この制御フローは、if-then-elseステートメントのネストよりも、プログラマに対して透過的になります。
確実にMLやHaskellを含む一部の言語では、コンパイラは、ケースを省略していないかどうかを確認します。私はこの機能をMLとHaskellの主要な利点の1つと見なしています。C#でこれができるかどうかわかりません。
逸話:彼が生涯の功績を称える賞を受賞した講義で、トニー・ホアールは彼のキャリアの中で彼がしたすべてのことの中で、彼が最も誇りに思っていることを3つ挙げたと聞きました:
case
ステートメントを呼び出しました)私はない生活を想像することはできませんswitch
。
コンパイラーは、ほとんどすべてを同じコードに最適化し、わずかな違いがあります(Knuthなど)。
違いは、他のステートメントがつながっている場合、switchステートメントは15よりもきれいであることです。
友達は、友達にif-elseステートメントをスタックさせません。
実際には、switchステートメントの方が効率的です。コンパイラーは、if / elseステートメントではできないルックアップテーブルに最適化します。欠点は、switchステートメントを変数値で使用できないことです。
あなたはできません:
switch(variable)
{
case someVariable
break;
default:
break;
}
それはする必要があります
switch(variable)
{
case CONSTANT_VALUE;
break;
default:
break;
}
私は、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に変えたとしても、単純にゼロをチェックするほど速くはありません。
switch
互換性がある場合、switch
ステートメントはより優れています(より読みやすく、場合によってはより高速です)。 あなたが知っている場合は 1ケースがはるかに可能性があることを、あなたは形成するために、それを引き出すことができますif
- else
- switch
構造をして、それはより速く測定可能だ場合、あなたは中にいることを去る(リピートを、必要に応じて。)IMOそれはまだの合理的に読めます。switch
縮退して小さすぎる場合、正規表現置換はそれを- else if
チェーンに変換する作業のほとんどを行います。
余談ですが、私はよく心配します(そしてもっと頻繁に見ます)if
/ else
およびswitch
ステートメントが非常に多くなり、ケースが多すぎます。これらはしばしば保守性を損なう。
一般的な犯人は次のとおりです。
修正するには:
これは実際にあなたの質問に答えるものではありませんが、コンパイルされたバージョン間にほとんど違いがないことを考えると、あなたの意図を最もよく表す方法でコードを書くことをお勧めします。コンパイラが期待どおりの動作をする可能性が高くなるだけでなく、他の人がコードを保守しやすくなります。
1つの変数/属性の値に基づいてプログラムを分岐させることが目的である場合、switchステートメントはその意図を最もよく表します。
さまざまな変数/属性/条件に基づいてプログラムを分岐させることが目的である場合、if / else ifチェーンがその意図を最もよく表します。
ブレイクコマンドを忘れた人にはcodyが適切であることを認めますが、{}が間違っているブロックでifを実行すると複雑になるため、条件ステートメントにあるはずの行はそうではありません。それが1行でも、ifステートメントに常に {}を含める理由の1つです。読みやすくなるだけでなく、条件に別の行を追加する必要がある場合は、忘れずに追加することができます。
興味のある質問。これは数週間前に職場で発生し、サンプルスニペットを記述して.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ステートメントを使用することをお勧めします。
値を操作してswitchステートメントにフィードする必要がある場合(切り替える一時変数を作成する場合)、おそらくif / else制御ステートメントを使用する必要があります。
アップデート:
文字列を大文字に変換すること(例えばToUpper()
)は、Just-In-Timeコンパイラーがと比較した場合に実行できるさらなる最適化があるようToLower()
です。これはミクロの最適化ですが、タイトなループでは役立ちます。
少し注意:
switchステートメントの読みやすさを改善するには、次のことを試してください。
switchステートメントは、if if if if if if else ifです。BlackWaspから提供されたスピードテストがあります
http://www.blackwasp.co.uk/SpeedTestIfElseSwitch.aspx
- 見てみな
しかし、説明しようとしている可能性に大きく依存しますが、私は可能な限りswitchステートメントを使用するようにしています。
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と等式のテストを計算する必要がないことをコンパイラーに暗黙的に伝えているためです。
私のcs教授はステートメントを切り替えないように提案しました。私は彼の言ったことを正確に思い出すことはできませんが、switchステートメント(数年前)の例を示したいくつかの独創的なコードベースを見ると、間違いがたくさんありました。
私が気付いたのは、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;
}
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を学ぶ前にそれを学びました。それらは近い言語です。
switchステートメントの考えられる欠点の1つは、複数の条件がないことです。if(else)には複数の条件を設定できますが、スイッチの条件が異なる複数のcaseステートメントを設定することはできません。
switchステートメントは、単純なブール式/式の範囲を超える論理演算には適していません。これらのブール方程式/式については、非常に適していますが、他の論理演算には適していません。
Ifステートメントで使用できるロジックの自由度ははるかに高くなりますが、Ifステートメントが扱いにくくなるか、扱いが不十分になると、読みやすさが低下する可能性があります。
どちらもあなたが直面しているもののコンテキストに応じて場所があります。