16

-5再び質問からインスピレーションを得ました!

@Quartermeisterさんの【このコメント】を読んでビックリ!

では、なぜこれがコンパイルされるのか

switch(1) {
    case 2:
}

しかし、これはそうではありません。

int i;

switch(i=1) {
    case 2: // Control cannot fall through from one case label ('case 2:') to another
}

これも

switch(2) {
    case 2: // Control cannot fall through from one case label ('case 2:') to another
}

アップデート:

-5質問は になりました-3

4

3 に答える 3

21

それらのどれもコンパイルするべきではありません。C#仕様では、スイッチセクションに少なくとも1つのステートメントが必要です。パーサーはそれを禁止する必要があります。

パーサーが空のステートメントリストを許可するという事実を無視しましょう。それは関係のあることではありません。仕様では、スイッチセクションの端に到達可能なエンドポイントがあってはならないことが規定されています。それが関連するビットです。

最後の例では、スイッチセクションに到達可能なエンドポイントがあります。

void M(int x) { switch(2) { case 2: ; } }

したがって、エラーである必要があります。

あなたが持っていた場合:

void M(int x) { switch(x) { case 2: ; } }

その場合、コンパイラはxが2になるかどうかを知りません。控えめに言って、xが2になる可能性があると想定し、switch caseラベルが到達可能であるため、セクションに到達可能なエンドポイントがあると言います。

あなたが持っていた場合

void M(int x) { switch(1) { case 2: ; } }

次に、コンパイラは、ケースラベルに到達できないため、エンドポイントに到達できないと推論できます。コンパイラーは、定数1が定数2と等しくなることは決してないことを認識しています。

あなたが持っていた場合:

void M(int x) { switch(x = 1) { case 2: ; } }

また

void M(int x) { x = 1; switch(x) { case 2: ; } }

そうすれば、エンドポイントに到達できないことはわかっていますが、コンパイラはそれを認識していません。仕様のルールは、到達可能性は定数式を分析することによってのみ決定されるということです。変数を含む式は、他の方法でその値を知っている場合でも、定数式ではありません。

過去には、C#コンパイラにはバグがありましたが、そうではありませんでした。あなたは次のようなことを言うことができます:

void M(int x) { switch(x * 0) { case 2: ; } }

コンパイラーは、x * 0は0でなければならないと推論するため、ケースラベルに到達できません。これはバグで、C#3.0で修正しました。仕様では、その分析には定数のみxが使用され、定数ではなく変数であると規定されています。

これで、プログラムが合法である場合、コンパイラーはこのような高度な手法を使用して、生成されるコードに影響を与えることができます。あなたが次のようなことを言うなら:

void M(int x) { if (x * 0 == 0) Y(); }

そうすれば、コンパイラはあなたが書いたかのようにコードを生成できます

void M(int x) { Y(); }

必要に応じて。ただし、x * 0 == 0ステートメントの到達可能性を判断する目的で真であるという事実を使用することはできません。

最後に、あなたが持っている場合

void M(int x) { if (false) switch(x) { case 2: ; } }

次に、スイッチに到達できないことがわかります。したがって、ブロックには到達可能なエンドポイントがないため、これは驚くべきことに合法です。しかし、上記の議論を考えると、あなたは今それを知っています

void M(int x) { if (x * 0 != 0) switch(x) { case 2: ; } }

x * 0 != 0はとして扱わないfalseため、エンドポイントは到達可能と見なされます。

于 2013-03-07T21:43:00.230 に答える
2

Visual Studio 2012 では、最初の理由は明らかです。コンパイラは、コードに到達できないと判断します。

switch (1)
{
    case 2:
}

警告: 到達不能なコードが検出されました。

他の 2 つのケースでは、コンパイラは「制御は 1 つのケース ラベル ('ケース 2:') から別のケース ラベルにフォールスルーできません」と報告します。どちらの失敗したケースでも、「('case 1')」と表示されていません。

コンパイラは、定数評価について積極的ではないと思います。たとえば、以下は同等です。

int i;
switch(i=1)
{
    case 2:
}

int i = 1;
switch(i)
{
    case 2:
}

どちらの場合も、コンパイラは、評価を行い、記述内容が次のようになっていると判断できる場合に、コードを生成しようとします。

switch (1)
{
    case 2:
}

そして、コードに到達できないと判断します。

「なぜこれがコンパイルされないのか」という答えは、「JIT コンパイラーに積極的な最適化を処理させているため」になるのではないかと思います。

于 2013-03-07T21:24:02.337 に答える
1

さて、これの問題は、コンパイラがスイッチを完全に最適化してしまうことです。これが証拠です。

static void withoutVar()
{
    Console.WriteLine("Before!");

    switch (1)
    {
        case 2:
    }

    Console.WriteLine("After!");
}

ILSpy で逆コンパイルすると、次の IL が表示されます。

.method private hidebysig static 
    void withoutVar () cil managed 
{
    // Method begins at RVA 0x2053
    // Code size 26 (0x1a)
    .maxstack 8

    IL_0000: nop
    IL_0001: ldstr "Before!"
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: br.s IL_000e

    IL_000e: ldstr "After!"
    IL_0013: call void [mscorlib]System.Console::WriteLine(string)
    IL_0018: nop
    IL_0019: ret
} // end of method Program::withoutVar

どこにもswitchステートメントの記憶がありません。2番目のものも最適化しない理由は、演算子のオーバーロードとソートに関係していると思います1したがって、 に割り当てられると に変わるカスタム タイプがある可能性があります2。ただし、完全にはわかりませんが、バグレポートを提出する必要があるようです。

于 2013-03-07T20:44:27.030 に答える