C # ifadesi geçişi geçiş?


365

Geçiş ifadesi geçişi, yapıları sevmek switchve sevmek için benim en önemli nedenlerimden biri if/else if. Burada bir örnek:

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

Akıllı insanlar kandırıyor çünkü string[]s fonksiyonun dışında ilan edilmeli: şey, bunlar sadece bir örnek.

Derleyici aşağıdaki hatayla başarısız olur:

Kontrol bir vaka etiketinden ('vaka 3:') diğerine geçemez
Kontrol bir vaka etiketinden ('vaka 2:') diğerine geçemez

Neden? Ve üç ifs olmadan bu tür davranışlar elde etmenin bir yolu var mı?

Yanıtlar:


648

( Başka bir yerde verdiğim cevabı kopyala / yapıştır )

Düşen switch- caseler, bir case(bkz. case 0) Kodunda hiç kod bulunmaması veya özel goto case(bkz. case 1) Veya goto default(bkz. case 2) Formlarının kullanılmasıyla elde edilebilir:

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

121
Bence, bu özel durumda, goto zararlı sayılmaz.
Thomas Owens

78
Lanet olsun - 1.0'ın ilk günlerinden beri C # ile program yapıyorum ve bunu şimdiye kadar hiç görmedim. Göstermeye gidiyor, her gün yeni şeyler öğreniyorsunuz.
Erik Forbes

37
Her şey yolunda, Erik. ECMA-334 özelliklerini büyüteçle okuyan derleyici teorisi nerd olduğumu bilmemizin tek nedeni.
Alex Lyman

13
@Dancrumb: Özelliğin yazıldığı sırada, C # henüz "yumuşak" anahtar kelimeler ('verim', 'var', 'from' ve 'select' gibi) eklememişti, bu yüzden üç gerçek seçeneği vardı: 1 ) 'geçişi' zor bir anahtar kelime yapın (değişken adı olarak kullanamazsınız), 2) böyle yumuşak bir anahtar kelimeyi desteklemek için gerekli kodu yazın, 3) zaten ayrılmış anahtar kelimeleri kullanın. # 1, bu taşıma kodu için büyük bir sorundu; # 2 anladığım kadarıyla oldukça büyük bir mühendislik göreviydi; ve gittikleri seçenekle, # 3'ün bir yan yararı vardı: aslında goto'nun temel kavramından özellik hakkında bilgi edinebilen kodu okuyan diğer geliştiriciler
Alex Lyman

10
Bütün bunlar açık bir düşüş için yeni / özel anahtar kelimeler hakkında konuşuyor. Sadece 'devam' anahtar kelimesini kullanamazlar mıydı? Başka bir deyişle. Anahtardan çıkın veya bir sonraki kasaya geçin (düşün).
Tal Even-Tov

44

"Neden" minnettar olduğum kazayla düşmekten kaçınmaktır. Bu, C ve Java'da nadir olmayan bir hata kaynağıdır.

Çözüm goto kullanmaktır, ör.

switch (number.ToString().Length)
{
    case 3:
        ans += string.Format("{0} hundred and ", numbers[number / 100]);
        goto case 2;
    case 2:
    // Etc
}

Anahtarın / kasanın genel tasarımı bence biraz talihsiz. C'ye çok yakın sıkıştı - kapsam belirleme vb. Açısından yapılabilecek bazı yararlı değişiklikler var. Muhtemelen desen eşleştirme vb. - hangi noktada belki de farklı bir isim aranırdı.


1
Bu, switch ve if / elseif arasındaki farktır. Anahtar, tek bir değişkenin çeşitli durumlarını kontrol etmek içindir, oysa / elseif bağlı olan, ancak mutlaka tek veya aynı değişken olmayan herhangi bir sayıda şeyi kontrol etmek için kullanılabilir.
Matthew Scharley

2
Kazayla düşmeyi önlemek olsaydı derleyici uyarısının daha iyi olacağını hissediyorum. Eğer if if (result = true) { }
ifadenizde

3
@ TalEven-Tov: Derleyici uyarıları, kodu daha iyi olmak için neredeyse her zaman düzeltebileceğiniz durumlar için olmalıdır. Şahsen örtük kırmayı tercih ederim, bu yüzden başlamak bir sorun olmaz, ancak bu farklı bir konudur.
Jon Skeet

26

Geçiş geçişi, tarihsel olarak modern yazılımlardaki en büyük hata kaynaklarından biridir. Dil tasarımcısı, bir sonraki davaya doğrudan işlem yapmadan varsayılan olarak geçmediğiniz sürece, davanın sonunda atlamayı zorunlu hale getirmeye karar verdi.

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

23
Bunun neden "durum 1, 2:" olmadığını asla anlamıyorum
BCS

11
@David Pfeffer: Evet, buna case 1, 2:izin veren dillerde de öyle. Asla anlamayacağım şey, herhangi bir modern dilin neden buna izin vermeyeceğidir.
BCS

@BCS ile goto deyimi, birden çok virgülle ayrılmış seçenekle başa çıkmak zor olabilir mi?
penguat

@pengut: Bunun case 1, 2:tek bir etiket olduğunu ancak birden çok ada sahip olduğunu söylemek daha doğru olabilir . - FWIW, bence düşmesini yasaklayan dillerin çoğunun "ardışık vaka etiketleri" indiyumuna özel bir durum olmadığını, bunun yerine vaka etiketlerini bir sonraki ifadeye ek açıklama olarak ele aldığını ve ( daha fazla) büyük / küçük harf etiketleri atlama.
BCS

22

Buradaki cevaplara eklemek için, bununla birlikte zıt soruyu düşünmeye değer olduğunu düşünüyorum, yani. C neden ilk etapta düşmeye izin verdi?

Herhangi bir programlama dili elbette iki amaca hizmet eder:

  1. Bilgisayara talimatlar verin.
  2. Programlayıcının niyetlerinin bir kaydını bırakın.

Herhangi bir programlama dilinin oluşturulması, bu iki amaca en iyi nasıl hizmet edileceği arasında bir dengedir. Bir yandan, bilgisayar yönergelerine (bunlar makine kodu, IL gibi bayt kodu veya yürütme sırasında talimatlar yorumlanıp dönüştürülmemesine) ne kadar kolaysa, derleme veya yorumlama sürecinin verimli, güvenilir ve kompakt çıktı. En uç noktaya gelindiğinde, bu hedef derleme, IL ve hatta ham op kodlarında sadece yazmamızla sonuçlanır, çünkü en kolay derleme hiç derlemenin olmadığı yerdir.

Tersine, dil, programcının amacını ne kadar ifade ederse, bu amaçla alınan araçlardan ziyade, hem yazarken hem de bakım sırasında programı daha anlaşılır hale getirir.

Şimdi, switchher zaman eşdeğer if-elseblok zincirine veya benzerine dönüştürülerek derlenebilirdi , ancak bir kişinin bir değer aldığı, ondan bir ofset hesapladığı (bir tabloya bakarak olsun) belirli bir ortak montaj desenine derlemeye izin vermek olarak tasarlandı. değerin mükemmel bir karmasıyla veya * değerindeki gerçek aritmetikle dizine eklenir). Bu noktada, C # derlemesinin bazen switcheşdeğer hale geleceği if-elseve bazen karma tabanlı bir atlama yaklaşımı (ve benzer şekilde C, C ++ ve benzer sözdizimine sahip diğer diller) kullanacağını belirtmek gerekir .

Bu durumda düşmeye izin vermenin iki iyi nedeni vardır:

  1. Her neyse doğal olarak gerçekleşir: bir talimatlar kümesine bir atlama tablosu oluşturursanız ve daha önceki talimat gruplarından biri bir tür atlama veya geri dönüş içermiyorsa, yürütme doğal olarak bir sonraki partiye ilerleyecektir. Düşenlere izin vermek, switch-ullanıcı C'yi makine kodunu kullanarak atlama tablosuna dönüştürürseniz "gerçekleşecekti" idi .

  2. Montajda yazan kodlayıcılar eşdeğeri için zaten kullanılmışlardı: montajda elle bir atlama tablosu yazarken, belirli bir kod bloğunun bir dönüşle mi yoksa masanın dışına atlayarak mı yoksa devam edip etmeyeceğini düşünmek zorunda kalacaklardı. sonraki bloğa. Bu nedenle, kodlayıcının breakgerektiğinde açık bir şekilde eklenmesi kodlayıcı için de "doğal" dır.

O zamanlar, hem üretilen makine kodu hem de kaynak kodun ifade edilebilirliği ile ilgili olduğu için, bir bilgisayar dilinin iki hedefini dengelemek için makul bir girişimdi.

Ancak kırk yıl sonra, birkaç nedenden ötürü işler tamamen aynı değildir:

  1. Bugün C'deki kodlayıcıların montaj deneyimi çok az olabilir veya hiç olmayabilir. Diğer birçok C stili dilde kodlayıcıların (özellikle Javascript!) "İnsanların toplantıdan ne alışkın oldukları" kavramı artık geçerli değildir.
  2. Optimizasyonlardaki gelişmeler, switchya if-elseyaklaşımın en verimli olacağı düşünülen ya da atlama tablosu yaklaşımının özellikle ezoterik bir varyantına dönüştüğü için dönüştürülme olasılığının daha yüksek olduğu anlamına gelir. Üst ve alt düzey yaklaşımlar arasındaki haritalama eskisi kadar güçlü değildir.
  3. Deneyimler, düşüşün normdan ziyade azınlık durumu olma eğiliminde olduğunu göstermiştir (Sun'ın derleyicisinin yaptığı bir çalışmada, switchblokların % 3'ünün aynı blokta birden çok etiketten başka bir düşme kullandığını ve Buradaki durum, bu% 3'ün aslında normalden çok daha yüksek olduğu anlamına geliyordu). Bu nedenle, çalışılan dil, olağandışı olanı ortak olandan daha kolay bir şekilde hazır hale getirir.
  4. Deneyimler, düşmenin hem kazayla yapıldığı durumlarda hem de kodu koruyan biri tarafından doğru düşmenin kaçırıldığı durumlarda sorun kaynağı olduğunu göstermiştir. Bu sonuncusu, düşme ile ilişkili hatalara ince bir ektir, çünkü kodunuz mükemmel bir şekilde hatasız olsa bile, düşme yine de sorunlara neden olabilir.

Bu son iki nokta ile ilgili olarak, mevcut K&R sürümünden şu alıntıyı göz önünde bulundurun:

Bir durumdan diğerine düşmek sağlam değildir, program değiştirildiğinde parçalanmaya eğilimlidir. Tek bir hesaplama için birden fazla etiket haricinde, düşüşler az kullanılmalı ve yorumlanmalıdır.

İyi bir form olarak, mantıksal olarak gereksiz olsa da, son durumdan sonra (burada varsayılan) bir mola verin. Bir gün sonunda başka bir vaka eklendiğinde, bu savunma programı biraz sizi kurtaracak.

Bu nedenle, atın ağzından, C'ye düşme problemlidir. Düşüşleri her zaman yorumlarla belgelemek iyi bir uygulama olarak kabul edilir, bu, kişinin olağandışı bir şeyi nerede yapması gerektiğini belgelemesi gereken genel prensibin bir uygulamasıdır, çünkü kodun daha sonra incelenmesini ve / veya kodunuzun buna benzemesini sağlar. aslında doğru olduğunda bir acemi hatası var.

Ve bunu düşündüğünüzde şöyle kodlayın:

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

Kodda geçişi açık yapmak için bir şey ekliyor, derleyici tarafından tespit edilebilen (veya yokluğu tespit edilebilen) bir şey değil.

Bu nedenle, C # 'da düşme ile açık olması gerektiği gerçeği, diğer C stili dillerinde iyi yazmış olan insanlara, düşme durumlarında zaten açık olacakları için herhangi bir ceza eklemez. †

Son olarak, gotoburada kullanımı zaten C ve diğer dillerden bir normdur:

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

Bir önceki bloğa bir tane getiren değerden başka bir değer için yürütülen koda bir bloğun dahil edilmesini istediğimiz bu durumda, zaten kullanmak zorundayız goto. (Elbette, farklı koşullarla bundan kaçınmanın yolları ve yolları vardır, ancak bu, bu soruyla ilgili her şey için geçerlidir). Böyle bir C #, bir kodda birden fazla kod bloğuna çarpmak istediğimiz bir durumla başa çıkmak için zaten normal bir yol üzerine inşa edildi switchve sadece düşmeyi de kapsayacak şekilde genelleştirdi. Ayrıca, her iki durumu da daha rahat ve kendi kendini belgelendirdi, çünkü C'ye yeni bir etiket eklememiz gerekiyor, ancak caseC #'da etiket olarak kullanabiliyoruz . C # 'da below_sixetiketten kurtulabiliriz ve goto case 5ne yaptığımız konusunda daha net olanı kullanabiliriz . (Ayrıca eklemek zorundayızbreakiçin, defaultsadece yukarıdaki C kodunu açıkça C # kodu yapmak için bıraktım).

Özet olarak bu nedenle:

  1. C # artık doğrudan C kodunun 40 yıl önce yaptığı gibi (ve bu günlerde C de) optimize edilmemiş derleyici çıktısı ile ilgili değildir, bu da düşme ilhamlarından birini ilgisiz kılar.
  2. C # break, benzer dillere aşina olanlar tarafından dili daha kolay öğrenmek ve daha kolay taşıma için yalnızca örtük olmakla kalmaz .
  3. C #, son kırk yıldır sorunlara neden olduğu iyi belgelenmiş olası bir hata kaynağını veya yanlış anlaşılmış kodu kaldırır.
  4. C #, derleyici tarafından C (belge düşme) ile mevcut en iyi uygulamayı yapar.
  5. C # alışılmadık durumu daha açık kodlu olanı yapar, normal durum kodu olan kişi otomatik olarak yazar.
  6. C #, C'de kullanıldığı gibi gotofarklı caseetiketlerden aynı bloğa vurmak için aynı temelli yaklaşımı kullanır. Sadece bazı diğer durumlarda genelleştirir.
  7. C #, ifadelerin etiket olarak hareket gotoetmesine izin vererek , bu temelli yaklaşımı C'den daha rahat ve net hale getirir case.

Sonuçta, oldukça makul bir tasarım kararı


* BASIC'in bazı formları, GOTO (x AND 7) * 50 + 240kırılgan ve dolayısıyla yasaklamak için özellikle ikna edici bir durum olsa da , beğenilerinizi yapmanıza izin verir goto, daha düşük seviyeli kodun manuel olarak sürdürülmesi gereken bir şeyden ziyade derlemenin sonucu olduğunda çok daha makul olan bir değer üzerine aritmetik. Özellikle Duff'ın Cihazının uygulamaları, eşdeğer makine koduna veya IL'ye iyi bir şekilde katkıda bulunur, çünkü her talimat bloğu, nopdolgu maddelerinin eklenmesine gerek kalmadan genellikle aynı uzunlukta olacaktır .

† Duff's Device yine makul bir istisna olarak karşımıza çıkıyor. Bu ve benzer örüntülerle operasyonların tekrarı olması, düşme kullanımını bu etkiye açık bir yorum yapmasa bile nispeten açık hale getirmeye yarar.


17

'Vaka etiketine gidebilirsiniz' http://www.blackwasp.co.uk/CSharpGoto.aspx

Goto ifadesi, programın denetimini koşulsuz olarak başka bir ifadeye aktaran basit bir komuttur. Komut genellikle, bazı geliştiricilerin spagetti koduna yol açabileceği için tüm üst düzey programlama dillerinden kaldırılmasını savunan eleştirilerle eleştirilir . Bu, kodun okunması ve bakımı zorlaşan birçok goto ifadesi veya benzer atlama ifadesi olduğunda oluşur. Bununla birlikte, goto ifadesinin dikkatle kullanıldığında bazı sorunlara zarif bir çözüm sunduğuna dikkat çeken programcılar var ...


8

Bu davranışı, irade tarafından kullanılmadığı, ancak sorun yarattığı zamanlardan kaçınmak için tasarımla bıraktılar.

Yalnızca vaka bölümünde herhangi bir ifade yoksa kullanılabilir:

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

4

C # için switch deyimini (C / Java / C ++ 'dan) değiştirdiler. Sanırım bunun sebebi insanların düşüşü unutması ve hatalara yol açmasıydı. Okuduğum bir kitabın simüle etmek için goto kullandığını söyledi, ama bu benim için iyi bir çözüm gibi gelmiyor.


2
C # goto destekler, ancak tam dönüşü değil mi? Vay. Ve bu sadece onlar değil. C # bu şekilde davrandığını bildiğim tek dildir.
Matthew Scharley

İlk başta tam olarak beğenmedim, ama "düşme" gerçekten felaket için bir reçete (özellikle genç programcılar arasında.) Birçok kişinin işaret ettiği gibi, C # hala boş çizgiler için düşmeye izin veriyor ( "Kenny" anahtar kasası ile zarif Goto kullanımını vurgulayan bir bağlantı yayınladı.
Pretzel

Bence çok büyük bir anlaşma değil. Zamanın% 99'u bir düşüş istemiyorum ve geçmişte böcekler tarafından yakıldım.
Ken

1
"Bu benim için iyi bir çözüm gibi gelmiyor" - bunu duyduğuma üzüldüm, çünkü bunun goto caseiçin var. Geçişe göre avantajı açık olmasıdır. Buradaki bazı insanlar goto case, konuyu anlamadan "goto" ya karşı aşılandıklarını ve kendi başlarına düşünemediklerini gösteriyor. Dijkstra "GOTO Zararlı Olarak Değerlendirildi" yazdığında, kontrol akışını değiştirmek için başka yolu olmayan dilleri ele alıyordu.
Jim Balter

@JimBalter ve sonra Dijkstra üzerinde teklif kaç kişi Knuth "erken optimizasyon tüm kötülüğün kökü" olduğunu teklif edecek olsa da, bu teklif Knuth açıkça gotokod optimize ederken ne kadar yararlı olabileceği hakkında yazarken oldu ?
Jon Hanna

1

Goto anahtar sözcüğü ile c ++ gibi düşüşe geçebilirsiniz.

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

9
Sadece birileri bunu iki yıl önce yayınlasaydı!

0

Her vaka bloğundan sonra, bir vaka ifadesi mi yoksa varsayılan bir ifade mi olduğu son blok da dahil olmak üzere, kesme gibi bir atlama ifadesi gereklidir. Bir istisna dışında, (C ++ anahtar ifadesinin aksine), C #, bir vaka etiketinden diğerine örtük bir düşüşü desteklemez. Bunun tek istisnası, bir case deyiminin kodu yoksa.

- C # switch () belgeleri


1
Bu davranışın belgelendiğini, neden böyle olduğunu ve eski davranışı elde etmek için herhangi bir alternatifi bilmek istiyorum.
Matthew Scharley

0

Her vaka ifadesinden sonra , varsayılan bir vaka olsa bile break veya goto ifadesi gerekir.


2
Sadece birileri bunu iki yıl önce yayınlasaydı!

1
@Poldie ilk seferinde komikti ... Shilpa her durum için bir mola ya da gitmeye gerek yok, sadece kendi kodu olan her vaka için. Kodu iyi paylaşan birden çok vakanız olabilir.
Maverick

0

Sadece hızlı bir not Xamarin için derleyici aslında bu yanlış anladı ve dönüş izin verir eklemek için. Sözde düzeltildi, ancak serbest bırakılmadı. Bu aslında düşen bazı kodda keşfetti ve derleyici şikayet etmedi.



-1

C #, switch / case ifadeleri ile düşüşü desteklemez. Neden olduğundan emin değilim, ama gerçekten destek yok. bağlantı


Bu çok yanlış. Diğer cevaplara bakın ... örtük bir düşüşün etkisi açık bir şekilde elde edilebilir goto case. Bu, C # tasarımcıları tarafından kasıtlı, akıllı bir tasarım seçeneğiydi.
Jim Balter

-11

"Break" i eklemeyi unuttunuz. 3. durumda 2 if if bloğuna yazdınız. Bu nedenle şunu deneyin:

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

2
Bu büyük ölçüde yanlış çıktı üretir. Anahtar ifadelerini tasarım gereği dışında bıraktım. Soru, neredeyse hiçbir dilde bu kısıtlama olmadığında, C # derleyicisinin bunu bir hata olarak görmesinin nedenidir.
Matthew Scharley

5
Kavramak ne çarpıcı bir başarısızlık. Ve bunu silmek için 5 yıl geçirdin ve hala yapmadın mı? Mindboggling.
Jim Balter
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.