簡體   English   中英

C# 中的 Switch 語句失敗?

[英]Switch statement fallthrough in C#?

Switch 語句失敗是我個人喜歡switchif/else if結構的主要原因之一。 這里有一個例子:

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[]應該在函數之外聲明:嗯,他們是,這只是一個例子。

編譯器失敗並出現以下錯誤:

Control cannot fall through from one case label ('case 3:') to another
Control cannot fall through from one case label ('case 2:') to another

為什么? 有沒有辦法在沒有三個if的情況下獲得這種行為?

(復制/粘貼我在其他地方提供答案

通過switch - case下降可以通過在一個case中沒有代碼(參見case 0 ),或使用特殊的goto case (見case 1 )或goto default (見case 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;
}

“為什么”是為了避免意外跌倒,對此我感激不盡。 這是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太近 - 有一些有用的變化可以在范圍等方面進行。可以說,一個更聰明的開關可以進行模式匹配等會有所幫助,但這確實從開關變為“檢查一系列條件” - 此時可能會要求使用其他名稱。

交換機漏洞歷史上是現代軟件中錯誤的主要來源之一。 語言設計者決定強制要求在案例結束時跳轉,除非您在沒有處理的情況下直接默認為下一個案例。

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

為了在這里添加答案,我認為值得考慮與此相關的相反問題,即。 為什么C首先允許掉線?

任何編程語言當然都有兩個目標:

  1. 向計算機提供說明。
  2. 留下程序員的意圖記錄。

因此,任何編程語言的創建都是如何最好地服務於這兩個目標之間的平衡。 一方面,變得更容易變成計算機指令(無論是機器代碼,字節碼如IL,還是指令在執行時被解釋),那么編譯或解釋過程將更加高效,可靠和緊湊的輸出。 盡管如此,這個目標導致我們只是在匯編,IL或甚至原始操作碼中編寫,因為最簡單的編譯是根本沒有編譯的地方。

相反,語言表達程序員的意圖越多,而不是為此目的采取的手段,在編寫和維護期間程序就越容易理解。

現在, switch總是可以通過將其轉換為等效的if-else塊或類似鏈來編譯,但它被設計為允許編譯成特定的公共匯編模式,其中一個取值,計算它的偏移量(無論是查找由值的完美哈希索引的表,或通過值*的實際算術查找。 在這一點上值得注意的是,今天,C#編譯有時會switch為等效的if-else ,有時會使用基於散列的跳轉方法(同樣使用C,C ++和其他具有可比語法的語言)。

在這種情況下,有兩個很好的理由允許掉期:

  1. 它無論如何都是自然發生的:如果你將一個跳轉表構建成一組指令,並且其中一個早期批量指令不包含某種跳轉或返回,那么執行將自然地進入下一批。 如果你將switch成使用機器碼的跳轉表,那么允許掉線就是“剛剛發生”。

  2. 在匯編中編寫的編碼器已經習慣了等價物:當在匯編中手動編寫跳轉表時,他們必須考慮給定的代碼塊是以返回結束,跳出表格還是繼續到下一個街區。 因此,讓編碼器在必要時添加明確的break對於編碼器來說也是“自然的”。

因此,在計算機語言的兩個目標之間進行平衡是合理的嘗試,因為它與生成的機器代碼和源代碼的表達性有關。

四十年后,情況並不完全相同,原因有以下幾點:

  1. 今天C中的編碼員可能很少或根本沒有裝配經驗。 許多其他C風格語言的編碼器甚至不太可能(尤其是Javascript!)。 任何“人們習慣於裝配”的概念都不再相關。
  2. 優化的改進意味着switch的可能性要么變成if-else因為它被認為是最有效的方法,或者轉變為跳轉表方法的特別深奧的變體更高。 較高級別和較低級別方法之間的映射不像以前那樣強大。
  3. 經驗表明,跌倒往往是少數情況而不是常態(對Sun的編譯器的研究發現,3%的switch塊在同一塊上使用了除了多個標簽之外的掉落,並且認為使用 - 這里的例子意味着這3%實際上遠高於正常水平)。 因此,所研究的語言使得不尋常的事情比普通的更容易照顧。
  4. 經驗表明,在意外完成的情況下,以及在維護代碼的人錯過正確的漏報的情況下,跌倒往往是問題的根源。 后者是與掉落相關的錯誤的一個微妙的補充,因為即使您的代碼完全沒有錯誤,您的墮落仍然可能導致問題。

與最后兩點相關,請考慮當前版本的K&R中的以下引用:

從一個案例落到另一個案例並不健全,在修改程序時容易崩潰。 除了單個計算的多個標簽之外,應謹慎使用漏洞並進行評論。

作為一個好的形式,在最后一個案例(默認在這里)之后休息,即使它在邏輯上是不必要的。 有一天,當最后添加另一個案例時,這一點防御性編程將為您節省開支。

因此,從馬的口中,C中的跌落是有問題的。 總是記錄帶有注釋的漏洞是一種良好的做法,這是應用一般原則的應用,應該記錄一個人做一些不尋常的事情,因為這將是后來檢查代碼和/或使你的代碼看起來像什么的當它實際上是正確的時,它有一個新手的錯誤。

當你考慮它時,代碼如下:

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

添加的東西,使落空在代碼中明確的,它只是沒有東西可以被檢測(或者其缺乏可檢測)的編譯器。

因此,事實上必須明確表示C#中的墮落並不會給那些在其他C風格的語言中寫得很好的人增加任何懲罰,因為他們已經在他們的墮落中明確表示。†

最后, goto的使用已經成為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();
}

在這種情況下,我們希望將一個塊包含在為除前一個塊之外的值執行的代碼中執行的代碼中,然后我們必須使用goto (當然,有一些方法和方法可以通過不同的條件避免這種情況,但對於與這個問題相關的所有事情都是如此)。 因此,C#建立在已經正常的方式來處理我們想要在switch擊中多個代碼塊的一種情況,並且只是將其概括為覆蓋掉落。 它還使兩個案例更方便和自我記錄,因為我們必須在C中添加新標簽,但可以將case用作C#中的標簽。 在C#中,我們可以刪除below_six標簽並使用goto case 5 ,它更清楚我們正在做什么。 (我們還必須為default添加break ,我只是為了使上面的C代碼顯然不是C#代碼而遺漏了)。

因此總結如下:

  1. C#不再像40年前的C代碼那樣直接與未經優化的編譯器輸出相關(這些天也不是C),這使得墮落的靈感之一無關緊要。
  2. C#與C語言保持兼容,不僅具有隱式break ,因為熟悉類似語言的人更容易學習語言,並且更容易移植。
  3. C#刪除了一個可能的錯誤來源或被誤解的代碼,這些代碼在過去四十年中已被充分記錄為導致問題。
  4. C#通過編譯器強制執行C(文檔落實)現有的最佳實踐。
  5. C#使得不常見的情況是具有更明確代碼的情況,通常情況下代碼之一只是自動寫入。
  6. C#使用相同的基於goto的方法從C中使用的不同case標簽中命中相同的塊。它只是將其推廣到其他一些情況。
  7. 通過允許case語句作為標簽,C#使得基於goto的方法比在C中更方便,更清晰。

總而言之,這是一個非常合理的設計決策


*某些形式的BASIC可以讓人們做GOTO (x AND 7) * 50 + 240 ,雖然脆弱,因此禁止goto特別有說服力的情況,確實有助於顯示更高語言的等效方式較低級別的代碼可以根據對值的算術進行跳轉,這在編譯結果時更合理,而不是必須手動維護。 Duff設備的實現尤其適用於等效的機器代碼或IL,因為每個指令塊通常具有相同的長度而無需添加nop填充器。

†Duff的設備再次出現在這里,作為一個合理的例外。 事實上,即使沒有對這種效果的明確評論,使用這種和相似的模式重復操作也可以使得使用跌倒相對清晰。

你可以'轉到案例標簽' http://www.blackwasp.co.uk/CSharpGoto.aspx

goto語句是一個簡單的命令,它無條件地將程序的控制權轉移到另一個語句。 該命令經常受到一些開發人員的批評,他們主張將其從所有高級編程語言中刪除,因為它可能導致意大利面條代碼 當有如此多的goto語句或類似的跳轉語句使代碼難以閱讀和維護時,會發生這種情況。 但是,有些程序員指出goto語句在仔細使用時可以為某些問題提供優雅的解決方案......

他們通過設計省略了這種行為,以避免它不被意志使用但引起問題。

只有在案例部分中沒有語句時才能使用它,例如:

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

他們更改了c#的switch語句(來自C / Java / C ++)行為。 我猜測的原因是人們忘記了墜落而導致的錯誤。 我讀過的一本書說使用goto來模擬,但這聽起來不是一個很好的解決方案。

每個case塊后都需要一個諸如break之類的跳轉語句,包括最后一個塊,無論是case語句還是default語句。 除了一個例外,(與C ++ switch語句不同),C#不支持從一個case標簽到另一個case標簽的隱式降級。 一個例外是case語句沒有代碼。

- C#switch()文檔

簡單地說,添加Xamarin的編譯器實際上是錯誤的,它允許通過。 它應該是固定的,但尚未發布。 在一些實際上已經失敗的代碼中發現了這一點,並且編譯器沒有抱怨。

在每個case語句之后,即使它是默認情況,也需要breakgoto語句。

您可以通過goto關鍵字實現類似c ++的功能。

EX:

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;
}

C#不支持使用switch / case語句。 不知道為什么,但實際上沒有它的支持。 連鎖

開關(C#參考)說

C#需要切換部分的結尾,包括最后一部分,

所以你還需要添加break; 到你的default部分,否則仍然會有編譯器錯誤。

你忘了添加“休息”; 聲明到案例3.在案例2中,您將其寫入if塊。 因此試試這個:

case 3:            
{
    ans += string.Format("{0} hundred and ", numbers[number / 100]);
    break;
}


case 2:            
{
    int t = (number / 10) % 10;            
    if (t == 1)            
    {                
        ans += teens[number % 10];                
    }            
    else if (t > 1)                
    {
        ans += string.Format("{0}-", tens[t]);        
    }
    break;
}

case 1:            
{
    int o = number % 10;            
    ans += numbers[o];            
    break;        
}

default:            
{
    throw new ArgumentException("number");
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM